Repository: rdmorganiser/rdmo (language: python)

Issue:
Dev 2.0.0 cleanup+refactor config
This PR:

* Refactors the way the config store works and how it persists into the local storage.
* Adds a show/hide link button in the nested view.

Needs to be merged after #633.


Discussion / hints:

Fix patch:
diff --git a/package-lock.json b/package-lock.json
index 117a692097..3429fc7bde 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,6 @@
         "font-awesome": "4.7.0",
         "jquery": "^3.6.0",
         "js-cookie": "^2.2.1",
-        "local-storage": "^2.0.0",
         "lodash": "^4.17.21",
         "popper.js": "^1.16.1",
         "prop-types": "^15.7.2",
@@ -5155,11 +5154,6 @@
         "node": ">=8.9.0"
       }
     },
-    "node_modules/local-storage": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/local-storage/-/local-storage-2.0.0.tgz",
-      "integrity": "sha512-/0sRoeijw7yr/igbVVygDuq6dlYCmtsuTmmpnweVlVtl/s10pf5BCq8LWBxW/AMyFJ3MhMUuggMZiYlx6qr9tw=="
-    },
     "node_modules/locate-path": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -10963,11 +10957,6 @@
         "json5": "^2.1.2"
       }
     },
-    "local-storage": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/local-storage/-/local-storage-2.0.0.tgz",
-      "integrity": "sha512-/0sRoeijw7yr/igbVVygDuq6dlYCmtsuTmmpnweVlVtl/s10pf5BCq8LWBxW/AMyFJ3MhMUuggMZiYlx6qr9tw=="
-    },
     "locate-path": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
diff --git a/package.json b/package.json
index 7343d2127c..578c025533 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,6 @@
     "font-awesome": "4.7.0",
     "jquery": "^3.6.0",
     "js-cookie": "^2.2.1",
-    "local-storage": "^2.0.0",
     "lodash": "^4.17.21",
     "popper.js": "^1.16.1",
     "prop-types": "^15.7.2",
diff --git a/rdmo/conditions/imports.py b/rdmo/conditions/imports.py
index e33edeaf49..809dd6ea6a 100644
--- a/rdmo/conditions/imports.py
+++ b/rdmo/conditions/imports.py
@@ -2,8 +2,8 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (set_common_fields, set_foreign_field,
-                               validate_instance)
+from rdmo.core.imports import (check_permissions, set_common_fields,
+                               set_foreign_field, validate_instance)
 
 from .models import Condition
 from .validators import ConditionLockedValidator, ConditionUniqueURIValidator
@@ -11,7 +11,7 @@
 logger = logging.getLogger(__name__)
 
 
-def import_condition(element, save=False):
+def import_condition(element, save=False, user=None):
     try:
         condition = Condition.objects.get(uri=element.get('uri'))
     except Condition.DoesNotExist:
@@ -27,6 +27,8 @@ def import_condition(element, save=False):
 
     validate_instance(condition, element, ConditionLockedValidator, ConditionUniqueURIValidator)
 
+    check_permissions(condition, element, user)
+
     if save and not element.get('errors'):
         if condition.id:
             element['updated'] = True
diff --git a/rdmo/conditions/migrations/0024_uri_path.py b/rdmo/conditions/migrations/0024_uri_path.py
new file mode 100644
index 0000000000..cf81489c0e
--- /dev/null
+++ b/rdmo/conditions/migrations/0024_uri_path.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.2.19 on 2023-06-29 12:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('conditions', '0023_condition_editors'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='condition',
+            old_name='key',
+            new_name='uri_path',
+        ),
+        migrations.AlterField(
+            model_name='condition',
+            name='uri',
+            field=models.URLField(blank=True, help_text='The Uniform Resource Identifier of this condition (auto-generated).', max_length=800, verbose_name='URI'),
+        ),
+        migrations.AlterField(
+            model_name='condition',
+            name='uri_path',
+            field=models.SlugField(blank=True, help_text='The path for the URI of this condition.', max_length=512, verbose_name='URI Path'),
+        ),
+    ]
diff --git a/rdmo/conditions/models.py b/rdmo/conditions/models.py
index 4251305dfb..e1a3303294 100644
--- a/rdmo/conditions/models.py
+++ b/rdmo/conditions/models.py
@@ -31,7 +31,7 @@ class Condition(models.Model):
     )
 
     uri = models.URLField(
-        max_length=640, blank=True,
+        max_length=800, blank=True,
         verbose_name=_('URI'),
         help_text=_('The Uniform Resource Identifier of this condition (auto-generated).')
     )
@@ -40,10 +40,10 @@ class Condition(models.Model):
         verbose_name=_('URI Prefix'),
         help_text=_('The prefix for the URI of this condition.')
     )
-    key = models.SlugField(
-        max_length=128, blank=True,
-        verbose_name=_('Key'),
-        help_text=_('The internal identifier of this condition.')
+    uri_path = models.SlugField(
+        max_length=512, blank=True,
+        verbose_name=_('URI Path'),
+        help_text=_('The path for the URI of this condition.')
     )
     comment = models.TextField(
         blank=True,
@@ -87,17 +87,12 @@ class Meta:
         verbose_name_plural = _('Conditions')
 
     def __str__(self):
-        return self.key
+        return self.uri
 
     def save(self, *args, **kwargs):
-        self.uri = self.build_uri(self.uri_prefix, self.key)
+        self.uri = self.build_uri(self.uri_prefix, self.uri_path)
         super().save(*args, **kwargs)
 
-    def copy(self, uri_prefix, key):
-        condition = copy_model(self, uri_prefix=uri_prefix, key=key, source=self.source, target_option=self.target_option)
- 
-        return condition
-
     @property
     def source_label(self):
         return self.source.uri
@@ -238,6 +233,7 @@ def _resolve_not_empty(self, values):
         return False
 
     @classmethod
-    def build_uri(cls, uri_prefix, key):
-        assert key
-        return join_url(uri_prefix or settings.DEFAULT_URI_PREFIX, '/conditions/', key)
+    def build_uri(cls, uri_prefix, uri_path):
+        if not uri_path:
+            raise RuntimeError('uri_path is missing')
+        return join_url(uri_prefix or settings.DEFAULT_URI_PREFIX, '/conditions/', uri_path)
diff --git a/rdmo/conditions/renderers/mixins.py b/rdmo/conditions/renderers/mixins.py
index 2152fc5da6..f838bc94bb 100644
--- a/rdmo/conditions/renderers/mixins.py
+++ b/rdmo/conditions/renderers/mixins.py
@@ -6,7 +6,7 @@ def render_condition(self, xml, condition):
 
             xml.startElement('condition', {'dc:uri': condition['uri']})
             self.render_text_element(xml, 'uri_prefix', {}, condition['uri_prefix'])
-            self.render_text_element(xml, 'key', {}, condition['key'])
+            self.render_text_element(xml, 'uri_path', {}, condition['uri_path'])
             self.render_text_element(xml, 'dc:comment', {}, condition['comment'])
             self.render_text_element(xml, 'source', {'dc:uri': condition['source']['uri']}, None)
             self.render_text_element(xml, 'relation', {}, condition['relation'])
diff --git a/rdmo/conditions/serializers/export.py b/rdmo/conditions/serializers/export.py
index 4eb3605e74..f03dd055bc 100644
--- a/rdmo/conditions/serializers/export.py
+++ b/rdmo/conditions/serializers/export.py
@@ -15,7 +15,7 @@ class Meta:
         fields = (
             'uri',
             'uri_prefix',
-            'key',
+            'uri_path',
             'comment',
             'source',
             'relation',
diff --git a/rdmo/conditions/serializers/v1.py b/rdmo/conditions/serializers/v1.py
index d262a3b30f..6723584afb 100644
--- a/rdmo/conditions/serializers/v1.py
+++ b/rdmo/conditions/serializers/v1.py
@@ -1,8 +1,7 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementExportSerializerMixin,
-                                   ElementModelSerializerMixin,
-                                   ReadOnlyObjectPermissionsSerializerMixin)
+from rdmo.core.serializers import (ElementModelSerializerMixin,
+                                   ReadOnlyObjectPermissionSerializerMixin)
 from rdmo.domain.models import Attribute
 from rdmo.options.models import OptionSet
 from rdmo.questions.models import Page, Question, QuestionSet
@@ -12,13 +11,13 @@
 from ..validators import ConditionLockedValidator, ConditionUniqueURIValidator
 
 
-class ConditionSerializer(ElementModelSerializerMixin, ReadOnlyObjectPermissionsSerializerMixin,
+class ConditionSerializer(ElementModelSerializerMixin, ReadOnlyObjectPermissionSerializerMixin,
                           serializers.ModelSerializer):
 
     model = serializers.SerializerMethodField()
-    key = serializers.SlugField(required=True)
-    source = serializers.PrimaryKeyRelatedField(queryset=Attribute.objects.all(), required=True)
+    uri_path = serializers.CharField(required=True)
 
+    source = serializers.PrimaryKeyRelatedField(queryset=Attribute.objects.all(), required=True)
     optionsets = serializers.PrimaryKeyRelatedField(queryset=OptionSet.objects.all(), required=False, many=True)
     pages = serializers.PrimaryKeyRelatedField(queryset=Page.objects.all(), required=False, many=True)
     questionsets = serializers.PrimaryKeyRelatedField(queryset=QuestionSet.objects.all(), required=False, many=True)
@@ -34,10 +33,9 @@ class Meta:
             'model',
             'uri',
             'uri_prefix',
-            'key',
+            'uri_path',
             'comment',
             'locked',
-            'read_only',
             'editors',
             'source',
             'relation',
@@ -47,7 +45,8 @@ class Meta:
             'pages',
             'questionsets',
             'questions',
-            'tasks'
+            'tasks',
+            'read_only'
         )
         validators = (
             ConditionUniqueURIValidator(),
@@ -55,16 +54,6 @@ class Meta:
         )
 
 
-class ConditionListSerializer(ElementExportSerializerMixin, ConditionSerializer):
-
-    xml_url = serializers.SerializerMethodField()
-
-    class Meta(ConditionSerializer.Meta):
-        fields = ConditionSerializer.Meta.fields + (
-            'xml_url',
-        )
-
-
 class ConditionIndexSerializer(serializers.ModelSerializer):
 
     class Meta:
diff --git a/rdmo/conditions/static/conditions/css/conditions.scss b/rdmo/conditions/static/conditions/css/conditions.scss
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/rdmo/conditions/static/conditions/js/conditions.js b/rdmo/conditions/static/conditions/js/conditions.js
deleted file mode 100644
index f0bea5f171..0000000000
--- a/rdmo/conditions/static/conditions/js/conditions.js
+++ /dev/null
@@ -1,139 +0,0 @@
-angular.module('conditions', ['core'])
-
-.factory('ConditionsService', ['$resource', '$timeout', '$window', '$q', '$filter', 'utils', function($resource, $timeout, $window, $q, $filter, utils) {
-
-    /* get the base url */
-
-    var baseurl = angular.element('meta[name="baseurl"]').attr('content');
-
-    /* configure resources */
-
-    var resources = {
-        conditions: $resource(baseurl + 'api/v1/conditions/conditions/:list_action/:id/:detail_action/'),
-        relations: $resource(baseurl + 'api/v1/conditions/relations/:id/'),
-        attributes: $resource(baseurl + 'api/v1/domain/attributes/:id/'),
-        options: $resource(baseurl + 'api/v1/options/options/:id/'),
-        settings: $resource(baseurl + 'api/v1/core/settings/'),
-        sites: $resource(baseurl + 'api/v1/core/sites/')
-    };
-
-    /* configure factories */
-
-    var factories = {
-        conditions: function(parent) {
-            return {
-                source: null,
-                relation: null,
-                target_option: null,
-                uri_prefix: (angular.isDefined(parent) && parent) ? parent.uri_prefix : service.settings.default_uri_prefix
-            };
-        }
-    };
-
-    /* create the conditions service */
-
-    var service = {};
-
-    service.init = function(options) {
-        service.relations = resources.relations.query();
-        service.settings = resources.settings.get();
-        service.sites = resources.sites.query();
-        service.uri_prefixes = []
-        service.uri_prefix = ''
-        service.filter = sessionStorage.getItem('conditions_filter') || '';
-
-        service.initView().then(function () {
-            var current_scroll_pos = sessionStorage.getItem('conditions_scroll_pos');
-            if (current_scroll_pos) {
-                $timeout(function() {
-                    $window.scrollTo(0, current_scroll_pos);
-                });
-            }
-        });
-
-        $window.addEventListener('beforeunload', function() {
-            sessionStorage.setItem('conditions_scroll_pos', $window.scrollY);
-            sessionStorage.setItem('conditions_filter', service.filter);
-        });
-    };
-
-    service.initView = function(options) {
-        return resources.conditions.query({list_action: 'index'}, function(response) {
-            service.conditions = response;
-            service.uri_prefixes = response.reduce(function(list, item) {
-                if (list.indexOf(item.uri_prefix) < 0) {
-                    list.push(item.uri_prefix)
-                }
-                return list
-            }, [])
-        }).$promise;
-    };
-
-    service.openFormModal = function(resource, obj, create, copy) {
-        service.errors = {};
-        service.values = utils.fetchValues(resources[resource], factories[resource], obj, create, copy);
-        service.attributes = resources.attributes.query();
-        service.options = resources.options.query();
-
-        $q.all([
-            service.values.$promise,
-            service.attributes.$promise,
-            service.options.$promise
-        ]).then(function() {
-            $('#' + resource + '-form-modal').modal('show');
-            $timeout(function() {
-                $('formgroup[data-quicksearch="true"]').trigger('refresh');
-            });
-        });
-    };
-
-    service.submitFormModal = function(resource) {
-        utils.storeValues(resources[resource], service.values).then(function() {
-            $('#' + resource + '-form-modal').modal('hide');
-            service.initView();
-        }, function(result) {
-            service.errors = result.data;
-        });
-    };
-
-    service.openDeleteModal = function(resource, obj) {
-        utils.fetchValues(resources[resource], factories[resource], obj).$promise.then(function(response) {
-            service.values = response;
-            $('#' + resource + '-delete-modal').modal('show');
-        });
-    };
-
-    service.submitDeleteModal = function(resource) {
-        resources[resource].delete({id: service.values.id}, function() {
-            $('#' + resource + '-delete-modal').modal('hide');
-            service.initView();
-        });
-    };
-
-    service.openShowModal = function(resource, obj) {
-        service.values = utils.fetchValues(resources[resource], factories[resource], obj)
-
-        $q.when(service.values.$promise).then(function() {
-            $('#' + resource + '-show-modal').modal('show');
-        });
-    };
-
-    service.hideCondition = function(item) {
-        if (service.filter && item.uri.indexOf(service.filter) < 0) {
-            return true;
-        }
-        if (service.uri_prefix && item.uri_prefix != service.uri_prefix) {
-            return true;
-        }
-    };
-
-    return service;
-
-}])
-
-.controller('ConditionsController', ['$scope', 'ConditionsService', function($scope, ConditionsService) {
-
-    $scope.service = ConditionsService;
-    $scope.service.init();
-
-}]);
diff --git a/rdmo/conditions/templates/conditions/conditions.html b/rdmo/conditions/templates/conditions/conditions.html
deleted file mode 100644
index e8a7428098..0000000000
--- a/rdmo/conditions/templates/conditions/conditions.html
+++ /dev/null
@@ -1,174 +0,0 @@
-{% extends 'core/page.html' %}
-{% load static %}
-{% load compress %}
-{% load i18n %}
-{% load core_tags %}
-
-{% block head %}
-    {% vendor 'angular' %}
-    {% vendor 'select2' %}
-    {% vendor 'select2-bootstrap-theme' %}
-
-    {% compress css %}
-    <link rel="stylesheet" type="text/x-scss" href="{% static 'conditions/css/conditions.scss' %}" />
-    {% endcompress %}
-
-    {% compress js %}
-    <script type="text/javascript" src="{% static 'core/js/core.js' %}" ></script>
-    <script type="text/javascript" src="{% static 'conditions/js/conditions.js' %}" ></script>
-    {% endcompress %}
-{% endblock %}
-
-{% block bodyattr %} ng-app="conditions" ng-controller="ConditionsController" {% endblock %}
-
-{% block sidebar %}
-
-    <h2>{% trans 'Filter conditions' %}</h2>
-
-    <div class="form-group">
-        <div class="input-group">
-            <input type="text" class="form-control" ng-model="service.filter"
-                   placeholder="{% trans 'Search' %}"></input>
-            <span class="input-group-btn" ng-click="service.filter = ''">
-                <button class="btn btn-default">
-                    <span class="fa fa-times"></span>
-                </button>
-            </span>
-        </div>
-    </div>
-
-    <div class="form-group">
-        <select class="form-control" ng-model="service.uri_prefix">
-            <option value="">{% trans 'All URI prefixes' %}</option>
-            <option ng-repeat="uri_prefix in service.uri_prefixes" value="{$ uri_prefix $}">
-                {$ uri_prefix $}
-            </option>
-        </select>
-    </div>
-
-    <h2>{% trans 'Options' %}</h2>
-
-    <ul class="list-unstyled">
-        <li>
-            <a href="" ng-click="service.openFormModal('conditions', null, true)">
-                {% trans 'Create new condition' %}
-            </a>
-        </li>
-    </ul>
-
-    <h2>{% trans 'Export' %}</h2>
-
-    <ul class="list-unstyled">
-        {% for format, text in export_formats %}
-        <li >
-            <a href="{% url 'conditions_export' format %}" target="_blank">
-                {{ text }}
-            </a>
-        </li>
-        {% endfor %}
-    </ul>
-    <ul class="list-unstyled">
-        <li >
-            <a href="{% url 'conditions_export' 'xml' %}" target="_blank">
-                {% trans 'XML' %}
-            </a>
-        </li>
-    </ul>
-
-    <h2>{% trans 'Import' %}</h2>
-    <ul class="list-unstyled">
-        <li>
-            {% url 'upload' as upload_url %}
-            {% include 'core/upload_form.html' with upload_url=upload_url %}
-        </li>
-    </ul>
-
-{% endblock %}
-
-{% block page %}
-
-    <h1>{% trans 'Conditions' %}</h1>
-
-    <div ng-repeat="condition in service.conditions" ng-cloak>
-
-        <div class="panel panel-default" ng-hide="service.hideCondition(condition)">
-            <div class="panel-heading">
-                <div class="pull-right">
-                    <i class="fa fa-lock text-danger" ng-show="condition.locked"></i>
-                    <i class="fa fa-ban text-muted" ng-show="condition.read_only"
-                        title="{% trans 'Read only' %}">
-                    </i>
-
-                    <a href="" class="fa fa-pencil"
-                        title="{% trans 'Update condition' %}"
-                        ng-click="service.openFormModal('conditions', condition)">
-                    </a>
-                    <a href="" class="fa fa-copy"
-                        title="{% trans 'Copy condition' %}"
-                        ng-click="service.openFormModal('conditions', condition, false, true)">
-                    </a>
-                    <a href="" class="fa fa-eye"
-                        title="{% trans 'Show references' %}"
-                        ng-click="service.openShowModal('conditions', condition)">
-                    </a>
-                    <a href="{$ condition.xml_url $}" class="fa fa-download"
-                        title="{% trans 'Export condition as XML' %}"
-                        target="blank">
-                    </a>
-                    <a href="" class="fa fa-trash"
-                        title="{% trans 'Delete condition' %}"
-                        ng-click="service.openDeleteModal('conditions', condition)">
-                    </a>
-                </div>
-                <strong ng-class="{'text-muted': condition.read_only}">
-                    {% trans 'Condition' %}
-                </strong>
-                <code class="code-conditions">{$ condition.uri $}</code>
-            </div>
-
-            <ul class="list-group">
-                <li class="list-group-item">
-                    <code>{$ condition.source_label $}</code>
-                    <span>{$ condition.relation_label $}</span>
-                    <span ng-show="condition.target_option_uri">
-                        <code class="code-options">{$ condition.target_option_uri $}</code> ("{$ condition.target_option_text $}")
-                    </span>
-                    <span ng-show="condition.target_text">
-                        "{$ condition.target_text $}"
-                    </span>
-                </li>
-                <li class="list-group-item" ng-show="condition.description">
-                    {$ condition.description $}
-                </li>
-            </ul>
-            {% if settings.MULTISITE %}
-            <div class="panel-body">
-                <div class="row">
-                    <div class="col-sm-3">
-                        <strong>{% trans 'Editors' %}</strong>
-                    </div>
-                    <div class="col-sm-9">
-                        <ul>
-                            <li ng-show="condition.editors.length > 0 && condition.editors.length < service.sites.length"
-                                ng-repeat="editor in condition.editors">
-                                {$ editor.name $}
-                            </li>
-                            <li ng-show="condition.editors.length == 0 || condition.editors.length == service.sites.length">
-                                {% trans 'All sites' %}
-                            </li>
-                        </ul>
-                    </div>
-                </div>
-            </div>
-            {% endif %}
-
-        </div>
-    </div>
-
-    {% include 'conditions/conditions_modal_form_conditions.html' %}
-
-    {% include 'conditions/conditions_modal_show_conditions.html' %}
-
-    {% include 'conditions/conditions_modal_delete_conditions.html' %}
-
-{% endblock %}
diff --git a/rdmo/conditions/templates/conditions/conditions_modal_delete_conditions.html b/rdmo/conditions/templates/conditions/conditions_modal_delete_conditions.html
deleted file mode 100644
index 91ce2063c9..0000000000
--- a/rdmo/conditions/templates/conditions/conditions_modal_delete_conditions.html
+++ /dev/null
@@ -1,53 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="conditions-delete-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Delete condition' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with object='{$ service.values.uri $}' %}
-                        You are about to permanently delete the condition <code>{{ object }}</code>.
-                        {% endblocktrans %}
-                    </p>
-
-                    <p>
-                        {% trans 'This condition is used in the following elements:' %}
-                    </p>
-
-                    <ul>
-                        <li><b>{$ service.values.optionsets.length $} {% trans 'option sets' %}</b></li>
-                        <li><b>{$ service.values.questionsets.length $} {% trans 'question sets' %}</b></li>
-                        <li><b>{$ service.values.questions.length $} {% trans 'questions' %}</b></li>
-                        <li><b>{$ service.values.tasks.length $} {% trans 'tasks' %}</b></li>
-                    </ul>
-
-                    <p class="text-danger">
-                        {% trans 'This action cannot be undone!' %}
-                    </p>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-danger"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitDeleteModal('conditions')">
-                        {% trans 'Delete' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/conditions/templates/conditions/conditions_modal_form_conditions.html b/rdmo/conditions/templates/conditions/conditions_modal_form_conditions.html
deleted file mode 100644
index 94481dcb13..0000000000
--- a/rdmo/conditions/templates/conditions/conditions_modal_form_conditions.html
+++ /dev/null
@@ -1,163 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="conditions-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        <span ng-hide="service.values.id">{% trans 'Create condition' %}</span>
-                        <span ng-show="service.values.copy">{% trans 'Copy condition' %}</span>
-                        <span ng-show="service.values.id && !service.values.copy">{% trans 'Update condition' %}</span>
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <div class="copy-block" ng-show="service.values.copy">
-                        <p>
-                            {% trans 'Please enter the values for the copy of the condition below and change at least the <strong>key</strong>.' %}
-                        </p>
-                        <p>
-                            {% trans 'Other properties can be changed once the new condition is created.' %}
-                        </p>
-                    </div>
-                    <div class="row">
-                        <div class="col-sm-6">
-                            <div class="form-group" ng-class="{'has-error': service.errors.uri_prefix }">
-                                <label class="control-label" for="uri_prefix">
-                                    {{ meta.Condition.uri_prefix.verbose_name }}
-                                </label>
-
-                                <div class="input-group">
-                                    <input class="form-control"
-                                           id="condition_uri_prefix"
-                                           type="text"
-                                           ng-model="service.values.uri_prefix" />
-
-                                    <span class="input-group-btn">
-                                        <button type="button" class="btn btn-default"
-                                            title="{% trans 'Insert default URI Prefix' %}"
-                                            ng-click="service.values.uri_prefix = service.settings.default_uri_prefix">
-
-                                            <span class="fa fa-magic"></span>
-                                        </button>
-                                    </span>
-                                </div>
-
-                                <p class="help-block">
-                                    {{ meta.Condition.uri_prefix.help_text }}
-                                </p>
-
-                                <ul class="help-block list-unstyled" ng-show="service.errors.uri_prefix">
-                                    <li ng-repeat="error in service.errors.uri_prefix">
-                                        {$ error $}
-                                    </li>
-                                </ul>
-                            </div>
-                        </div>
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="condition_key"
-                                data-type="text"
-                                data-label="{{ meta.Condition.key.verbose_name }}"
-                                data-help="{{ meta.Condition.key.help_text }}"
-                                data-model="service.values.key"
-                                data-errors="service.errors.key">
-                            </formgroup>
-                        </div>
-                    </div>
-
-                    <div ng-hide="service.values.copy">
-                        <formgroup
-                            data-id="condition_comment"
-                            data-type="textarea"
-                            data-label="{{ meta.Condition.comment.verbose_name }}"
-                            data-help="{{ meta.Condition.comment.help_text }}"
-                            data-model="service.values.comment"
-                            data-errors="service.errors.comment">
-                        </formgroup>
-                        <formgroup
-                            data-id="condition_locked"
-                            data-type="checkbox"
-                            data-label="{{ meta.Condition.locked.verbose_name }}"
-                            data-help="{{ meta.Condition.locked.help_text }}"
-                            data-model="service.values.locked"
-                            data-errors="service.errors.locked">
-                        </formgroup>
-                        <formgroup
-                            data-id="condition_source"
-                            data-type="selectnumber"
-                            data-label="{{ meta.Condition.source.verbose_name }}"
-                            data-help="{{ meta.Condition.source.help_text }}"
-                            data-model="service.values.source"
-                            data-errors="service.errors.source"
-                            data-options="service.attributes"
-                            data-options-label="uri"
-                            data-options-null="1"
-                            data-quicksearch="true">
-                        </formgroup>
-                        <formgroup
-                            data-id="condition_relation"
-                            data-type="select"
-                            data-label="{{ meta.Condition.relation.verbose_name }}"
-                            data-help="{{ meta.Condition.relation.help_text }}"
-                            data-model="service.values.relation"
-                            data-errors="service.errors.relation"
-                            data-options="service.relations"
-                            data-options-label="text">
-                        </formgroup>
-                        <formgroup
-                            data-id="condition_target_text"
-                            data-type="text"
-                            data-label="{{ meta.Condition.target_text.verbose_name }}"
-                            data-help="{{ meta.Condition.target_text.help_text }}"
-                            data-model="service.values.target_text"
-                            data-errors="service.errors.target_text">
-                        </formgroup>
-                        <formgroup
-                            data-id="condition_target_option"
-                            data-type="selectnumber"
-                            data-label="{{ meta.Condition.target_option.verbose_name }}"
-                            data-help="{{ meta.Condition.target_option.help_text }}"
-                            data-model="service.values.target_option"
-                            data-errors="service.errors.target_option"
-                            data-options="service.options"
-                            data-options-label="label"
-                            data-options-null="1"
-                            data-quicksearch="true">
-                        </formgroup>
-                        {% if settings.MULTISITE %}
-                        <div role="tabpanel" class="tab-pane" id="condition-editors">
-                            <formgroup
-                                data-id="condition_editors"
-                                data-type="selectmultiple"
-                                data-label="{% trans 'Editors' %}"
-                                data-model="service.values.editors"
-                                data-errors="service.errors.editors"
-                                data-options="service.sites"
-                                data-options-label="name"
-                                data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                            </formgroup>
-                        </div>
-                        {% endif %}
-                    </div>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitFormModal('conditions')">
-                        {% trans 'Save' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/conditions/templates/conditions/conditions_modal_show_conditions.html b/rdmo/conditions/templates/conditions/conditions_modal_show_conditions.html
deleted file mode 100644
index bd7d7f2bcc..0000000000
--- a/rdmo/conditions/templates/conditions/conditions_modal_show_conditions.html
+++ /dev/null
@@ -1,66 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="conditions-show-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">{% trans 'Condition references' %}</h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% trans 'This condition is used in the following elements:' %}
-                    </p>
-
-                    <h6>{% trans 'Option sets' %}</h6>
-                    <ul>
-                        <li ng-repeat="optionset in service.values.optionsets">
-                            <code>{$ optionset.uri $}</code>
-                        </li>
-                        <li ng-hide="service.values.optionsets.length">
-                            {% trans 'none' %}
-                        </li>
-                    </ul>
-
-                    <h6>{% trans 'Question sets' %}</h6>
-                    <ul>
-                        <li ng-repeat="questionset in service.values.questionsets">
-                            <code class="text-primary">{$ questionset.uri $}</code>
-                        </li>
-                        <li ng-hide="service.values.questionsets.length">
-                            {% trans 'none' %}
-                        </li>
-                    </ul>
-
-                    <h6>{% trans 'Questions' %}</h6>
-                    <ul>
-                        <li ng-repeat="question in service.values.questions">
-                            <code class="text-primary">{$ question.uri $}</code>
-                        </li>
-                        <li ng-hide="service.values.questions.length">
-                            {% trans 'none' %}
-                        </li>
-                    </ul>
-
-                    <h6>{% trans 'Tasks' %}</h6>
-                    <ul>
-                        <li ng-repeat="task in service.values.tasks">
-                            <code>{$ task.uri $}</code>
-                        </li>
-                        <li ng-hide="service.values.tasks.length">
-                            {% trans 'none' %}
-                        </li>
-                    </ul>
-                </div>
-
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/conditions/urls/__init__.py b/rdmo/conditions/urls/__init__.py
index c6f9a6fac4..e69de29bb2 100644
--- a/rdmo/conditions/urls/__init__.py
+++ b/rdmo/conditions/urls/__init__.py
@@ -1,8 +0,0 @@
-from django.urls import re_path
-
-from ..views import ConditionsExportView, ConditionsView
-
-urlpatterns = [
-    re_path(r'^$', ConditionsView.as_view(), name='conditions'),
-    re_path(r'^export/(?P<format>[a-z]+)/$', ConditionsExportView.as_view(), name='conditions_export'),
-]
diff --git a/rdmo/conditions/validators.py b/rdmo/conditions/validators.py
index 82ecd4e2ac..fad4897761 100644
--- a/rdmo/conditions/validators.py
+++ b/rdmo/conditions/validators.py
@@ -1,5 +1,3 @@
-from django.utils.translation import gettext_lazy as _
-
 from rdmo.core.validators import LockedValidator, UniqueURIValidator
 
 from .models import Condition
@@ -9,13 +7,6 @@ class ConditionUniqueURIValidator(UniqueURIValidator):
 
     model = Condition
 
-    def get_uri(self, data):
-        if not data.get('key'):
-            self.raise_validation_error({'key': _('This field is required.')})
-        else:
-            uri = self.model.build_uri(data.get('uri_prefix'), data.get('key'))
-            return uri
-
 
 class ConditionLockedValidator(LockedValidator):
 
diff --git a/rdmo/conditions/views.py b/rdmo/conditions/views.py
deleted file mode 100644
index 3e390baae4..0000000000
--- a/rdmo/conditions/views.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import logging
-
-from django.conf import settings
-from django.utils.translation import gettext_lazy as _
-from django.views.generic import ListView, TemplateView
-
-from rdmo.core.exports import XMLResponse
-from rdmo.core.utils import get_model_field_meta, render_to_format
-from rdmo.core.views import CSRFViewMixin, ModelPermissionMixin
-
-from .models import Condition
-from .renderers import ConditionRenderer
-from .serializers.export import ConditionExportSerializer
-
-log = logging.getLogger(__name__)
-
-
-class ConditionsView(ModelPermissionMixin, CSRFViewMixin, TemplateView):
-    template_name = 'conditions/conditions.html'
-    permission_required = 'conditions.view_condition'
-
-    def get_context_data(self, **kwargs):
-        context = super(ConditionsView, self).get_context_data(**kwargs)
-        context['export_formats'] = settings.EXPORT_FORMATS
-        context['meta'] = {
-            'Condition': get_model_field_meta(Condition)
-        }
-        return context
-
-
-class ConditionsExportView(ModelPermissionMixin, ListView):
-    model = Condition
-    context_object_name = 'conditions'
-    permission_required = 'conditions.view_condition'
-
-    def render_to_response(self, context, **response_kwargs):
-        format = self.kwargs.get('format')
-        if format == 'xml':
-            serializer = ConditionExportSerializer(context['conditions'], many=True)
-            xml = ConditionRenderer().render(serializer.data)
-            return XMLResponse(xml, name='conditions')
-        else:
-            return render_to_format(self.request, format, _('Conditions'), 'conditions/conditions_export.html', context)
diff --git a/rdmo/conditions/viewsets.py b/rdmo/conditions/viewsets.py
index 6334c30c6d..fd561ea209 100644
--- a/rdmo/conditions/viewsets.py
+++ b/rdmo/conditions/viewsets.py
@@ -9,17 +9,16 @@
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
 from rdmo.core.utils import is_truthy, render_to_format
 from rdmo.core.views import ChoicesViewSet
-from rdmo.core.viewsets import CopyModelMixin
 
 from .models import Condition
 from .renderers import ConditionRenderer
 from .serializers.export import ConditionExportSerializer
-from .serializers.v1 import (ConditionIndexSerializer, ConditionListSerializer,
-                             ConditionSerializer)
+from .serializers.v1 import ConditionIndexSerializer, ConditionSerializer
 
 
-class ConditionViewSet(CopyModelMixin, ModelViewSet):
+class ConditionViewSet(ModelViewSet):
     permission_classes = (HasModelPermission | HasObjectPermission, )
+    serializer_class = ConditionSerializer
     queryset = Condition.objects.select_related('source', 'target_option') \
                                 .prefetch_related('optionsets', 'pages', 'questionsets',
                                                   'questions', 'tasks', 'editors')
@@ -29,16 +28,13 @@ class ConditionViewSet(CopyModelMixin, ModelViewSet):
     filterset_fields = (
         'uri',
         'uri_prefix',
-        'key',
+        'uri_path',
         'source',
         'relation',
         'target_text',
         'target_option'
     )
 
-    def get_serializer_class(self):
-        return ConditionListSerializer if self.action == 'list' else ConditionSerializer
-
     @action(detail=False)
     def index(self, request):
         queryset = self.filter_queryset(self.get_queryset())
@@ -62,9 +58,9 @@ def detail_export(self, request, pk=None, export_format='xml'):
         if export_format == 'xml':
             serializer = ConditionExportSerializer(self.get_object())
             xml = ConditionRenderer().render([serializer.data], context=self.get_export_renderer_context(request))
-            return XMLResponse(xml, name=self.get_object().key)
+            return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().key, 'conditions/export/conditions.html', {
+            return render_to_format(self.request, export_format, self.get_object().uri_path, 'conditions/export/conditions.html', {
                 'conditions': [self.get_object()]
             })
 
diff --git a/rdmo/core/imports.py b/rdmo/core/imports.py
index 5e1b525e49..4cebb92fc7 100644
--- a/rdmo/core/imports.py
+++ b/rdmo/core/imports.py
@@ -55,146 +55,106 @@ def set_lang_field(instance, field_name, element):
             setattr(instance, '%s_%s' % (field_name, lang_field), '')
 
 
-def set_foreign_field(instance, field_name, element):
-    if field_name in element:
-        foreign_element = element[field_name]
+def set_foreign_field(instance, field_name, element) -> None:
+    if field_name not in element:
+        return
 
-        if foreign_element:
-            foreign_uri = foreign_element.get('uri')
+    foreign_element = element[field_name]
 
-            model_info = model_meta.get_field_info(instance)
-            foreign_model = model_info.forward_relations[field_name].related_model
+    if not foreign_element:
+        setattr(instance, field_name, None)
+        return
 
-            try:
-                foreign_instance = foreign_model.objects.get(uri=foreign_uri)
-                setattr(instance, field_name, foreign_instance)
-            except foreign_model.DoesNotExist:
-                message = '{foreign_model} {foreign_uri} for {instance_model} {instance_uri} does not exist.'.format(
-                    foreign_model=foreign_model._meta.object_name,
-                    foreign_uri=foreign_uri,
-                    instance_model=instance._meta.object_name,
-                    instance_uri=element.get('uri')
-                )
-                logger.info(message)
-                element['warnings'][foreign_uri].append(message)
-        else:
-            setattr(instance, field_name, None)
+    foreign_uri = foreign_element.get('uri')
+
+    model_info = model_meta.get_field_info(instance)
+    foreign_model = model_info.forward_relations[field_name].related_model
+
+    try:
+        foreign_instance = foreign_model.objects.get(uri=foreign_uri)
+        setattr(instance, field_name, foreign_instance)
+    except foreign_model.DoesNotExist:
+        message = '{foreign_model} {foreign_uri} for {instance_model} {instance_uri} does not exist.'.format(
+            foreign_model=foreign_model._meta.object_name,
+            foreign_uri=foreign_uri,
+            instance_model=instance._meta.object_name,
+            instance_uri=element.get('uri')
+        )
+        logger.info(message)
+        element['warnings'][foreign_uri].append(message)
 
 
 def set_m2m_instances(instance, field_name, element):
-    if field_name in element:
-        foreign_elements = element.get(field_name, [])
-
-        if foreign_elements:
-            foreign_instances = []
-
-            model_info = model_meta.get_field_info(instance)
-            foreign_model = model_info.forward_relations[field_name].related_model
-
-            for foreign_element in foreign_elements:
-                foreign_uri = foreign_element.get('uri')
-
-                try:
-                    foreign_instance = foreign_model.objects.get(uri=foreign_uri)
-                    foreign_instances.append(foreign_instance)
-                except foreign_model.DoesNotExist:
-                    message = '{foreign_model} {foreign_uri} for {instance_model} {instance_uri} does not exist.'.format(
-                        foreign_model=foreign_model._meta.object_name,
-                        foreign_uri=foreign_uri,
-                        instance_model=instance._meta.object_name,
-                        instance_uri=element.get('uri')
-                    )
-                    logger.info(message)
-                    element['warnings'][foreign_uri].append(message)
-
-            getattr(instance, field_name).set(foreign_instances)
-        else:
-            getattr(instance, field_name).clear()
-
-
-def set_m2m_through_instances(instance, field_name, element, source_name, target_name, through_name):
-    if field_name in element:
-        target_elements = element.get(field_name)
-
-        model_info = model_meta.get_field_info(instance)
-        through_model = model_info.reverse_relations[through_name].related_model
-        target_model = model_info.forward_relations[field_name].related_model
-        through_instances = list(getattr(instance, through_name).all())
-
-        if target_elements:
-            for target_element in target_elements:
-                target_uri = target_element.get('uri')
-                order = target_element.get('order')
-
-                try:
-                    target_instance = target_model.objects.get(uri=target_uri)
-
-                    try:
-                        # look for the item in items
-                        through_instance = next(filter(lambda item: getattr(item, target_name).uri == target_instance.uri,
-                                                       through_instances))
-
-                        # update order if the item if it changed
-                        if through_instance.order != order:
-                            through_instance.order = order
-                            through_instance.save()
-
-                        # remove the through_instance from the through_instances list so that it won't get removed
-                        through_instances.remove(through_instance)
-                    except StopIteration:
-                        # create a new item
-                        through_model(**{
-                            source_name: instance,
-                            target_name: target_instance,
-                            'order': order
-                        }).save()
-
-                except target_model.DoesNotExist:
-                    message = '{target_model} {target_uri} for imported {instance_model} {instance_uri} does not exist.'.format(
-                        target_model=target_model._meta.object_name,
-                        target_uri=target_uri,
-                        instance_model=instance._meta.object_name,
-                        instance_uri=element.get('uri')
-                    )
-                    logger.info(message)
-                    element['warnings'][target_uri].append(message)
-
-        # remove the remainders of the items list
-        for through_instance in through_instances:
-            if getattr(through_instance, target_name).uri_prefix == instance.uri_prefix:
-                through_instance.delete()
-
-
-def set_reverse_m2m_through_instance(instance, field_name, element, source_name, target_name, through_name):
-    if field_name in element:
-        target_element = element.get(field_name)
-
-        model_info = model_meta.get_field_info(instance)
-        through_model = model_info.reverse_relations[through_name].related_model
-        through_info = model_meta.get_field_info(through_model)
-        target_model = through_info.forward_relations[target_name].related_model
+    if field_name not in element:
+        return
+
+    foreign_elements = element.get(field_name, [])
+
+    if not foreign_elements:
+        getattr(instance, field_name).clear()
+        return
+
+    foreign_instances = []
+
+    model_info = model_meta.get_field_info(instance)
+    foreign_model = model_info.forward_relations[field_name].related_model
+
+    for foreign_element in foreign_elements:
+        foreign_uri = foreign_element.get('uri')
+
+        try:
+            foreign_instance = foreign_model.objects.get(uri=foreign_uri)
+            foreign_instances.append(foreign_instance)
+        except foreign_model.DoesNotExist:
+            message = '{foreign_model} {foreign_uri} for {instance_model} {instance_uri} does not exist.'.format(
+                foreign_model=foreign_model._meta.object_name,
+                foreign_uri=foreign_uri,
+                instance_model=instance._meta.object_name,
+                instance_uri=element.get('uri')
+            )
+            logger.info(message)
+            element['warnings'][foreign_uri].append(message)
+
+    getattr(instance, field_name).set(foreign_instances)
+
+
+def set_m2m_through_instances(instance, field_name, element, source_name, target_name, through_name) -> None:
+    if field_name not in element:
+        return
+
+    target_elements = element.get(field_name) or []
+
+    model_info = model_meta.get_field_info(instance)
+    through_model = model_info.reverse_relations[through_name].related_model
+    target_model = model_info.forward_relations[field_name].related_model
+    through_instances = list(getattr(instance, through_name).all())
 
+    for target_element in target_elements:
         target_uri = target_element.get('uri')
         order = target_element.get('order')
 
         try:
             target_instance = target_model.objects.get(uri=target_uri)
-            if target_instance.is_locked:
-                message = '{target_model} {target_uri} for imported {instance_model} {instance_uri} is locked.'.format(
-                    target_model=target_model._meta.object_name,
-                    target_uri=target_uri,
-                    instance_model=instance._meta.object_name,
-                    instance_uri=element.get('uri')
-                )
-                logger.info(message)
-                element['errors'].append(message)
-            else:
-                through_instance, created = through_model.objects.get_or_create(**{
+
+            try:
+                # look for the item in items
+                through_instance = next(filter(lambda item: getattr(item, target_name).uri == target_instance.uri,
+                                                through_instances))
+
+                # update order if the item if it changed
+                if through_instance.order != order:
+                    through_instance.order = order
+                    through_instance.save()
+
+                # remove the through_instance from the through_instances list so that it won't get removed
+                through_instances.remove(through_instance)
+            except StopIteration:
+                # create a new item
+                through_model(**{
                     source_name: instance,
-                    target_name: target_instance
-                })
-                through_instance.order = order
-                through_instance.save()
+                    target_name: target_instance,
+                    'order': order
+                }).save()
 
         except target_model.DoesNotExist:
             message = '{target_model} {target_uri} for imported {instance_model} {instance_uri} does not exist.'.format(
@@ -206,6 +166,55 @@ def set_reverse_m2m_through_instance(instance, field_name, element, source_name,
             logger.info(message)
             element['warnings'][target_uri].append(message)
 
+    # remove the remainders of the items list
+    for through_instance in through_instances:
+        if getattr(through_instance, target_name).uri_prefix == instance.uri_prefix:
+            through_instance.delete()
+
+
+def set_reverse_m2m_through_instance(instance, field_name, element, source_name, target_name, through_name) -> None:
+    if field_name not in element:
+        return
+
+    target_element = element.get(field_name)
+
+    model_info = model_meta.get_field_info(instance)
+    through_model = model_info.reverse_relations[through_name].related_model
+    through_info = model_meta.get_field_info(through_model)
+    target_model = through_info.forward_relations[target_name].related_model
+
+    target_uri = target_element.get('uri')
+    order = target_element.get('order')
+
+    try:
+        target_instance = target_model.objects.get(uri=target_uri)
+        if target_instance.is_locked:
+            message = '{target_model} {target_uri} for imported {instance_model} {instance_uri} is locked.'.format(
+                target_model=target_model._meta.object_name,
+                target_uri=target_uri,
+                instance_model=instance._meta.object_name,
+                instance_uri=element.get('uri')
+            )
+            logger.info(message)
+            element['errors'].append(message)
+        else:
+            through_instance, created = through_model.objects.get_or_create(**{
+                source_name: instance,
+                target_name: target_instance
+            })
+            through_instance.order = order
+            through_instance.save()
+
+    except target_model.DoesNotExist:
+        message = '{target_model} {target_uri} for imported {instance_model} {instance_uri} does not exist.'.format(
+            target_model=target_model._meta.object_name,
+            target_uri=target_uri,
+            instance_model=instance._meta.object_name,
+            instance_uri=element.get('uri')
+        )
+        logger.info(message)
+        element['warnings'][target_uri].append(message)
+
 
 def validate_instance(instance, element, *validators):
     exception_message = None
@@ -221,11 +230,33 @@ def validate_instance(instance, element, *validators):
     except ObjectDoesNotExist as e:
         exception_message = e
     finally:
-        if exception_message is not None:
-            message = '{instance_model} {instance_uri} cannot be imported ({exception}).'.format(
-                instance_model=instance._meta.object_name,
-                instance_uri=element.get('uri'),
-                exception=exception_message
-            )
-            logger.info(message)
-            element['errors'].append(message)
+        if exception_message is None:
+            return
+        message = '{instance_model} {instance_uri} cannot be imported ({exception}).'.format(
+            instance_model=instance._meta.object_name,
+            instance_uri=element.get('uri'),
+            exception=exception_message
+        )
+        logger.info(message)
+        element['errors'].append(message)
+
+
+def check_permissions(instance, element, user):
+    if user is None:
+        return
+
+    app_label = instance._meta.app_label
+    model_name = instance._meta.model_name
+
+    if instance.id:
+        perms = [f'{app_label}.change_{model_name}_object']
+    else:
+        perms = [f'{app_label}.add_{model_name}_object']
+
+    if not user.has_perms(perms, instance):
+        message = 'You have no permissions to import {instance_model} {instance_uri}.'.format(
+            instance_model=instance._meta.object_name,
+            instance_uri=element.get('uri')
+        )
+        logger.info(message)
+        element['errors'].append(message)
diff --git a/rdmo/core/serializers.py b/rdmo/core/serializers.py
index d56859ff55..c1f5e6a28a 100644
--- a/rdmo/core/serializers.py
+++ b/rdmo/core/serializers.py
@@ -11,6 +11,7 @@
 
 logger = logging.getLogger(__name__)
 
+
 class RecursiveField(serializers.Serializer):
 
     def to_representation(self, value):
@@ -49,17 +50,19 @@ def __init__(self, *args, **kwargs):
         super(TranslationSerializerMixin, self).__init__(*args, **kwargs)
 
         meta = getattr(self, 'Meta', None)
-        if meta:
-            for lang_code, lang_string, lang_field in get_languages():
-                for field in meta.trans_fields:
-                    field_name = '%s_%s' % (field, lang_field)
-                    model_field = meta.model._meta.get_field(field_name)
+        if meta is None:
+            return
+
+        for lang_code, lang_string, lang_field in get_languages():
+            for field in meta.trans_fields:
+                field_name = '%s_%s' % (field, lang_field)
+                model_field = meta.model._meta.get_field(field_name)
 
-                    self.fields['%s_%s' % (field, lang_code)] = serializers.CharField(
-                        source=field_name,
-                        required=not model_field.blank,
-                        allow_null=model_field.null,
-                        allow_blank=model_field.blank)
+                self.fields['%s_%s' % (field, lang_code)] = serializers.CharField(
+                    source=field_name,
+                    required=not model_field.blank,
+                    allow_null=model_field.null,
+                    allow_blank=model_field.blank)
 
 
 class ThroughModelSerializerMixin(object):
@@ -102,32 +105,34 @@ def set_through_fields(self, instance, through_fields):
 
         for field_name, source_name, target_name, through_name in self.Meta.through_fields:
             through_model, validated_data = through_fields[field_name]
-            if validated_data is not None:
-                items = list(getattr(instance, through_name).all())
-
-                for data in validated_data:
-                    try:
-                        # look for the item in items
-                        item = next(filter(lambda item: getattr(item, target_name) ==
-                                           data.get(target_name), items))
-                        # update order if the item if it changed
-                        if item.order != data.get('order'):
-                            item.order = data.get('order')
-                            item.save()
-
-                        # remove the item from the items list so that it won't get removed
-                        items.remove(item)
-                    except StopIteration:
-                        # create a new item
-                        new_data = dict({
-                            source_name: instance
-                        }, **data)
-                        new_item = through_model(**new_data)
-                        new_item.save()
-
-                # remove the remainders of the items list
-                for item in items:
-                    item.delete()
+            if validated_data is None:
+                continue
+
+            items = list(getattr(instance, through_name).all())
+
+            for data in validated_data:
+                try:
+                    # look for the item in items
+                    item = next(filter(lambda item: getattr(item, target_name) ==
+                                        data.get(target_name), items))
+                    # update order if the item if it changed
+                    if item.order != data.get('order'):
+                        item.order = data.get('order')
+                        item.save()
+
+                    # remove the item from the items list so that it won't get removed
+                    items.remove(item)
+                except StopIteration:
+                    # create a new item
+                    new_data = dict({
+                        source_name: instance
+                    }, **data)
+                    new_item = through_model(**new_data)
+                    new_item.save()
+
+            # remove the remainders of the items list
+            for item in items:
+                item.delete()
 
         return instance
 
@@ -159,14 +164,16 @@ def set_parent_fields(self, instance, parent_fields):
         for field_name, source_name, target_name, through_name in self.Meta.parent_fields:
             through_model, validated_data = parent_fields[field_name]
 
-            if validated_data is not None:
-                for parent in validated_data:
-                    order = (getattr(parent, through_name).aggregate(order=Max('order')).get('order') or 0) + 1
-                    through_model(**{
-                        source_name: parent,
-                        target_name: instance,
-                        'order': order
-                    }).save()
+            if validated_data is None:
+                continue
+
+            for parent in validated_data:
+                order = (getattr(parent, through_name).aggregate(order=Max('order')).get('order') or 0) + 1
+                through_model(**{
+                    source_name: parent,
+                    target_name: instance,
+                    'order': order
+                }).save()
 
         return instance
 
@@ -178,25 +185,17 @@ def get_model(self, obj):
         return str(self.Meta.model._meta)
 
 
-class ElementExportSerializerMixin(serializers.ModelSerializer):
-
-    def get_xml_url(self, obj):
-        app_name = self.context['request'].version
-        basename = self.Meta.model._meta.model_name
-        return reverse(f'{app_name}:{basename}-detail-export', args=[obj.pk])
-
-
 class ElementWarningSerializerMixin(serializers.ModelSerializer):
 
     def get_warning(self, obj):
         return any([get_language_warning(obj, field_name) for field_name in self.Meta.warning_fields])
 
 
-class ReadOnlyObjectPermissionsSerializerMixin:
+class ReadOnlyObjectPermissionSerializerMixin:
     '''
     A mixin class for Serializers that adds a boolean field with the name read_only.
     It checks the object permissions based on the model of the serializer.
-    
+
     Requires:
         - the request object in the context kwargs of the Serializer call:
             ..., context={'request': request}
@@ -207,9 +206,9 @@ class ReadOnlyObjectPermissionsSerializerMixin:
     and the field to fields:
         read_only
     '''
-    
+
     OBJECT_PERMISSION_ACTION_NAMES = ('change', 'delete')
-    
+
     @staticmethod
     def construct_object_permission(model, action_name: str) -> str:
         model_app_label = model._meta.app_label
@@ -220,20 +219,26 @@ def construct_object_permission(model, action_name: str) -> str:
     def get_read_only(self, obj) -> bool:
         user = self.context['request'].user
         perms = (self.construct_object_permission(self.Meta.model, action_name)
-                    for action_name in self.OBJECT_PERMISSION_ACTION_NAMES)
+                 for action_name in self.OBJECT_PERMISSION_ACTION_NAMES)
         return not all(user.has_perm(perm, obj) for perm in perms)
 
 
 class SiteSerializer(serializers.ModelSerializer):
 
+    current = serializers.SerializerMethodField()
+
     class Meta:
         model = Site
         fields = (
             'id',
             'domain',
-            'name'
+            'name',
+            'current'
         )
 
+    def get_current(self, obj):
+        return obj == Site.objects.get_current()
+
 
 class GroupSerializer(serializers.ModelSerializer):
 
diff --git a/rdmo/core/templates/core/base_navigation.html b/rdmo/core/templates/core/base_navigation.html
index 48fafd5498..d158240e1a 100644
--- a/rdmo/core/templates/core/base_navigation.html
+++ b/rdmo/core/templates/core/base_navigation.html
@@ -19,9 +19,9 @@
             <ul class="nav navbar-nav">
                 {% back_to_project_link %}
 
-                {% test_rule 'can_view_management' request.user as can_view_management %}
+                {% test_rule 'management.can_view_management' request.user request.site as can_view_management %}
 
-                {% if user.is_superuser or can_view_management %}
+                {% if can_view_management %}
                 <li>
                     <a href="{% url 'management' %}">{% trans 'Management' %}</a>
                 </li>
diff --git a/rdmo/core/urls/__init__.py b/rdmo/core/urls/__init__.py
index e22d1b9aa0..e575385582 100644
--- a/rdmo/core/urls/__init__.py
+++ b/rdmo/core/urls/__init__.py
@@ -5,16 +5,10 @@
 
 urlpatterns = [
     path('account/', include('rdmo.accounts.urls')),
-    path('conditions/', include('rdmo.conditions.urls')),
-    path('domain/', include('rdmo.domain.urls')),
     path('management/', include('rdmo.management.urls')),
-    path('options/', include('rdmo.options.urls')),
     path('overlays/', include('rdmo.overlays.urls')),
     path('projects/', include('rdmo.projects.urls')),
-    path('questions/', include('rdmo.questions.urls')),
     path('services/', include('rdmo.services.urls')),
-    path('tasks/', include('rdmo.tasks.urls')),
-    path('views/', include('rdmo.views.urls')),
 
     path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
     re_path(r'^i18n/([a-z]{2})/$', i18n_switcher, name='i18n_switcher'),
diff --git a/rdmo/core/viewsets.py b/rdmo/core/viewsets.py
index 487d4fe370..1b6af7cbbe 100644
--- a/rdmo/core/viewsets.py
+++ b/rdmo/core/viewsets.py
@@ -31,38 +31,3 @@ class GroupViewSet(viewsets.ReadOnlyModelViewSet):
     permission_classes = (HasModelPermission, )
     queryset = Group.objects.all()
     serializer_class = GroupSerializer
-
-
-class CopyModelMixin:
-
-    @action(detail=True, methods=['PUT'])
-    def copy(self, request, pk=None):
-        # get the instance to be copied
-        instance = self.get_object()
-
-        # get the copy relevant data from the request
-        uri_prefix = request.data.get('uri_prefix')
-        uri_path = request.data.get('uri_path')
-        key = request.data.get('key')
-
-        # get the original and the original_serializer
-        original = self.get_object()
-        original_serializer = self.get_serializer(original)
-
-        # merge the original_serializer with the data from the request and validate
-        data = original_serializer.data
-        data.update({
-            'uri_prefix': uri_prefix,
-            'uri_path': uri_path,
-            'key': key
-        })
-        validation_serializer = self.get_serializer(data=data)
-        validation_serializer.is_valid(raise_exception=True)
-
-        # perform the copy on the database
-        instance.copy(uri_prefix, uri_path or key)
-
-        # the rest is similar to CreateModelMixin.create()
-        serializer = self.get_serializer(instance)
-        headers = self.get_success_headers(serializer.data)
-        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
diff --git a/rdmo/core/xml.py b/rdmo/core/xml.py
index 14b3cca93d..a4f67f4963 100644
--- a/rdmo/core/xml.py
+++ b/rdmo/core/xml.py
@@ -138,7 +138,10 @@ def convert_legacy_elements(elements):
     # second pass: del key, set uri_path, add order to reverse m2m through models
     # and sort questions into pages or questionsets
     for uri, element in elements.items():
-        if element['model'] == 'questions.catalog':
+        if element['model'] == 'conditions.condition':
+            element['uri_path'] = element.pop('key')
+
+        elif element['model'] == 'questions.catalog':
             element['uri_path'] = element.pop('key')
 
         elif element['model'] == 'questions.section':
@@ -199,6 +202,12 @@ def convert_legacy_elements(elements):
             if element.get('optionset') is not None:
                 element['optionset']['order'] = element.pop('order')
 
+        if element['model'] == 'tasks.task':
+            element['uri_path'] = element.pop('key')
+
+        if element['model'] == 'views.view':
+            element['uri_path'] = element.pop('key')
+
     return elements
 
 
@@ -210,20 +219,22 @@ def order_elements(elements):
 
 
 def append_element(ordered_elements, unordered_elements, uri, element):
-    if element is not None:
-        for element_value in element.values():
-            if isinstance(element_value, dict):
-                sub_uri = element_value.get('uri')
+    if element is None:
+        return
+
+    for element_value in element.values():
+        if isinstance(element_value, dict):
+            sub_uri = element_value.get('uri')
+            sub_element = unordered_elements.get(sub_uri)
+            if sub_uri not in ordered_elements:
+                append_element(ordered_elements, unordered_elements, sub_uri, sub_element)
+
+        elif isinstance(element_value, list):
+            for value in element_value:
+                sub_uri = value.get('uri')
                 sub_element = unordered_elements.get(sub_uri)
                 if sub_uri not in ordered_elements:
                     append_element(ordered_elements, unordered_elements, sub_uri, sub_element)
 
-            elif isinstance(element_value, list):
-                for value in element_value:
-                    sub_uri = value.get('uri')
-                    sub_element = unordered_elements.get(sub_uri)
-                    if sub_uri not in ordered_elements:
-                        append_element(ordered_elements, unordered_elements, sub_uri, sub_element)
-
-        if uri not in ordered_elements:
-            ordered_elements[uri] = element
+    if uri not in ordered_elements:
+        ordered_elements[uri] = element
diff --git a/rdmo/domain/imports.py b/rdmo/domain/imports.py
index 83fff81663..6cd660b116 100644
--- a/rdmo/domain/imports.py
+++ b/rdmo/domain/imports.py
@@ -2,8 +2,8 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (set_common_fields, set_foreign_field,
-                               validate_instance)
+from rdmo.core.imports import (check_permissions, set_common_fields,
+                               set_foreign_field, validate_instance)
 
 from .models import Attribute
 from .validators import (AttributeLockedValidator, AttributeParentValidator,
@@ -12,7 +12,7 @@
 logger = logging.getLogger(__name__)
 
 
-def import_attribute(element, save=False):
+def import_attribute(element, save=False, user=None):
     try:
         attribute = Attribute.objects.get(uri=element.get('uri'))
     except Attribute.DoesNotExist:
@@ -27,6 +27,8 @@ def import_attribute(element, save=False):
     validate_instance(attribute, element, AttributeLockedValidator,
                       AttributeParentValidator, AttributeUniqueURIValidator)
 
+    check_permissions(attribute, element, user)
+
     if save and not element.get('errors'):
         if attribute.id:
             element['updated'] = True
diff --git a/rdmo/domain/models.py b/rdmo/domain/models.py
index 9b08b2ce5e..fd377d4887 100644
--- a/rdmo/domain/models.py
+++ b/rdmo/domain/models.py
@@ -68,22 +68,6 @@ def save(self, *args, **kwargs):
         for child in self.children.all():
             child.save()
 
-    def copy(self, uri_prefix, key, parent=None, rebuild=True):
-        assert parent not in self.get_descendants(include_self=True)
-
-        # copy the attribute
-        attribute = copy_model(self, uri_prefix=uri_prefix, key=key, parent=parent or self.parent)
-
-        # recursively copy children
-        for child in self.children.all():
-            child.copy(uri_prefix, child.key, parent=attribute, rebuild=False)
-
-        # rebuild the mptt tree, but only for the initially copied attribute
-        if rebuild:
-            Attribute.objects.rebuild()
-
-        return attribute
-
     @property
     def is_locked(self):
         return self.get_ancestors(include_self=True).filter(locked=True).exists()
diff --git a/rdmo/domain/serializers/v1.py b/rdmo/domain/serializers/v1.py
index cc1769a12e..4e2b5f3e3a 100644
--- a/rdmo/domain/serializers/v1.py
+++ b/rdmo/domain/serializers/v1.py
@@ -3,9 +3,8 @@
 from rest_framework import serializers
 
 from rdmo.conditions.models import Condition
-from rdmo.core.serializers import (ElementExportSerializerMixin,
-                                   ElementModelSerializerMixin,
-                                   ReadOnlyObjectPermissionsSerializerMixin)
+from rdmo.core.serializers import (ElementModelSerializerMixin,
+                                   ReadOnlyObjectPermissionSerializerMixin)
 from rdmo.questions.models import Page, Question, QuestionSet
 
 from ..models import Attribute
@@ -15,7 +14,7 @@
 log = logging.getLogger(__name__)
 
 
-class BaseAttributeSerializer(ElementModelSerializerMixin, ReadOnlyObjectPermissionsSerializerMixin,
+class BaseAttributeSerializer(ElementModelSerializerMixin, ReadOnlyObjectPermissionSerializerMixin,
                               serializers.ModelSerializer):
 
     model = serializers.SerializerMethodField()
@@ -76,14 +75,11 @@ def get_attributes(self, obj):
         return [attribute.id for attribute in obj.get_descendants()]
 
 
-class AttributeListSerializer(ElementExportSerializerMixin, BaseAttributeSerializer):
+class AttributeListSerializer(BaseAttributeSerializer):
 
-    xml_url = serializers.SerializerMethodField()
-    
     class Meta(BaseAttributeSerializer.Meta):
         fields = BaseAttributeSerializer.Meta.fields + (
-            'xml_url',
-            'is_leaf_node'
+            'is_leaf_node',
         )
 
 
diff --git a/rdmo/domain/static/domain/css/domain.scss b/rdmo/domain/static/domain/css/domain.scss
deleted file mode 100644
index 94a8a57e23..0000000000
--- a/rdmo/domain/static/domain/css/domain.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.nested-attribute {
-    margin-left: 40px;
-}
diff --git a/rdmo/domain/static/domain/js/domain.js b/rdmo/domain/static/domain/js/domain.js
deleted file mode 100644
index a3af6a9954..0000000000
--- a/rdmo/domain/static/domain/js/domain.js
+++ /dev/null
@@ -1,133 +0,0 @@
-angular.module('domain', ['core'])
-
-.factory('DomainService', ['$resource', '$timeout', '$window', '$q', 'utils', function($resource, $timeout, $window, $q, utils) {
-
-    /* get the base url */
-
-    var baseurl = angular.element('meta[name="baseurl"]').attr('content');
-
-    /* configure resources */
-
-    var resources = {
-        attributes: $resource(baseurl + 'api/v1/domain/attributes/:list_action/:id/:detail_action/'),
-        settings: $resource(baseurl + 'api/v1/core/settings/'),
-        sites: $resource(baseurl + 'api/v1/core/sites/'),
-    };
-
-    /* configure factories */
-
-    var factories = {
-        attributes: function(parent) {
-            return {
-                parent: (angular.isDefined(parent) && parent) ? parent.id : null,
-                uri_prefix: (angular.isDefined(parent) && parent) ? parent.uri_prefix : service.settings.default_uri_prefix
-            };
-        }
-    };
-
-    /* create the domain service */
-
-    var service = {};
-
-    service.init = function(options) {
-        service.settings = resources.settings.get();
-        service.sites = resources.sites.query();
-        service.uri_prefixes = []
-        service.uri_prefix = ''
-        service.filter = sessionStorage.getItem('domain_filter') || '';
-
-        service.initView().then(function () {
-            var current_scroll_pos = sessionStorage.getItem('domain_scroll_pos');
-            if (current_scroll_pos) {
-                $timeout(function() {
-                    $window.scrollTo(0, current_scroll_pos);
-                });
-            }
-        });
-
-        $window.addEventListener('beforeunload', function() {
-            sessionStorage.setItem('domain_scroll_pos', $window.scrollY);
-            sessionStorage.setItem('domain_filter', service.filter);
-        });
-    };
-
-    service.initView = function() {
-        service.domain = resources.attributes.query({list_action: 'nested'})
-        service.attributes = resources.attributes.query({list_action: 'index'}, function(response) {
-            service.uri_prefixes = response.reduce(function(list, item) {
-                if (list.indexOf(item.uri_prefix) < 0) {
-                    list.push(item.uri_prefix)
-                }
-                return list
-            }, [])
-        });
-
-        return $q.all([
-            service.domain.$promise,
-            service.attributes.$promise
-        ]);
-    };
-
-    service.openFormModal = function(resource, obj, create, copy) {
-        service.errors = {};
-        service.values = utils.fetchValues(resources[resource], factories[resource], obj, create, copy);
-
-        $q.when(service.values.$promise).then(function() {
-            $('#' + resource + '-form-modal').modal('show');
-            $timeout(function() {
-                $('formgroup[data-quicksearch="true"]').trigger('refresh');
-            });
-        });
-    };
-
-    service.submitFormModal = function(resource) {
-        utils.storeValues(resources[resource], service.values).then(function() {
-            $('.modal').modal('hide');
-            service.initView();
-        }, function(result) {
-            service.errors = result.data;
-        });
-    };
-
-    service.openDeleteModal = function(resource, obj) {
-        utils.fetchValues(resources[resource], factories[resource], obj).$promise.then(function(response) {
-            service.values = response;
-            service.values.children = obj.children;
-            $('#' + resource + '-delete-modal').modal('show');
-        });
-    };
-
-    service.submitDeleteModal = function(resource) {
-        resources[resource].delete({id: service.values.id}, function() {
-            $('.modal').modal('hide');
-            service.initView();
-        });
-    };
-
-    service.openShowModal = function(resource, obj) {
-        service.values = utils.fetchValues(resources[resource], factories[resource], obj)
-
-        $q.when(service.values.$promise).then(function() {
-            $('#' + resource + '-show-modal').modal('show');
-        });
-    };
-
-    service.hideAttribute = function(item) {
-        if (service.filter && item.uri.indexOf(service.filter) < 0) {
-            return true;
-        }
-        if (service.uri_prefix && item.uri_prefix != service.uri_prefix) {
-            return true;
-        }
-    };
-
-    return service;
-
-}])
-
-.controller('DomainController', ['$scope', 'DomainService', function($scope, DomainService) {
-
-    $scope.service = DomainService;
-    $scope.service.init();
-
-}]);
diff --git a/rdmo/domain/templates/domain/domain.html b/rdmo/domain/templates/domain/domain.html
deleted file mode 100644
index 1182530240..0000000000
--- a/rdmo/domain/templates/domain/domain.html
+++ /dev/null
@@ -1,139 +0,0 @@
-{% extends 'core/page.html' %}
-{% load static %}
-{% load compress %}
-{% load i18n %}
-{% load core_tags %}
-
-{% block head %}
-    {% vendor 'angular' %}
-    {% vendor 'select2' %}
-    {% vendor 'select2-bootstrap-theme' %}
-
-    {% compress css %}
-    <link rel="stylesheet" type="text/x-scss" href="{% static 'domain/css/domain.scss' %}" />
-    {% endcompress %}
-
-    {% compress js %}
-    <script type="text/javascript" src="{% static 'core/js/core.js' %}" ></script>
-    <script type="text/javascript" src="{% static 'domain/js/domain.js' %}" ></script>
-    {% endcompress %}
-{% endblock %}
-
-{% block bodyattr %} ng-app="domain" ng-controller="DomainController" {% endblock %}
-
-{% block sidebar %}
-
-    <h2>{% trans 'Filter attributes' %}</h2>
-
-    <div class="form-group">
-        <div class="input-group">
-            <input type="text" class="form-control" ng-model="service.filter"
-                   placeholder="{% trans 'Search' %}"></input>
-            <span class="input-group-btn" ng-click="service.filter = ''">
-                <button class="btn btn-default">
-                    <span class="fa fa-times"></span>
-                </button>
-            </span>
-        </div>
-    </div>
-
-    <div class="form-group">
-        <select class="form-control" ng-model="service.uri_prefix">
-            <option value="">{% trans 'All URI prefixes' %}</option>
-            <option ng-repeat="uri_prefix in service.uri_prefixes" value="{$ uri_prefix $}">
-                {$ uri_prefix $}
-            </option>
-        </select>
-    </div>
-
-    <h2>{% trans 'Options' %}</h2>
-
-    <ul class="list-unstyled">
-        <li>
-            <a href="" ng-click="service.openFormModal('attributes', null, true)">
-                {% trans 'Create new attribute' %}
-            </a>
-        </li>
-    </ul>
-
-    <h2>{% trans 'Export' %}</h2>
-
-    <ul class="list-unstyled">
-        {% for format, text in export_formats %}
-        <li >
-            <a href="{% url 'domain_export' format %}" target="_blank">
-                {{ text }}
-            </a>
-        </li>
-        {% endfor %}
-    </ul>
-
-    <ul class="list-unstyled">
-        <li >
-            <a href="{% url 'domain_export' 'csvcomma' %}" target="_blank">
-                {% trans 'CSV comma separated' %}
-            </a>
-        </li>
-        <li >
-            <a href="{% url 'domain_export' 'csvsemicolon' %}" target="_blank">
-                {% trans 'CSV semicolon separated' %}
-            </a>
-        </li>
-    </ul>
-    <ul class="list-unstyled">
-        <li>
-            <a href="{% url 'domain_export' 'xml' %}" target="_blank">
-                {% trans 'XML' %}
-            </a>
-        </li>
-    </ul>
-
-    <h2>{% trans 'Import' %}</h2>
-    <ul class="list-unstyled">
-        <li>
-            {% url 'upload' as upload_url %}
-            {% include 'core/upload_form.html' with upload_url=upload_url %}
-        </li>
-    </ul>
-
-{% endblock %}
-
-{% block page %}
-
-    <h1>{% trans 'Domain' %}</h1>
-
-    <div ng-repeat="attribute in service.domain | orderBy: 'path'"
-         ng-include src="'nested_attribute.html'">
-    </div>
-
-    <script type="text/ng-template" id="nested_attribute.html">
-
-    <div class="panel panel-default" ng-hide="service.hideAttribute(attribute)">
-
-        <div class="panel-body">
-            <div class="pull-right">
-                {% include 'domain/domain_options.html' %}
-            </div>
-
-            <strong ng-class="{'text-muted': attribute.read_only}">
-                {% trans 'Attribute' %}
-            </strong>
-
-            <code>{$ attribute.uri $}</code>
-        </div>
-
-    </div>
-
-    <div class="nested-attribute"
-         ng-repeat="attribute in attribute.children | orderBy: 'path'"
-         ng-include src="'nested_attribute.html'">
-    </div>
-
-    </script>
-
-    {% include 'domain/domain_modal_form_attributes.html' %}
-    {% include 'domain/domain_modal_show_attributes.html' %}
-
-    {% include 'domain/domain_modal_delete_attributes.html' %}
-
-{% endblock %}
diff --git a/rdmo/domain/templates/domain/domain_modal_delete_attributes.html b/rdmo/domain/templates/domain/domain_modal_delete_attributes.html
deleted file mode 100644
index f2a63ece1c..0000000000
--- a/rdmo/domain/templates/domain/domain_modal_delete_attributes.html
+++ /dev/null
@@ -1,81 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="attributes-delete-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Delete attribute' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with object='{$ service.values.uri $}' %}
-                        You are about to permanently delete the attribute <code>{{ object }}</code>.
-                        {% endblocktrans %}
-                    </p>
-
-                    <p ng-show="service.values.children.length">
-                        {% trans 'This will also delete the following attributes:' %}
-                    </p>
-
-                    <ul>
-                        <li ng-repeat="attribute in service.values.children" ng-include src="'nested_attributes_delete.html'">
-                    </ul>
-
-                    <script type="text/ng-template" id="nested_attributes_delete.html">
-                    <code>{$ attribute.uri $}</code>
-
-                    <ul>
-                        <li ng-repeat="attribute in attribute.children" ng-include src="'nested_attributes_delete.html'"></li>
-                    </ul>
-                    </script>
-
-                    <p>
-                        {% blocktrans trimmed with values_count='{$ service.values.values_count $}' projects_count='{$ service.values.projects_count $}' %}
-                        This attribute is used for <b>{{ values_count }} values</b> in <b>{{ projects_count }} projects</b>.
-                        {% endblocktrans %}
-                        {% trans 'Furthermore, this attribute is used in the following elements:' %}
-                    </p>
-
-                    <ul>
-                        <li><b>{$ service.values.conditions.length $} {% trans 'conditions' %}</b></li>
-                        <li><b>{$ service.values.questionsets.length $} {% trans 'questionsets' %}</b></li>
-                        <li><b>{$ service.values.questions.length $} {% trans 'questions' %}</b></li>
-                        <li>
-                            <b>
-                                {$ service.values.tasks_as_start.length + service.values.tasks_as_end.length $}
-                                {% trans 'tasks' %}
-                            </b>
-                        </li>
-                    </ul>
-
-                    <p ng-show="service.values.children.length">
-                        <b>{% trans 'This does not include references from the attributes above. Please check carefully.' %}</b>
-                    </p>
-
-                    <p class="text-danger">
-                        {% trans 'This action cannot be undone!' %}
-                    </p>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-danger"
-                            ng-class="{'disabled': attribute.read_only}"
-                            ng-click="service.submitDeleteModal('attributes')">
-                        {% trans 'Delete' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/domain/templates/domain/domain_modal_form_attributes.html b/rdmo/domain/templates/domain/domain_modal_form_attributes.html
deleted file mode 100644
index ee43510e5b..0000000000
--- a/rdmo/domain/templates/domain/domain_modal_form_attributes.html
+++ /dev/null
@@ -1,133 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="attributes-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        <span ng-hide="service.values.id">{% trans 'Create attribute' %}</span>
-                        <span ng-show="service.values.copy">{% trans 'Copy attribute' %}</span>
-                        <span ng-show="service.values.id && !service.values.copy">{% trans 'Update attribute' %}</span>
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <div class="copy-block" ng-show="service.values.copy">
-                        <p>
-                            {% trans 'Please enter the values for the copy of the attribute below and change at least the <strong>key</strong>.' %}
-                        </p>
-                        <p>
-                            {% trans 'Other properties can be changed once the new attribute is created.' %}
-                        </p>
-                    </div>
-                    <div class="row">
-                        <div class="col-sm-6">
-                            <div class="form-group" ng-class="{'has-error': service.errors.uri_prefix }">
-                                <label class="control-label" for="uri_prefix">
-                                    {{ meta.Attribute.uri_prefix.verbose_name }}
-                                </label>
-
-                                <div class="input-group">
-                                    <input class="form-control"
-                                           id="attribute_uri_prefix"
-                                           type="text"
-                                           ng-model="service.values.uri_prefix" />
-
-                                    <span class="input-group-btn">
-                                        <button type="button" class="btn btn-default"
-                                            title="{% trans 'Insert default URI Prefix' %}"
-                                            ng-click="service.values.uri_prefix = service.settings.default_uri_prefix">
-
-                                            <span class="fa fa-magic"></span>
-                                        </button>
-                                    </span>
-                                </div>
-
-                                <p class="help-block">
-                                    {{ meta.Attribute.uri_prefix.help_text }}
-                                </p>
-
-                                <ul class="help-block list-unstyled" ng-show="service.errors.uri_prefix">
-                                    <li ng-repeat="error in service.errors.uri_prefix">
-                                        {$ error $}
-                                    </li>
-                                </ul>
-                            </div>
-                        </div>
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="attribute_key"
-                                data-type="text"
-                                data-label="{{ meta.Attribute.key.verbose_name }}"
-                                data-help="{{ meta.Attribute.key.help_text }}"
-                                data-model="service.values.key"
-                                data-errors="service.errors.key">
-                            </formgroup>
-                        </div>
-                    </div>
-                    <div ng-hide="service.values.copy">
-                        <formgroup
-                            data-id="attribute_comment"
-                            data-type="textarea"
-                            data-label="{{ meta.Attribute.comment.verbose_name }}"
-                            data-help="{{ meta.Attribute.comment.help_text }}"
-                            data-model="service.values.comment"
-                            data-errors="service.errors.comment">
-                        </formgroup>
-                        <formgroup
-                            data-id="attribute_locked"
-                            data-type="checkbox"
-                            data-label="{{ meta.Attribute.locked.verbose_name }}"
-                            data-help="{{ meta.Attribute.locked.help_text }}"
-                            data-model="service.values.locked"
-                            data-errors="service.errors.locked">
-                        </formgroup>
-                    </div>
-                    <div>
-                        <formgroup
-                            data-id="attribute_parent"
-                            data-type="selectnumber"
-                            data-label="{{ meta.Attribute.parent.verbose_name }}"
-                            data-help="{{ meta.Attribute.comment.help_text }}"
-                            data-model="service.values.parent"
-                            data-errors="service.errors.parent"
-                            data-options="service.attributes"
-                            data-options-label="uri"
-                            data-options-null="1"
-                            data-quicksearch="true">
-                        </formgroup>
-                    </div>
-                    {% if settings.MULTISITE %}
-                    <div>
-                        <formgroup
-                            data-id="attribute_editors"
-                            data-type="selectmultiple"
-                            data-label="{% trans 'Editors' %}"
-                            data-model="service.values.editors"
-                            data-errors="service.errors.editors"
-                            data-options="service.sites"
-                            data-options-label="name"
-                            data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                        </formgroup>
-                    </div>
-                    {% endif %}
-                </div>
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary" data-dismiss="alert" 
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitFormModal('attributes')">
-                        {% trans 'Save' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/domain/templates/domain/domain_modal_show_attributes.html b/rdmo/domain/templates/domain/domain_modal_show_attributes.html
deleted file mode 100644
index 6ec8bc769e..0000000000
--- a/rdmo/domain/templates/domain/domain_modal_show_attributes.html
+++ /dev/null
@@ -1,79 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="attributes-show-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">{% trans 'Attribute references' %}</h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with values_count='{$ service.values.values_count $}' projects_count='{$ service.values.projects_count $}' %}
-                        This attribute is used for <b>{{ values_count }} values</b> in <b>{{ projects_count }} projects</b>.
-                        {% endblocktrans %}
-                        {% trans 'Furthermore, this attribute is used in the following elements:' %}
-                    </p>
-
-                    <h6>{% trans 'Conditions' %}</h6>
-                    <ul>
-                        <li ng-repeat="condition in service.values.conditions">
-                            <code>{$ condition.uri $}</code>
-                        </li>
-                        <li ng-hide="service.values.conditions.length">
-                            {% trans 'none' %}
-                        </li>
-                    </ul>
-
-                    <h6>{% trans 'Question sets' %}</h6>
-                    <ul>
-                        <li ng-repeat="questionset in service.values.questionsets">
-                            <code class="text-primary">{$ questionset.uri $}</code>
-                        </li>
-                        <li ng-hide="service.values.questionsets.length">
-                            {% trans 'none' %}
-                        </li>
-                    </ul>
-
-                    <h6>{% trans 'Questions' %}</h6>
-                    <ul>
-                        <li ng-repeat="question in service.values.questions">
-                            <code class="text-primary">{$ question.uri $}</code>
-                        </li>
-                        <li ng-hide="service.values.questions.length">
-                            {% trans 'none' %}
-                        </li>
-                    </ul>
-
-                    <h6>{% trans 'Tasks (start)' %}</h6>
-                    <ul>
-                        <li ng-repeat="task in service.values.tasks_as_start">
-                            <code>{$ task.uri $}</code>
-                        </li>
-                        <li ng-hide="service.values.tasks_as_start.length">
-                            {% trans 'none' %}
-                        </li>
-                    </ul>
-
-                    <h6>{% trans 'Tasks (end)' %}</h6>
-                    <ul>
-                        <li ng-repeat="task in service.values.tasks_as_end">
-                            <code>{$ task.uri $}</code>
-                        </li>
-                        <li ng-hide="service.values.tasks_as_end.length">
-                            {% trans 'none' %}
-                        </li>
-                    </ul>
-                </div>
-
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/domain/templates/domain/domain_options.html b/rdmo/domain/templates/domain/domain_options.html
deleted file mode 100644
index c04be18824..0000000000
--- a/rdmo/domain/templates/domain/domain_options.html
+++ /dev/null
@@ -1,36 +0,0 @@
-{% load i18n %}
-
-<i class="fa fa-lock text-danger" ng-show="attribute.locked"></i>
-<i class="fa fa-ban text-muted" ng-show="attribute.read_only"
-    title="{% trans 'Read only' %}">
-</i>
-
-<a href="" class="fa fa-plus"
-    title="{% trans 'Add attribute' %}"
-    ng-click="service.openFormModal('attributes', attribute, true)">
-</a>
-
-<a href="" class="fa fa-pencil"
-    title="{% trans 'Update attribute' %}"
-    ng-click="service.openFormModal('attributes', attribute)">
-</a>
-
-<a href="" class="fa fa-copy"
-    title="{% trans 'Copy attribute' %}"
-    ng-click="service.openFormModal('attributes', attribute, false, true)">
-</a>
-
-<a href="" class="fa fa-eye"
-    title="{% trans 'Show references' %}"
-    ng-click="service.openShowModal('attributes', attribute)">
-</a>
-
-<a href="{$ attribute.xml_url $}" class="fa fa-download"
-    title="{% trans 'Export attribute as XML' %}"
-    target="blank">
-</a>
-
-<a href="" class="fa fa-trash"
-    title="{% trans 'Delete attribute' %}"
-    ng-click="service.openDeleteModal('attributes', attribute)">
-</a>
diff --git a/rdmo/domain/urls/__init__.py b/rdmo/domain/urls/__init__.py
index 094bf3d80d..e69de29bb2 100644
--- a/rdmo/domain/urls/__init__.py
+++ b/rdmo/domain/urls/__init__.py
@@ -1,8 +0,0 @@
-from django.urls import re_path
-
-from ..views import DomainExportView, DomainView
-
-urlpatterns = [
-    re_path(r'^$', DomainView.as_view(), name='domain'),
-    re_path(r'^export/(?P<format>[a-z]+)/$', DomainExportView.as_view(), name='domain_export')
-]
diff --git a/rdmo/domain/validators.py b/rdmo/domain/validators.py
index 073c927163..7d3db99d83 100644
--- a/rdmo/domain/validators.py
+++ b/rdmo/domain/validators.py
@@ -14,7 +14,14 @@ def get_uri(self, data):
         if not data.get('key'):
             self.raise_validation_error({'key': _('This field is required.')})
         else:
-            path = self.model.build_path(data.get('key'), data.get('parent'))
+            parent = data.get('parent')
+            if parent is None:
+                # workaround for import
+                parent_id = data.get('parent_id')
+                if parent_id:
+                    parent = Attribute.objects.get(id=data.get('parent_id'))
+
+            path = self.model.build_path(data.get('key'), parent)
             uri = self.model.build_uri(data.get('uri_prefix'), path)
             return uri
 
@@ -23,6 +30,12 @@ class AttributeParentValidator(InstanceValidator):
 
     def __call__(self, data):
         parent = data.get('parent')
+        if parent is None:
+            # workaround for import
+            parent_id = data.get('parent_id')
+            if parent_id:
+                parent = Attribute.objects.get(id=data.get('parent_id'))
+
         if parent:
             if self.serializer:
                 # check copied attributes
diff --git a/rdmo/domain/views.py b/rdmo/domain/views.py
deleted file mode 100644
index fbabbee925..0000000000
--- a/rdmo/domain/views.py
+++ /dev/null
@@ -1,56 +0,0 @@
-import logging
-
-from django.conf import settings
-from django.utils.translation import gettext_lazy as _
-from django.views.generic import ListView, TemplateView
-from rdmo.core.exports import XMLResponse
-from rdmo.core.utils import (get_model_field_meta, render_to_csv,
-                             render_to_format)
-from rdmo.core.views import CSRFViewMixin, ModelPermissionMixin
-
-from .models import Attribute
-from .renderers import AttributeRenderer
-from .serializers.export import AttributeExportSerializer
-
-logger = logging.getLogger(__name__)
-
-
-class DomainView(ModelPermissionMixin, CSRFViewMixin, TemplateView):
-    template_name = 'domain/domain.html'
-    permission_required = 'domain.view_attribute'
-
-    def get_context_data(self, **kwargs):
-        context = super(DomainView, self).get_context_data(**kwargs)
-        context['export_formats'] = settings.EXPORT_FORMATS
-        context['meta'] = {
-            'Attribute': get_model_field_meta(Attribute)
-        }
-        return context
-
-
-class DomainExportView(ModelPermissionMixin, ListView):
-    model = Attribute
-    context_object_name = 'attributes'
-    permission_required = 'domain.view_attribute'
-
-    def render_to_response(self, context, **response_kwargs):
-        format = self.kwargs.get('format')
-        if format == 'xml':
-            serializer = AttributeExportSerializer(context['attributes'], many=True)
-            xml = AttributeRenderer().render(serializer.data)
-            return XMLResponse(xml, name='domain')
-        elif format[:3] == 'csv':
-            if format == 'csvcomma':
-                delimiter = ','
-            else:
-                delimiter = ';'
-            rows = []
-            for attribute in context['attributes']:
-                rows.append((
-                    attribute.key,
-                    attribute.comment,
-                    attribute.uri
-                ))
-            return render_to_csv(_('Domain'), rows, delimiter)
-        else:
-            return render_to_format(self.request, format, _('Domain'), 'domain/domain_export.html', context)
diff --git a/rdmo/domain/viewsets.py b/rdmo/domain/viewsets.py
index 83a4eaeef6..f4f042bbdd 100644
--- a/rdmo/domain/viewsets.py
+++ b/rdmo/domain/viewsets.py
@@ -8,7 +8,6 @@
 from rdmo.core.exports import XMLResponse
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
 from rdmo.core.utils import render_to_csv, render_to_format
-from rdmo.core.viewsets import CopyModelMixin
 
 from .models import Attribute
 from .renderers import AttributeRenderer
@@ -17,13 +16,13 @@
                              AttributeNestedSerializer, AttributeSerializer)
 
 
-class AttributeViewSet(CopyModelMixin, ModelViewSet):
+class AttributeViewSet(ModelViewSet):
     permission_classes = (HasModelPermission | HasObjectPermission, )
-    queryset = Attribute.objects.order_by('path') \
-                        .annotate(values_count=models.Count('values')) \
-                        .annotate(projects_count=models.Count('values__project', distinct=True)) \
-                        .prefetch_related('conditions', 'pages', 'questionsets', 'questions', 
-                                          'tasks_as_start', 'tasks_as_end', 'editors')
+    queryset = Attribute.objects.annotate(values_count=models.Count('values')) \
+                                .annotate(projects_count=models.Count('values__project', distinct=True)) \
+                                .prefetch_related('conditions', 'pages', 'questionsets', 'questions',
+                                                  'tasks_as_start', 'tasks_as_end', 'editors') \
+                                .order_by('path')
 
     filter_backends = (SearchFilter, DjangoFilterBackend)
     search_fields = ('uri', )
diff --git a/rdmo/management/assets/js/actions/configActions.js b/rdmo/management/assets/js/actions/configActions.js
index 24f32ac146..e3c57f2452 100644
--- a/rdmo/management/assets/js/actions/configActions.js
+++ b/rdmo/management/assets/js/actions/configActions.js
@@ -1,12 +1,14 @@
+import get from 'lodash/get'
+
 import CoreApi from 'rdmo/core/assets/js/api/CoreApi'
 import ManagementApi from '../api/ManagementApi'
 import ConditionsApi from '../api/ConditionsApi'
 import OptionsApi from '../api/OptionsApi'
 import QuestionsApi from '../api/QuestionsApi'
 
-export function updateConfig(path, value) {
-  return {type: 'config/updateConfig', path, value}
-}
+import { elementTypes } from '../constants/elements'
+import { findDescendants } from '../utils/elements'
+
 
 export function fetchConfig() {
   return (dispatch) => Promise.all([
@@ -31,3 +33,21 @@ export function fetchConfigSuccess(config) {
 export function fetchConfigError(errors) {
   return {type: 'elements/fetchConfigError', errors}
 }
+
+export function updateConfig(path, value) {
+  return {type: 'config/updateConfig', path, value}
+}
+
+export function toggleElements(element) {
+  return (dispatch, getState) => {
+    const path = `display.elements.${elementTypes[element.model]}.${element.id}`
+    const value = !get(getState().config, path, true)
+    dispatch(updateConfig(path, value))
+  }
+}
+
+export function toggleDescandants(element, elementType) {
+  return (dispatch) => {
+    findDescendants(element, elementType).forEach(e => dispatch(toggleElements(e)))
+  }
+}
diff --git a/rdmo/management/assets/js/actions/elementActions.js b/rdmo/management/assets/js/actions/elementActions.js
index 901ef06552..1c320eee1e 100644
--- a/rdmo/management/assets/js/actions/elementActions.js
+++ b/rdmo/management/assets/js/actions/elementActions.js
@@ -111,39 +111,39 @@ export function fetchElement(elementType, elementId, elementAction=null) {
     switch (elementType) {
       case 'catalogs':
         if (elementAction == 'nested') {
-          action = (dispatch) => QuestionsApi.fetchCatalog(elementId, 'nested')
-            .then(element => dispatch(fetchElementSuccess({ element })))
+          action = () => QuestionsApi.fetchCatalog(elementId, 'nested')
+            .then(element => ({ element }))
         } else {
-          action = (dispatch) => Promise.all([
+          action = () => Promise.all([
             QuestionsApi.fetchCatalog(elementId),
             QuestionsApi.fetchSections('index')
-          ]).then(([element, sections]) => dispatch(fetchElementSuccess({
+          ]).then(([element, sections]) => ({
             element, sections
-          })))
+          }))
         }
         break
 
       case 'sections':
         if (elementAction == 'nested') {
-          action = (dispatch) => QuestionsApi.fetchSection(elementId, 'nested')
-            .then(element => dispatch(fetchElementSuccess({ element })))
+          action = () => QuestionsApi.fetchSection(elementId, 'nested')
+            .then(element => ({ element }))
         } else {
-          action = (dispatch) => Promise.all([
+          action = () => Promise.all([
             QuestionsApi.fetchSection(elementId),
             QuestionsApi.fetchCatalogs('index'),
             QuestionsApi.fetchPages('index'),
-          ]).then(([element, catalogs, pages]) => dispatch(fetchElementSuccess({
+          ]).then(([element, catalogs, pages]) => ({
             element, catalogs, pages
-          })))
+          }))
         }
         break
 
       case 'pages':
         if (elementAction == 'nested') {
-          action = (dispatch) => QuestionsApi.fetchPage(elementId, 'nested')
-            .then(element => dispatch(fetchElementSuccess({ element })))
+          action = () => QuestionsApi.fetchPage(elementId, 'nested')
+            .then(element => ({ element }))
         } else {
-          action = (dispatch) => Promise.all([
+          action = () => Promise.all([
             QuestionsApi.fetchPage(elementId),
             DomainApi.fetchAttributes('index'),
             ConditionsApi.fetchConditions('index'),
@@ -151,18 +151,18 @@ export function fetchElement(elementType, elementId, elementAction=null) {
             QuestionsApi.fetchQuestionSets('index'),
             QuestionsApi.fetchQuestions('index')
           ]).then(([element, attributes, conditions, sections,
-                    questionsets, questions]) => dispatch(fetchElementSuccess({
+                    questionsets, questions]) => ({
             element, attributes, conditions, sections, questionsets, questions
-          })))
+          }))
         }
         break
 
       case 'questionsets':
         if (elementAction == 'nested') {
-          action = (dispatch) => QuestionsApi.fetchQuestionSet(elementId, 'nested')
-            .then(element => dispatch(fetchElementSuccess({ element })))
+          action = () => QuestionsApi.fetchQuestionSet(elementId, 'nested')
+            .then(element => ({ element }))
         } else {
-          action = (dispatch) => Promise.all([
+          action = () => Promise.all([
             QuestionsApi.fetchQuestionSet(elementId),
             DomainApi.fetchAttributes('index'),
             ConditionsApi.fetchConditions('index'),
@@ -170,18 +170,18 @@ export function fetchElement(elementType, elementId, elementAction=null) {
             QuestionsApi.fetchQuestionSets('index'),
             QuestionsApi.fetchQuestions('index')
           ]).then(([element, attributes, conditions, pages,
-                    questionsets, questions]) => dispatch(fetchElementSuccess({
+                    questionsets, questions]) => ({
             element, attributes, conditions, pages, questionsets, questions
-          })))
+          }))
         }
         break
 
       case 'questions':
         if (elementAction == 'nested') {
-          action = (dispatch) => QuestionsApi.fetchQuestion(elementId, 'nested')
-            .then(element => dispatch(fetchElementSuccess({ element })))
+          action = () => QuestionsApi.fetchQuestion(elementId, 'nested')
+            .then(element => ({ element }))
         } else {
-          action = (dispatch) => Promise.all([
+          action = () => Promise.all([
             QuestionsApi.fetchQuestion(elementId),
             DomainApi.fetchAttributes('index'),
             OptionsApi.fetchOptionSets('index'),
@@ -190,18 +190,18 @@ export function fetchElement(elementType, elementId, elementAction=null) {
             QuestionsApi.fetchPages('index'),
             QuestionsApi.fetchQuestionSets('index')
           ]).then(([element, attributes, optionsets, options, conditions,
-                    pages, questionsets]) => dispatch(fetchElementSuccess({
-            element, attributes, optionsets, options, conditions, pages, questionsets,
-          })))
+                    pages, questionsets]) => ({
+            element, attributes, optionsets, options, conditions, pages, questionsets
+          }))
         }
         break
 
       case 'attributes':
         if (elementAction == 'nested') {
-          action = (dispatch) => DomainApi.fetchAttribute(elementId, 'nested')
-            .then(element => dispatch(fetchElementSuccess({ element })))
+          action = () => DomainApi.fetchAttribute(elementId, 'nested')
+            .then(element => ({ element }))
         } else {
-          action = (dispatch) => Promise.all([
+          action = () => Promise.all([
             DomainApi.fetchAttribute(elementId),
             DomainApi.fetchAttributes('index'),
             ConditionsApi.fetchConditions('index'),
@@ -210,40 +210,40 @@ export function fetchElement(elementType, elementId, elementAction=null) {
             QuestionsApi.fetchQuestions('index'),
             TasksApi.fetchTasks('index'),
           ]).then(([element, attributes, conditions, pages, questionsets,
-                    questions, tasks]) => dispatch(fetchElementSuccess({
+                    questions, tasks]) => ({
             element, attributes, conditions, pages, questionsets, questions, tasks
-          })))
+          }))
         }
         break
 
       case 'optionsets':
         if (elementAction == 'nested') {
-          action = (dispatch) => OptionsApi.fetchOptionSet(elementId, 'nested')
-            .then(element => dispatch(fetchElementSuccess({ element })))
+          action = () => OptionsApi.fetchOptionSet(elementId, 'nested')
+            .then(element => ({ element }))
         } else {
-          action = (dispatch) => Promise.all([
+          action = () => Promise.all([
             OptionsApi.fetchOptionSet(elementId),
             ConditionsApi.fetchConditions('index'),
             OptionsApi.fetchOptions('index'),
             QuestionsApi.fetchQuestions('index')
-          ]).then(([element, conditions, options, questions]) => dispatch(fetchElementSuccess({
+          ]).then(([element, conditions, options, questions]) => ({
             element, conditions, options, questions
-          })))
+          }))
         }
         break
 
       case 'options':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           OptionsApi.fetchOption(elementId),
           OptionsApi.fetchOptionSets('index'),
           ConditionsApi.fetchConditions('index'),
-        ]).then(([element, optionsets, conditions]) => dispatch(fetchElementSuccess({
+        ]).then(([element, optionsets, conditions]) => ({
           element, optionsets, conditions
-        })))
+        }))
         break
 
       case 'conditions':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           ConditionsApi.fetchCondition(elementId),
           DomainApi.fetchAttributes('index'),
           OptionsApi.fetchOptionSets('index'),
@@ -253,33 +253,47 @@ export function fetchElement(elementType, elementId, elementAction=null) {
           QuestionsApi.fetchQuestions('index'),
           TasksApi.fetchTasks('index'),
         ]).then(([element, attributes, optionsets, options,
-                  pages, questionsets, questions, tasks]) => dispatch(fetchElementSuccess({
+                  pages, questionsets, questions, tasks]) => ({
           element, attributes, optionsets, options, pages, questionsets, questions, tasks
-        })))
+        }))
         break
 
       case 'tasks':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           TasksApi.fetchTask(elementId),
           DomainApi.fetchAttributes('index'),
           ConditionsApi.fetchConditions('index'),
           QuestionsApi.fetchCatalogs('index')
-        ]).then(([element, attributes, conditions, catalogs]) => dispatch(fetchElementSuccess({
+        ]).then(([element, attributes, conditions, catalogs]) => ({
           element, attributes, conditions, catalogs
-        })))
+        }))
         break
 
       case 'views':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           ViewsApi.fetchView(elementId),
           QuestionsApi.fetchCatalogs('index')
-        ]).then(([element, catalogs]) => dispatch(fetchElementSuccess({
+        ]).then(([element, catalogs]) => ({
           element, catalogs
-        })))
+        }))
         break
     }
 
     return dispatch(action)
+      .then(elements => {
+        if (elementAction == 'copy') {
+          const { settings, currentSite } = getState().config
+
+          elements.element.id = null
+          elements.element.read_only = false
+
+          if (settings.multisite) {
+            elements.element.sites = [currentSite.id]
+            elements.element.editors = [currentSite.id]
+          }
+        }
+        return dispatch(fetchElementSuccess({ ...elements }))
+      })
       .catch(error => dispatch(fetchElementError(error)))
   }
 }
@@ -385,51 +399,51 @@ export function createElement(elementType, parent={}) {
     let action
     switch (elementType) {
       case 'catalogs':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           QuestionsFactory.createCatalog(getState().config),
           QuestionsApi.fetchSections('index')
-        ]).then(([element, sections]) => dispatch(createElementSuccess({
+        ]).then(([element, sections]) => ({
           element, sections
-        })))
+        }))
         break
 
       case 'sections':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           QuestionsFactory.createSection(getState().config, parent),
           QuestionsApi.fetchPages('index'),
-        ]).then(([element, pages]) => dispatch(createElementSuccess({
+        ]).then(([element, pages]) => ({
           element, parent, pages
-        })))
+        }))
         break
 
       case 'pages':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           QuestionsFactory.createPage(getState().config, parent),
           DomainApi.fetchAttributes('index'),
           ConditionsApi.fetchConditions('index'),
           QuestionsApi.fetchQuestionSets('index'),
           QuestionsApi.fetchQuestions('index')
         ]).then(([element, attributes, conditions,
-                  questionsets, questions]) => dispatch(createElementSuccess({
+                  questionsets, questions]) => ({
           element, parent, attributes, conditions, questionsets, questions
-        })))
+        }))
         break
 
       case 'questionsets':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           QuestionsFactory.createQuestionSet(getState().config, parent),
           DomainApi.fetchAttributes('index'),
           ConditionsApi.fetchConditions('index'),
           QuestionsApi.fetchQuestionSets('index'),
           QuestionsApi.fetchQuestions('index')
         ]).then(([element, attributes, conditions,
-                  questionsets, questions]) => dispatch(createElementSuccess({
+                  questionsets, questions]) => ({
           element, parent, attributes, conditions, questionsets, questions
-        })))
+        }))
         break
 
       case 'questions':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           QuestionsFactory.createQuestion(getState().config, parent),
           DomainApi.fetchAttributes('index'),
           OptionsApi.fetchOptionSets('index'),
@@ -438,70 +452,71 @@ export function createElement(elementType, parent={}) {
           QuestionsApi.fetchWidgetTypes(),
           QuestionsApi.fetchValueTypes()
         ]).then(([element, attributes, optionsets,
-                  options, conditions]) => dispatch(createElementSuccess({
+                  options, conditions]) => ({
             element, parent, attributes, optionsets, options, conditions
-          })))
+          }))
         break
 
       case 'attributes':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           DomainFactory.createAttribute(getState().config, parent),
           DomainApi.fetchAttributes('index'),
-        ]).then(([element, attributes]) => dispatch(createElementSuccess({
+        ]).then(([element, attributes]) => ({
             element, parent, attributes
-          })))
+          }))
         break
 
       case 'optionsets':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           OptionsFactory.createOptionSet(getState().config, parent),
           OptionsApi.fetchOptions('index'),
-        ]).then(([element, options]) => dispatch(createElementSuccess({
+        ]).then(([element, options]) => ({
             element, parent, options
-          })))
+          }))
         break
 
       case 'options':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           OptionsFactory.createOption(getState().config, parent),
           OptionsApi.fetchOptionSets('index'),
-        ]).then(([element, optionsets]) => dispatch(createElementSuccess({
+        ]).then(([element, optionsets]) => ({
             element, parent, optionsets
-          })))
+          }))
         break
 
       case 'conditions':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           ConditionsFactory.createCondition(getState().config, parent),
           DomainApi.fetchAttributes('index'),
           OptionsApi.fetchOptions('index'),
-        ]).then(([element, attributes, options]) => dispatch(createElementSuccess({
+        ]).then(([element, attributes, options]) => ({
             element, parent, attributes, options
-          })))
+          }))
         break
 
       case 'tasks':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           TasksFactory.createTask(getState().config),
           DomainApi.fetchAttributes('index'),
           ConditionsApi.fetchConditions('index'),
           QuestionsApi.fetchCatalogs('index')
-        ]).then(([element, attributes, conditions, catalogs]) => dispatch(createElementSuccess({
+        ]).then(([element, attributes, conditions, catalogs]) => ({
             element, attributes, conditions, catalogs
-          })))
+          }))
         break
 
       case 'views':
-        action = (dispatch) => Promise.all([
+        action = () => Promise.all([
           ViewsFactory.createView(getState().config),
           QuestionsApi.fetchCatalogs('index')
-        ]).then(([element, catalogs]) => dispatch(createElementSuccess({
+        ]).then(([element, catalogs]) => ({
           element, catalogs
-        })))
+        }))
         break
     }
 
     return dispatch(action)
+      .then(elements => dispatch(createElementSuccess({...elements})))
       .catch(error => dispatch(createElementError(error)))
   }
 }
diff --git a/rdmo/management/assets/js/components/common/Filter.js b/rdmo/management/assets/js/components/common/Filter.js
index f0823e1f4e..48f10b29fa 100644
--- a/rdmo/management/assets/js/components/common/Filter.js
+++ b/rdmo/management/assets/js/components/common/Filter.js
@@ -42,11 +42,11 @@ FilterUriPrefix.propTypes = {
   onChange: PropTypes.func
 }
 
-const FilterSite = ({ value, options, onChange }) => {
+const FilterSite = ({ value, options, onChange, allLabel = 'All sites' }) => {
   return (
     <div className="form-group mb-0">
       <select className="form-control" value={value} onChange={event => onChange(event.target.value)}>
-        <option value="">{gettext('All sites')}</option>
+       <option value="">{gettext(allLabel)}</option>
         {
           options.map((option, index) => <option value={option.id} key={index}>{option.name}</option>)
         }
@@ -58,7 +58,8 @@ const FilterSite = ({ value, options, onChange }) => {
 FilterSite.propTypes = {
   value: PropTypes.string,
   options: PropTypes.array,
-  onChange: PropTypes.func
+  onChange: PropTypes.func,
+  allLabel: PropTypes.string
 }
 
 export { FilterString, FilterUriPrefix, FilterSite }
diff --git a/rdmo/management/assets/js/components/common/Links.js b/rdmo/management/assets/js/components/common/Links.js
index df0c99b0f5..0c291ad8ed 100644
--- a/rdmo/management/assets/js/components/common/Links.js
+++ b/rdmo/management/assets/js/components/common/Links.js
@@ -102,6 +102,24 @@ LockedLink.propTypes = {
   disabled: PropTypes.bool
 }
 
+const ShowElementsLink = ({ showElements, show, onClick }) => {
+  const className = classNames({
+    'element-btn-link fa': true,
+    'fa-chevron-down': showElements,
+    'fa-chevron-up': !showElements
+  })
+
+  const title = showElements ? gettext('Hide elements') : gettext('Show elements')
+
+  return show && <LinkButton className={className} title={title} onClick={onClick} />
+}
+
+ShowElementsLink.propTypes = {
+  showElements: PropTypes.bool.isRequired,
+  show: PropTypes.bool.isRequired,
+  onClick: PropTypes.func.isRequired
+}
+
 const ExportLink = ({ exportUrl, title, exportFormats, csv=false, full=false }) => {
   return (
     <span className="dropdown">
@@ -217,5 +235,5 @@ ShowLink.propTypes = {
   onClick: PropTypes.func.isRequired
 }
 
-export { EditLink, CopyLink, AddLink, AvailableLink, LockedLink,
+export { EditLink, CopyLink, AddLink, AvailableLink, LockedLink, ShowElementsLink,
          NestedLink, ExportLink, ExtendLink, CodeLink, ErrorLink, WarningLink, ShowLink }
diff --git a/rdmo/management/assets/js/components/edit/EditCondition.js b/rdmo/management/assets/js/components/edit/EditCondition.js
index 9a609b10e1..b62de93bb8 100644
--- a/rdmo/management/assets/js/components/edit/EditCondition.js
+++ b/rdmo/management/assets/js/components/edit/EditCondition.js
@@ -100,7 +100,7 @@ const EditCondition = ({ config, condition, elements, elementActions }) => {
                        onChange={updateCondition} />
           </div>
           <div className="col-sm-6">
-            <Text config={config} element={condition} field="key"
+            <Text config={config} element={condition} field="uri_path"
                   onChange={updateCondition} />
           </div>
         </div>
diff --git a/rdmo/management/assets/js/components/edit/EditTask.js b/rdmo/management/assets/js/components/edit/EditTask.js
index f4913769f1..2057562628 100644
--- a/rdmo/management/assets/js/components/edit/EditTask.js
+++ b/rdmo/management/assets/js/components/edit/EditTask.js
@@ -67,7 +67,7 @@ const EditTask = ({ config, task, elements, elementActions}) => {
                        onChange={updateTask} />
           </div>
           <div className="col-sm-6">
-            <Text config={config} element={task} field="key"
+            <Text config={config} element={task} field="uri_path"
                   onChange={updateTask} />
           </div>
         </div>
diff --git a/rdmo/management/assets/js/components/edit/EditView.js b/rdmo/management/assets/js/components/edit/EditView.js
index 62fb9ba3ea..ab19a8b9c0 100644
--- a/rdmo/management/assets/js/components/edit/EditView.js
+++ b/rdmo/management/assets/js/components/edit/EditView.js
@@ -61,7 +61,7 @@ const EditView = ({ config, view, elements, elementActions }) => {
                        onChange={updateView} />
           </div>
           <div className="col-sm-6">
-            <Text config={config} element={view} field="key"
+            <Text config={config} element={view} field="uri_path"
                   onChange={updateView} />
           </div>
         </div>
diff --git a/rdmo/management/assets/js/components/element/Attribute.js b/rdmo/management/assets/js/components/element/Attribute.js
index 1acd40bc57..8f123633c3 100644
--- a/rdmo/management/assets/js/components/element/Attribute.js
+++ b/rdmo/management/assets/js/components/element/Attribute.js
@@ -8,9 +8,10 @@ import { ElementErrors } from '../common/Errors'
 import { EditLink, CopyLink, AddLink, LockedLink, NestedLink, ExportLink, CodeLink } from '../common/Links'
 import { ReadOnlyIcon } from '../common/Icons'
 
-const Attribute = ({ config, attribute, elementActions, display='list', filter=null, indent=0 }) => {
+const Attribute = ({ config, attribute, elementActions, display='list', indent=0,
+                     filter=null, filterEditors=false }) => {
 
-  const showElement = filterElement(filter, attribute)
+  const showElement = filterElement(config, filter, false, filterEditors, attribute)
 
   const editUrl = buildPath(config.baseUrl, 'attributes', attribute.id)
   const copyUrl = buildPath(config.baseUrl, 'attributes', attribute.id, 'copy')
@@ -28,7 +29,7 @@ const Attribute = ({ config, attribute, elementActions, display='list', filter=n
     <div className="element">
       <div className="pull-right">
         <ReadOnlyIcon title={gettext('This attribute is read only')} show={attribute.read_only} />
-        <NestedLink title={gettext('View attribute nested')} href={nestedUrl} onClick={fetchNested} 
+        <NestedLink title={gettext('View attribute nested')} href={nestedUrl} onClick={fetchNested}
                     show={!attribute.is_leaf_node} />
         <EditLink title={gettext('Edit attribute')} href={editUrl} onClick={fetchEdit} />
         <CopyLink title={gettext('Copy attribute')} href={copyUrl} onClick={fetchCopy} />
@@ -83,8 +84,9 @@ Attribute.propTypes = {
   attribute: PropTypes.object.isRequired,
   elementActions: PropTypes.object.isRequired,
   display: PropTypes.string,
+  indent: PropTypes.number,
   filter: PropTypes.object,
-  indent: PropTypes.number
+  filterEditors: PropTypes.bool
 }
 
 export default Attribute
diff --git a/rdmo/management/assets/js/components/element/Catalog.js b/rdmo/management/assets/js/components/element/Catalog.js
index e6ae177006..fd934ae585 100644
--- a/rdmo/management/assets/js/components/element/Catalog.js
+++ b/rdmo/management/assets/js/components/element/Catalog.js
@@ -1,5 +1,6 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { filterElement } from '../../utils/filter'
 import { buildPath } from '../../utils/location'
@@ -9,9 +10,10 @@ import { EditLink, CopyLink, AddLink, AvailableLink, LockedLink, NestedLink,
          ExportLink, CodeLink } from '../common/Links'
 import { ReadOnlyIcon } from '../common/Icons'
 
-const Catalog = ({ config, catalog, elementActions, display='list', filter=null }) => {
+const Catalog = ({ config, catalog, elementActions, display='list',
+                   filter=false, filterSites=false, filterEditors=false }) => {
 
-  const showElement = filterElement(filter, catalog)
+  const showElement = filterElement(config, filter, filterSites, filterEditors, catalog)
 
   const editUrl = buildPath(config.baseUrl, 'catalogs', catalog.id)
   const copyUrl = buildPath(config.baseUrl, 'catalogs', catalog.id, 'copy')
@@ -49,7 +51,7 @@ const Catalog = ({ config, catalog, elementActions, display='list', filter=null
           <strong>{gettext('Catalog')}{': '}</strong> {catalog.title}
         </p>
         {
-          config.display.uri.catalogs &&
+          get(config, 'display.uri.catalogs', true) &&
           <CodeLink className="code-questions" uri={catalog.uri} onClick={() => fetchEdit()} />
         }
         <ElementErrors element={catalog} />
@@ -74,7 +76,9 @@ Catalog.propTypes = {
   catalog: PropTypes.object.isRequired,
   elementActions: PropTypes.object.isRequired,
   display: PropTypes.string,
-  filter: PropTypes.object
+  filter: PropTypes.string,
+  filterSites: PropTypes.bool,
+  filterEditors: PropTypes.bool
 }
 
 export default Catalog
diff --git a/rdmo/management/assets/js/components/element/Condition.js b/rdmo/management/assets/js/components/element/Condition.js
index 2a7c6dfc22..816f77c65d 100644
--- a/rdmo/management/assets/js/components/element/Condition.js
+++ b/rdmo/management/assets/js/components/element/Condition.js
@@ -8,9 +8,9 @@ import { ElementErrors } from '../common/Errors'
 import { EditLink, CopyLink, LockedLink, ExportLink, CodeLink } from '../common/Links'
 import { ReadOnlyIcon } from '../common/Icons'
 
-const Condition = ({ config, condition, elementActions, filter=null }) => {
+const Condition = ({ config, condition, elementActions, filter=false, filterEditors=false }) => {
 
-  const showElement = filterElement(filter, condition)
+  const showElement = filterElement(config, filter, false, filterEditors, condition)
 
   const editUrl = buildPath(config.baseUrl, 'conditions', condition.id)
   const copyUrl = buildPath(config.baseUrl, 'conditions', condition.id, 'copy')
@@ -48,7 +48,8 @@ Condition.propTypes = {
   config: PropTypes.object.isRequired,
   condition: PropTypes.object.isRequired,
   elementActions: PropTypes.object.isRequired,
-  filter: PropTypes.object
+  filter: PropTypes.string,
+  filterEditors: PropTypes.bool
 }
 
 export default Condition
diff --git a/rdmo/management/assets/js/components/element/Option.js b/rdmo/management/assets/js/components/element/Option.js
index 2fe2355388..64aac49362 100644
--- a/rdmo/management/assets/js/components/element/Option.js
+++ b/rdmo/management/assets/js/components/element/Option.js
@@ -1,5 +1,6 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { filterElement } from '../../utils/filter'
 import { buildPath } from '../../utils/location'
@@ -8,9 +9,9 @@ import { ElementErrors } from '../common/Errors'
 import { EditLink, CopyLink, LockedLink, ExportLink, CodeLink } from '../common/Links'
 import { ReadOnlyIcon } from '../common/Icons'
 
-const Option = ({ config, option, elementActions, display='list', indent=0, filter=null }) => {
+const Option = ({ config, option, elementActions, display='list', indent=0, filter=false, filterEditors=false }) => {
 
-  const showElement = filterElement(filter, option)
+  const showElement = filterElement(config, filter, false, filterEditors, option)
 
   const editUrl = buildPath(config.baseUrl, 'options', option.id)
   const copyUrl = buildPath(config.baseUrl, 'options', option.id, 'copy')
@@ -36,7 +37,7 @@ const Option = ({ config, option, elementActions, display='list', indent=0, filt
           <strong>{gettext('Option')}{': '}</strong> {option.text}
         </p>
         {
-          config.display.uri.options &&
+          get(config, 'display.uri.options', true) &&
           <CodeLink className="code-options" uri={option.uri} onClick={() => fetchEdit()} />
         }
         <ElementErrors element={option} />
@@ -68,7 +69,8 @@ Option.propTypes = {
   elementActions: PropTypes.object.isRequired,
   display: PropTypes.string,
   indent: PropTypes.number,
-  filter: PropTypes.object
+  filter: PropTypes.string,
+  filterEditors: PropTypes.bool
 }
 
 export default Option
diff --git a/rdmo/management/assets/js/components/element/OptionSet.js b/rdmo/management/assets/js/components/element/OptionSet.js
index fa43465cc0..3628d8efa1 100644
--- a/rdmo/management/assets/js/components/element/OptionSet.js
+++ b/rdmo/management/assets/js/components/element/OptionSet.js
@@ -9,9 +9,9 @@ import { EditLink, CopyLink, AddLink, LockedLink, NestedLink,
          ExportLink, CodeLink } from '../common/Links'
 import { ReadOnlyIcon } from '../common/Icons'
 
-const OptionSet = ({ config, optionset, elementActions, display='list', filter=null }) => {
+const OptionSet = ({ config, optionset, elementActions, display='list', filter=false, filterEditors=false }) => {
 
-  const showElement = filterElement(filter, optionset)
+  const showElement = filterElement(config, filter, false, filterEditors, optionset)
 
   const editUrl = buildPath(config.baseUrl, 'optionsets', optionset.id)
   const copyUrl = buildPath(config.baseUrl, 'optionsets', optionset.id, 'copy')
@@ -65,7 +65,8 @@ OptionSet.propTypes = {
   optionset: PropTypes.object.isRequired,
   elementActions: PropTypes.object.isRequired,
   display: PropTypes.string,
-  filter: PropTypes.object
+  filter: PropTypes.string,
+  filterEditors: PropTypes.bool
 }
 
 export default OptionSet
diff --git a/rdmo/management/assets/js/components/element/Page.js b/rdmo/management/assets/js/components/element/Page.js
index 15b2f264e6..af692967be 100644
--- a/rdmo/management/assets/js/components/element/Page.js
+++ b/rdmo/management/assets/js/components/element/Page.js
@@ -1,5 +1,6 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { filterElement } from '../../utils/filter'
 import { buildPath } from '../../utils/location'
@@ -8,13 +9,15 @@ import QuestionSet from './QuestionSet'
 import Question from './Question'
 import { ElementErrors } from '../common/Errors'
 import { EditLink, CopyLink, AddLink, LockedLink, NestedLink,
-         ExportLink, CodeLink } from '../common/Links'
+         ExportLink, CodeLink, ShowElementsLink } from '../common/Links'
 import { ReadOnlyIcon } from '../common/Icons'
 import { Drag, Drop } from '../common/DragAndDrop'
 
-const Page = ({ config, page, elementActions, display='list', filter=null, indent=0 }) => {
+const Page = ({ config, page, configActions, elementActions, display='list', indent=0,
+                filter=false, filterEditors=false }) => {
 
-  const showElement = filterElement(filter, page)
+  const showElement = filterElement(config, filter, false, filterEditors, page)
+  const showElements = get(config, `display.elements.pages.${page.id}`, true)
 
   const editUrl = buildPath(config.baseUrl, 'pages', page.id)
   const copyUrl = buildPath(config.baseUrl, 'pages', page.id, 'copy')
@@ -25,6 +28,7 @@ const Page = ({ config, page, elementActions, display='list', filter=null, inden
   const fetchCopy = () => elementActions.fetchElement('pages', page.id, 'copy')
   const fetchNested = () => elementActions.fetchElement('pages', page.id, 'nested')
   const toggleLocked = () => elementActions.storeElement('pages', {...page, locked: !page.locked })
+  const toggleElements = () => configActions.toggleElements(page)
 
   const createQuestionSet = () => elementActions.createElement('questionsets', { page })
   const createQuestion = () => elementActions.createElement('questions', { page })
@@ -36,7 +40,8 @@ const Page = ({ config, page, elementActions, display='list', filter=null, inden
     <div className="element">
       <div className="pull-right">
         <ReadOnlyIcon title={gettext('This page is read only')} show={page.read_only} />
-        <NestedLink title={gettext('View page nested')} href={nestedUrl} onClick={fetchNested} />
+        <NestedLink title={gettext('View page nested')} href={nestedUrl} onClick={fetchNested} show={display != 'nested'} />
+        <ShowElementsLink showElements={showElements} show={display == 'nested'} onClick={toggleElements} />
         <EditLink title={gettext('Edit page')} href={editUrl} onClick={fetchEdit} />
         <CopyLink title={gettext('Copy page')} href={copyUrl} onClick={fetchCopy} />
         <AddLink title={gettext('Add question')} altTitle={gettext('Add question set')}
@@ -52,17 +57,17 @@ const Page = ({ config, page, elementActions, display='list', filter=null, inden
           <strong>{gettext('Page')}{': '}</strong> {page.title}
         </p>
         {
-          config.display.uri.pages && <p>
+          get(config, 'display.uri.pages', true) && <p>
             <CodeLink className="code-questions" uri={page.uri} onClick={() => fetchEdit()} />
           </p>
         }
         {
-          config.display.uri.attributes && page.attribute_uri && <p>
+          get(config, 'display.uri.attributes', true) && page.attribute_uri && <p>
             <CodeLink className="code-domain" uri={page.attribute_uri} onClick={() => fetchAttribute()} />
           </p>
         }
         {
-          config.display.uri.conditions && page.condition_uris.map((uri, index) => (
+          get(config, 'display.uri.conditions', true) && page.condition_uris.map((uri, index) => (
             <p key={index}>
               <CodeLink className="code-conditions" uri={uri} onClick={() => fetchCondition(index)} />
             </p>
@@ -84,7 +89,7 @@ const Page = ({ config, page, elementActions, display='list', filter=null, inden
       return (
         <>
           {
-            showElement && config.display.elements.pages && (
+            showElement && (
               <Drop element={page} elementActions={elementActions}>
                 <div className="panel panel-default panel-nested" style={{ marginLeft: 30 * indent }}>
                   <div className="panel-heading">
@@ -95,12 +100,14 @@ const Page = ({ config, page, elementActions, display='list', filter=null, inden
             )
           }
           {
-            page.elements.map((element, index) => {
+            showElements && page.elements.map((element, index) => {
               if (element.model == 'questions.questionset') {
-                return <QuestionSet key={index} config={config} questionset={element} elementActions={elementActions}
+                return <QuestionSet key={index} config={config} questionset={element}
+                                    configActions={configActions} elementActions={elementActions}
                                     display="nested" filter={filter}  indent={indent + 1} />
               } else {
-                return <Question key={index} config={config} question={element} elementActions={elementActions}
+                return <Question key={index} config={config} question={element}
+                                 configActions={configActions} elementActions={elementActions}
                                  display="nested" filter={filter}  indent={indent + 1}  />
               }
             })
@@ -116,10 +123,12 @@ const Page = ({ config, page, elementActions, display='list', filter=null, inden
 Page.propTypes = {
   config: PropTypes.object.isRequired,
   page: PropTypes.object.isRequired,
+  configActions: PropTypes.object.isRequired,
   elementActions: PropTypes.object.isRequired,
   display: PropTypes.string,
-  filter: PropTypes.object,
-  indent: PropTypes.number
+  indent: PropTypes.number,
+  filter: PropTypes.string,
+  filterEditors: PropTypes.bool
 }
 
 export default Page
diff --git a/rdmo/management/assets/js/components/element/Question.js b/rdmo/management/assets/js/components/element/Question.js
index e80cc9ceff..ecf280573f 100644
--- a/rdmo/management/assets/js/components/element/Question.js
+++ b/rdmo/management/assets/js/components/element/Question.js
@@ -1,5 +1,6 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { filterElement } from '../../utils/filter'
 import { buildPath } from '../../utils/location'
@@ -9,9 +10,10 @@ import { EditLink, CopyLink, LockedLink, ExportLink, CodeLink } from '../common/
 import { ReadOnlyIcon } from '../common/Icons'
 import { Drag, Drop } from '../common/DragAndDrop'
 
-const Question = ({ config, question, elementActions, display='list', filter=null, indent=0 }) => {
+const Question = ({ config, question, elementActions, display='list', indent=0,
+                    filter=false, filterEditors=false }) => {
 
-  const showElement = filterElement(filter, question)
+  const showElement = filterElement(config, filter, false, filterEditors, question)
 
   const editUrl = buildPath(config.baseUrl, 'questions', question.id)
   const copyUrl = buildPath(config.baseUrl, 'questions', question.id, 'copy')
@@ -43,24 +45,24 @@ const Question = ({ config, question, elementActions, display='list', filter=nul
           {question.text}
         </p>
         {
-          config.display.uri.questions && <p>
+          get(config, 'display.uri.questions', true) && <p>
             <CodeLink className="code-questions" uri={question.uri} onClick={() => fetchEdit()} />
           </p>
         }
         {
-          config.display.uri.attributes && question.attribute_uri && <p>
+          get(config, 'display.uri.attributes', true) && question.attribute_uri && <p>
             <CodeLink className="code-domain" uri={question.attribute_uri} onClick={() => fetchAttribute()} />
           </p>
         }
         {
-          config.display.uri.conditions && question.condition_uris.map((uri, index) => (
+          get(config, 'display.uri.conditions', true) && question.condition_uris.map((uri, index) => (
             <p key={index}>
               <CodeLink className="code-conditions" uri={uri} onClick={() => fetchCondition(index)} />
             </p>
           ))
         }
         {
-          config.display.uri.optionsets && question.optionset_uris.map((uri, index) => (
+          get(config, 'display.uri.optionsets', true) && question.optionset_uris.map((uri, index) => (
             <p key={index}>
               <CodeLink className="code-options" uri={uri} onClick={() => fetchOptionSet(index)} />
             </p>
@@ -82,7 +84,7 @@ const Question = ({ config, question, elementActions, display='list', filter=nul
       return (
         <>
           {
-            showElement && config.display.elements.questions && (
+            showElement && (
               <div className="panel panel-default panel-nested" style={{ marginLeft: 30 * indent }}>
                 <div className="panel-body">
                   { elementNode }
@@ -99,10 +101,12 @@ const Question = ({ config, question, elementActions, display='list', filter=nul
 Question.propTypes = {
   config: PropTypes.object.isRequired,
   question: PropTypes.object.isRequired,
+  configActions: PropTypes.object.isRequired,
   elementActions: PropTypes.object.isRequired,
   display: PropTypes.string,
-  filter: PropTypes.object,
-  indent: PropTypes.number
+  indent: PropTypes.number,
+  filter: PropTypes.string,
+  filterEditors: PropTypes.bool
 }
 
 export default Question
diff --git a/rdmo/management/assets/js/components/element/QuestionSet.js b/rdmo/management/assets/js/components/element/QuestionSet.js
index 980855ccb1..024652536f 100644
--- a/rdmo/management/assets/js/components/element/QuestionSet.js
+++ b/rdmo/management/assets/js/components/element/QuestionSet.js
@@ -1,5 +1,6 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { filterElement } from '../../utils/filter'
 import { buildPath } from '../../utils/location'
@@ -7,13 +8,15 @@ import { buildPath } from '../../utils/location'
 import Question from './Question'
 import { ElementErrors } from '../common/Errors'
 import { EditLink, CopyLink, AddLink, LockedLink,
-         NestedLink, ExportLink, CodeLink } from '../common/Links'
+         NestedLink, ExportLink, CodeLink, ShowElementsLink } from '../common/Links'
 import { ReadOnlyIcon } from '../common/Icons'
 import { Drag, Drop } from '../common/DragAndDrop'
 
-const QuestionSet = ({ config, questionset, elementActions, display='list', filter=null, indent=0 }) => {
+const QuestionSet = ({ config, questionset, configActions, elementActions, display='list', indent=0,
+                       filter=false, filterEditors=false }) => {
 
-  const showElement = filterElement(filter, questionset)
+  const showElement = filterElement(config, filter, false, filterEditors, questionset)
+  const showElements = get(config, `display.elements.questionsets.${questionset.id}`, true)
 
   const editUrl = buildPath(config.baseUrl, 'questionsets', questionset.id)
   const copyUrl = buildPath(config.baseUrl, 'questionsets', questionset.id, 'copy')
@@ -24,6 +27,7 @@ const QuestionSet = ({ config, questionset, elementActions, display='list', filt
   const fetchCopy = () => elementActions.fetchElement('questionsets', questionset.id, 'copy')
   const fetchNested = () => elementActions.fetchElement('questionsets', questionset.id, 'nested')
   const toggleLocked = () => elementActions.storeElement('questionsets', {...questionset, locked: !questionset.locked })
+  const toggleElements = () => configActions.toggleElements(questionset)
 
   const createQuestionSet = () => elementActions.createElement('questionsets', { questionset })
   const createQuestion = () => elementActions.createElement('questions', { questionset })
@@ -35,7 +39,8 @@ const QuestionSet = ({ config, questionset, elementActions, display='list', filt
     <div className="element">
       <div className="pull-right">
         <ReadOnlyIcon title={gettext('This question set is read only')} show={questionset.read_only} />
-        <NestedLink title={gettext('View question set nested')} href={nestedUrl} onClick={fetchNested} />
+        <NestedLink title={gettext('View question set nested')} href={nestedUrl} onClick={fetchNested} show={display != 'nested'} />
+        <ShowElementsLink showElements={showElements} show={display == 'nested'} onClick={toggleElements} />
         <EditLink title={gettext('Edit question set')} href={editUrl} onClick={fetchEdit} />
         <CopyLink title={gettext('Copy question set')} href={copyUrl} onClick={fetchCopy} />
         <AddLink title={gettext('Add question')} altTitle={gettext('Add question set')}
@@ -51,17 +56,17 @@ const QuestionSet = ({ config, questionset, elementActions, display='list', filt
           <strong>{gettext('Question set')}{': '}</strong> {questionset.title}
         </p>
         {
-          config.display.uri.questionsets && <p>
+          get(config, 'display.uri.questionsets', true) && <p>
             <CodeLink className="code-questions" uri={questionset.uri} onClick={() => fetchEdit()} />
           </p>
         }
         {
-          config.display.uri.attributes && questionset.attribute_uri &&<p>
+          get(config, 'display.uri.attributes', true) && questionset.attribute_uri &&<p>
             <CodeLink className="code-domain" uri={questionset.attribute_uri} onClick={() => fetchAttribute()} />
           </p>
         }
         {
-          config.display.uri.conditions && questionset.condition_uris.map((uri, index) => (
+          get(config, 'display.uri.conditions', true) && questionset.condition_uris.map((uri, index) => (
             <p key={index}>
               <CodeLink className="code-conditions" uri={uri} onClick={() => fetchCondition(index)} />
             </p>
@@ -83,7 +88,7 @@ const QuestionSet = ({ config, questionset, elementActions, display='list', filt
       return (
         <>
           {
-            showElement && config.display.elements.questionsets && (
+            showElement && (
               <Drop element={questionset} elementActions={elementActions}>
                 <div className="panel panel-default panel-nested" style={{ marginLeft: 30 * indent }}>
                   <div className="panel-heading">
@@ -94,13 +99,15 @@ const QuestionSet = ({ config, questionset, elementActions, display='list', filt
             )
           }
           {
-            questionset.elements.map((element, index) => {
+            showElements && questionset.elements.map((element, index) => {
               if (element.model == 'questions.questionset') {
-                return <QuestionSet key={index} config={config} questionset={element} elementActions={elementActions}
-                                    display="nested" filter={filter}  indent={indent + 1}  />
+                return <QuestionSet key={index} config={config} questionset={element}
+                                    configActions={configActions} elementActions={elementActions}
+                                    display="nested" filter={filter} indent={indent + 1}  />
               } else {
-                return <Question key={index} config={config} question={element} elementActions={elementActions}
-                                 display="nested" filter={filter}  indent={indent + 1} />
+                return <Question key={index} config={config} question={element}
+                                 configActions={configActions} elementActions={elementActions}
+                                 display="nested" filter={filter} indent={indent + 1} />
               }
             })
           }
@@ -115,10 +122,12 @@ const QuestionSet = ({ config, questionset, elementActions, display='list', filt
 QuestionSet.propTypes = {
   config: PropTypes.object.isRequired,
   questionset: PropTypes.object.isRequired,
+  configActions: PropTypes.object.isRequired,
   elementActions: PropTypes.object.isRequired,
   display: PropTypes.string,
-  filter: PropTypes.object,
-  indent: PropTypes.number
+  indent: PropTypes.number,
+  filter: PropTypes.string,
+  filterEditors: PropTypes.bool
 }
 
 export default QuestionSet
diff --git a/rdmo/management/assets/js/components/element/Section.js b/rdmo/management/assets/js/components/element/Section.js
index 27a1671148..c675ee8103 100644
--- a/rdmo/management/assets/js/components/element/Section.js
+++ b/rdmo/management/assets/js/components/element/Section.js
@@ -1,5 +1,6 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 import isEmpty from 'lodash/isEmpty'
 
 import { filterElement } from '../../utils/filter'
@@ -7,13 +8,17 @@ import { buildPath } from '../../utils/location'
 
 import Page from './Page'
 import { ElementErrors } from '../common/Errors'
-import { EditLink, CopyLink, AddLink, LockedLink, NestedLink, ExportLink, CodeLink } from '../common/Links'
+import { EditLink, CopyLink, AddLink, LockedLink, NestedLink, ExportLink,
+         CodeLink, ShowElementsLink } from '../common/Links'
 import { ReadOnlyIcon } from '../common/Icons'
 import { Drag, Drop } from '../common/DragAndDrop'
 
-const Section = ({ config, section, elementActions, display='list', filter=null, indent=0 }) => {
 
-  const showElement = filterElement(filter, section)
+const Section = ({ config, section, configActions, elementActions, display='list', indent=0,
+                   filter=false, filterEditors=false }) => {
+
+  const showElement = filterElement(config, filter, false, filterEditors, section)
+  const showElements = get(config, `display.elements.sections.${section.id}`, true)
 
   const editUrl = buildPath(config.baseUrl, 'sections', section.id)
   const copyUrl = buildPath(config.baseUrl, 'sections', section.id, 'copy')
@@ -24,6 +29,7 @@ const Section = ({ config, section, elementActions, display='list', filter=null,
   const fetchCopy = () => elementActions.fetchElement('sections', section.id, 'copy')
   const fetchNested = () => elementActions.fetchElement('sections', section.id, 'nested')
   const toggleLocked = () => elementActions.storeElement('sections', {...section, locked: !section.locked })
+  const toggleElements = () => configActions.toggleElements(section)
 
   const createPage = () => elementActions.createElement('pages', { section })
 
@@ -31,7 +37,8 @@ const Section = ({ config, section, elementActions, display='list', filter=null,
     <div className="element">
       <div className="pull-right">
         <ReadOnlyIcon title={gettext('This section is read only')} show={section.read_only} />
-        <NestedLink title={gettext('View section nested')} href={nestedUrl} onClick={fetchNested} />
+        <NestedLink title={gettext('View section nested')} href={nestedUrl} onClick={fetchNested} show={display != 'nested'} />
+        <ShowElementsLink showElements={showElements} show={display == 'nested'} onClick={toggleElements} />
         <EditLink title={gettext('Edit section')} href={editUrl} onClick={fetchEdit} />
         <CopyLink title={gettext('Copy section')} href={copyUrl} onClick={fetchCopy} />
         <AddLink title={gettext('Add page')} onClick={createPage} disabled={section.read_only} />
@@ -47,7 +54,7 @@ const Section = ({ config, section, elementActions, display='list', filter=null,
           <strong>{gettext('Section')}{': '}</strong> {section.title}
         </p>
         {
-          config.display.uri.sections &&
+          get(config, 'display.uri.sections', true) &&
           <CodeLink className="code-questions" uri={section.uri} onClick={() => fetchEdit()} />
         }
         <ElementErrors element={section} />
@@ -66,7 +73,7 @@ const Section = ({ config, section, elementActions, display='list', filter=null,
       return (
         <>
           {
-            showElement && config.display.elements.sections && (
+            showElement && (
               <Drop element={section} elementActions={elementActions}>
                 <div className="panel panel-default panel-nested" style={{ marginLeft: 30 * indent }}>
                   <div className="panel-heading">
@@ -81,8 +88,9 @@ const Section = ({ config, section, elementActions, display='list', filter=null,
             <Drop element={section.elements[0]} elementActions={elementActions} indent={indent + 1} mode="before" />
           }
           {
-            section.elements.map((page, index) => (
-              <Page key={index} config={config} page={page} elementActions={elementActions}
+            showElements && section.elements.map((page, index) => (
+              <Page key={index} config={config} page={page}
+                    configActions={configActions} elementActions={elementActions}
                     display="nested" filter={filter} indent={indent + 1} />
             ))
           }
@@ -97,10 +105,12 @@ const Section = ({ config, section, elementActions, display='list', filter=null,
 Section.propTypes = {
   config: PropTypes.object.isRequired,
   section: PropTypes.object.isRequired,
+  configActions: PropTypes.object.isRequired,
   elementActions: PropTypes.object.isRequired,
   display: PropTypes.string,
-  filter: PropTypes.object,
-  indent: PropTypes.number
+  indent: PropTypes.number,
+  filter: PropTypes.string,
+  filterEditors: PropTypes.bool
 }
 
 export default Section
diff --git a/rdmo/management/assets/js/components/element/Task.js b/rdmo/management/assets/js/components/element/Task.js
index 2095fbf5f4..c8a4f981bf 100644
--- a/rdmo/management/assets/js/components/element/Task.js
+++ b/rdmo/management/assets/js/components/element/Task.js
@@ -8,9 +8,9 @@ import { ElementErrors } from '../common/Errors'
 import { EditLink, CopyLink, AvailableLink, LockedLink, ExportLink, CodeLink } from '../common/Links'
 import { ReadOnlyIcon } from '../common/Icons'
 
-const Task = ({ config, task, elementActions, filter=null }) => {
+const Task = ({ config, task, elementActions, filter=false, filterSites=false, filterEditors=false }) => {
 
-  const showElement = filterElement(filter, task)
+  const showElement = filterElement(config, filter, filterSites, filterEditors, task)
 
   const editUrl = buildPath(config.baseUrl, 'tasks', task.id)
   const copyUrl = buildPath(config.baseUrl, 'tasks', task.id, 'copy')
@@ -52,8 +52,11 @@ const Task = ({ config, task, elementActions, filter=null }) => {
 Task.propTypes = {
   config: PropTypes.object.isRequired,
   task: PropTypes.object.isRequired,
+  configActions: PropTypes.object.isRequired,
   elementActions: PropTypes.object.isRequired,
-  filter: PropTypes.object
+  filter: PropTypes.string,
+  filterSites: PropTypes.bool,
+  filterEditors: PropTypes.bool
 }
 
 export default Task
diff --git a/rdmo/management/assets/js/components/element/View.js b/rdmo/management/assets/js/components/element/View.js
index 5bec6647bc..3c1655ae81 100644
--- a/rdmo/management/assets/js/components/element/View.js
+++ b/rdmo/management/assets/js/components/element/View.js
@@ -8,9 +8,9 @@ import { ElementErrors } from '../common/Errors'
 import { EditLink, CopyLink, AvailableLink, LockedLink, ExportLink, CodeLink } from '../common/Links'
 import { ReadOnlyIcon } from '../common/Icons'
 
-const View = ({ config, view, elementActions, filter=null }) => {
+const View = ({ config, view, elementActions, filter=false, filterSites=false, filterEditors=false }) => {
 
-  const showElement = filterElement(filter, view)
+  const showElement = filterElement(config, filter, filterSites, filterEditors, view)
 
   const editUrl = buildPath(config.baseUrl, 'views', view.id)
   const copyUrl = buildPath(config.baseUrl, 'views', view.id, 'copy')
@@ -52,8 +52,11 @@ const View = ({ config, view, elementActions, filter=null }) => {
 View.propTypes = {
   config: PropTypes.object.isRequired,
   view: PropTypes.object.isRequired,
+  configActions: PropTypes.object.isRequired,
   elementActions: PropTypes.object.isRequired,
-  filter: PropTypes.object
+  filter: PropTypes.string,
+  filterSites: PropTypes.bool,
+  filterEditors: PropTypes.bool
 }
 
 export default View
diff --git a/rdmo/management/assets/js/components/elements/Attributes.js b/rdmo/management/assets/js/components/elements/Attributes.js
index 7a75399f7d..ffb83c62d9 100644
--- a/rdmo/management/assets/js/components/elements/Attributes.js
+++ b/rdmo/management/assets/js/components/elements/Attributes.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
-import { FilterString, FilterUriPrefix } from '../common/Filter'
+import { FilterString, FilterUriPrefix, FilterSite } from '../common/Filter'
 import { BackButton, NewButton } from '../common/Buttons'
 
 import Attribute from '../element/Attribute'
@@ -12,6 +13,7 @@ const Attributes = ({ config, attributes, configActions, elementActions }) => {
 
   const updateFilterString = (value) => configActions.updateConfig('filter.attributes.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.attributes.uri_prefix', value)
+  const updateFilterEditor = (value) => configActions.updateConfig('filter.editors', value)
   const createAttribute = () => elementActions.createElement('attributes')
 
   return (
@@ -26,22 +28,29 @@ const Attributes = ({ config, attributes, configActions, elementActions }) => {
 
       <div className="panel-body">
         <div className="row">
-          <div className="col-sm-8">
-            <FilterString value={config.filter.attributes.search} onChange={updateFilterString}
+          <div className={config.settings.multisite ? 'col-sm-6' : 'col-sm-8'}>
+            <FilterString value={get(config, 'filter.attributes.search', '')} onChange={updateFilterString}
                           placeholder={gettext('Filter attributes')} />
           </div>
           <div className="col-sm-4">
-            <FilterUriPrefix value={config.filter.attributes.uriPrefix} onChange={updateFilterUriPrefix}
+            <FilterUriPrefix value={get(config, 'filter.attributes.uriPrefix', '')} onChange={updateFilterUriPrefix}
                              options={getUriPrefixes(attributes)} />
           </div>
+          {
+            config.settings.multisite && <div className="col-sm-2">
+              <FilterSite value={get(config, 'filter.editors', '')} onChange={updateFilterEditor}
+                          options={config.sites} allLabel={gettext('All editors')} />
+            </div>
+          }
         </div>
       </div>
 
       <ul className="list-group">
       {
         attributes.map((attribute, index) => (
-          <Attribute key={index} config={config} attribute={attribute} elementActions={elementActions}
-                     filter={config.filter.attributes} />
+          <Attribute key={index} config={config} attribute={attribute}
+                     configActions={configActions} elementActions={elementActions}
+                     filter="attributes" filterEditors={true} />
         ))
       }
       </ul>
diff --git a/rdmo/management/assets/js/components/elements/Catalogs.js b/rdmo/management/assets/js/components/elements/Catalogs.js
index b46cd8c15f..a51d3c9f32 100644
--- a/rdmo/management/assets/js/components/elements/Catalogs.js
+++ b/rdmo/management/assets/js/components/elements/Catalogs.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
-import { FilterString, FilterUriPrefix, FilterSite } from '../common/Filter'
+import { FilterString, FilterUriPrefix, FilterSite} from '../common/Filter'
 import { Checkbox } from '../common/Checkboxes'
 import { BackButton, NewButton } from '../common/Buttons'
 
@@ -13,7 +14,8 @@ const Catalogs = ({ config, catalogs, configActions, elementActions }) => {
 
   const updateFilterString = (value) => configActions.updateConfig('filter.catalogs.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.catalogs.uri_prefix', value)
-  const updateFilterSite = (value) => configActions.updateConfig('filter.catalogs.sites', value)
+  const updateFilterSite = (value) => configActions.updateConfig('filter.sites', value)
+  const updateFilterEditor = (value) => configActions.updateConfig('filter.editors', value)
   const updateDisplayCatalogURI = (value) => configActions.updateConfig('display.uri.catalogs', value)
 
   const createCatalog = () => elementActions.createElement('catalogs')
@@ -31,24 +33,30 @@ const Catalogs = ({ config, catalogs, configActions, elementActions }) => {
       <div className="panel-body">
         <div className="row">
           <div className={config.settings.multisite ? 'col-sm-4' : 'col-sm-8'}>
-            <FilterString value={config.filter.catalogs.search} onChange={updateFilterString}
+            <FilterString value={get(config, 'filter.catalogs.search', '')} onChange={updateFilterString}
                           placeholder={gettext('Filter catalogs')} />
           </div>
           <div className="col-sm-4">
-            <FilterUriPrefix value={config.filter.catalogs.uri_prefix} onChange={updateFilterUriPrefix}
+            <FilterUriPrefix value={get(config, 'filter.catalogs.uri_prefix', '')} onChange={updateFilterUriPrefix}
                              options={getUriPrefixes(catalogs)} />
           </div>
           {
-            config.settings.multisite && <div className="col-sm-4">
-              <FilterSite value={config.filter.catalogs.sites} onChange={updateFilterSite}
-                          options={config.sites} />
-            </div>
+            config.settings.multisite && <>
+              <div className="col-sm-2">
+                <FilterSite value={get(config, 'filter.sites', '')} onChange={updateFilterSite}
+                            options={config.sites} />
+              </div>
+              <div className="col-sm-2">
+                <FilterSite value={get(config, 'filter.editors', '')} onChange={updateFilterEditor}
+                            options={config.sites} allLabel={gettext('All editors')} />
+              </div>
+            </>
           }
         </div>
         <div className="checkboxes">
           <span className="mr-10">{gettext('Show URIs:')}</span>
           <Checkbox label={<code className="code-questions">{gettext('Catalogs')}</code>}
-                    value={config.display.uri.catalogs} onChange={updateDisplayCatalogURI} />
+                    value={get(config, 'display.uri.catalogs', true)} onChange={updateDisplayCatalogURI} />
         </div>
       </div>
 
@@ -56,7 +64,8 @@ const Catalogs = ({ config, catalogs, configActions, elementActions }) => {
       {
         catalogs.map((catalog, index) => (
           <Catalog key={index} config={config} catalog={catalog}
-                   elementActions={elementActions} filter={config.filter.catalogs} />
+                   configActions={configActions} elementActions={elementActions}
+                   filter="catalogs" filterSites={true} filterEditors={true} />
         ))
       }
       </ul>
diff --git a/rdmo/management/assets/js/components/elements/Conditions.js b/rdmo/management/assets/js/components/elements/Conditions.js
index b39746096d..b46c6829d9 100644
--- a/rdmo/management/assets/js/components/elements/Conditions.js
+++ b/rdmo/management/assets/js/components/elements/Conditions.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
-import { FilterString, FilterUriPrefix } from '../common/Filter'
+import { FilterString, FilterUriPrefix, FilterSite} from '../common/Filter'
 import { BackButton, NewButton } from '../common/Buttons'
 
 import Condition from '../element/Condition'
@@ -12,6 +13,7 @@ const Conditions = ({ config, conditions, configActions, elementActions}) => {
 
   const updateFilterString = (value) => configActions.updateConfig('filter.conditions.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.conditions.uri_prefix', value)
+  const updateFilterEditor = (value) => configActions.updateConfig('filter.editors', value)
 
   const createCondition = () => elementActions.createElement('conditions')
 
@@ -27,22 +29,29 @@ const Conditions = ({ config, conditions, configActions, elementActions}) => {
 
       <div className="panel-body">
         <div className="row">
-          <div className="col-sm-8">
-            <FilterString value={config.filter.conditions.search} onChange={updateFilterString}
+          <div className={config.settings.multisite ? 'col-sm-6' : 'col-sm-8'}>
+            <FilterString value={get(config, 'filter.conditions.search', '')} onChange={updateFilterString}
                           placeholder={gettext('Filter conditions')} />
           </div>
           <div className="col-sm-4">
-            <FilterUriPrefix value={config.filter.conditions.uri_prefix} onChange={updateFilterUriPrefix}
+            <FilterUriPrefix value={get(config, 'filter.conditions.uri_prefix', '')} onChange={updateFilterUriPrefix}
                              options={getUriPrefixes(conditions)} />
           </div>
+          {
+            config.settings.multisite && <div className="col-sm-2">
+              <FilterSite value={get(config, 'filter.editors', '')} onChange={updateFilterEditor}
+                          options={config.sites} allLabel={gettext('All editors')} />
+            </div>
+          }
         </div>
       </div>
 
       <ul className="list-group">
       {
         conditions.map((condition, index) => (
-          <Condition key={index} config={config} condition={condition} elementActions={elementActions}
-                     filter={config.filter.conditions} />
+          <Condition key={index} config={config} condition={condition}
+                     configActions={configActions} elementActions={elementActions}
+                     filter="conditions" filterEditors={true} />
         ))
       }
       </ul>
diff --git a/rdmo/management/assets/js/components/elements/OptionSets.js b/rdmo/management/assets/js/components/elements/OptionSets.js
index 14b3289716..33093bb17f 100644
--- a/rdmo/management/assets/js/components/elements/OptionSets.js
+++ b/rdmo/management/assets/js/components/elements/OptionSets.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
-import { FilterString, FilterUriPrefix } from '../common/Filter'
+import { FilterString, FilterUriPrefix, FilterSite } from '../common/Filter'
 import { BackButton, NewButton } from '../common/Buttons'
 
 import OptionSet from '../element/OptionSet'
@@ -12,6 +13,7 @@ const OptionSets = ({ config, optionsets, configActions, elementActions}) => {
 
   const updateFilterString = (value) => configActions.updateConfig('filter.optionsets.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.optionsets.uri_prefix', value)
+  const updateFilterEditor = (value) => configActions.updateConfig('filter.editors', value)
 
   const createOptionSet = () => elementActions.createElement('optionsets')
 
@@ -27,22 +29,29 @@ const OptionSets = ({ config, optionsets, configActions, elementActions}) => {
 
       <div className="panel-body">
         <div className="row">
-          <div className="col-sm-8">
-            <FilterString value={config.filter.optionsets.search} onChange={updateFilterString}
+          <div className={config.settings.multisite ? 'col-sm-6' : 'col-sm-8'}>
+            <FilterString value={get(config, 'filter.optionsets.search', '')} onChange={updateFilterString}
                           placeholder={gettext('Filter option sets')} />
           </div>
           <div className="col-sm-4">
-            <FilterUriPrefix value={config.filter.optionsets.uri_prefix} onChange={updateFilterUriPrefix}
+            <FilterUriPrefix value={get(config, 'filter.optionsets.uri_prefix', '')} onChange={updateFilterUriPrefix}
                              options={getUriPrefixes(optionsets)} />
           </div>
+          {
+            config.settings.multisite && <div className="col-sm-2">
+              <FilterSite value={get(config, 'filter.editors', '')} onChange={updateFilterEditor}
+                          options={config.sites} allLabel={gettext('All editors')} />
+            </div>
+          }
         </div>
       </div>
 
       <ul className="list-group">
       {
         optionsets.map((optionset, index) => (
-          <OptionSet key={index} config={config} optionset={optionset} elementActions={elementActions}
-                     filter={config.filter.optionsets} />
+          <OptionSet key={index} config={config} optionset={optionset}
+                     configActions={configActions} elementActions={elementActions}
+                     filter="optionsets" filterEditors={true} />
         ))
       }
       </ul>
diff --git a/rdmo/management/assets/js/components/elements/Options.js b/rdmo/management/assets/js/components/elements/Options.js
index b5cb401273..62b70a1a3e 100644
--- a/rdmo/management/assets/js/components/elements/Options.js
+++ b/rdmo/management/assets/js/components/elements/Options.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
-import { FilterString, FilterUriPrefix } from '../common/Filter'
+import { FilterString, FilterUriPrefix, FilterSite } from '../common/Filter'
 import { Checkbox } from '../common/Checkboxes'
 import { BackButton, NewButton } from '../common/Buttons'
 
@@ -13,6 +14,8 @@ const Options = ({ config, options, configActions, elementActions }) => {
 
   const updateFilterString = (value) => configActions.updateConfig('filter.options.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.options.uri_prefix', value)
+  const updateFilterEditor = (value) => configActions.updateConfig('filter.editors', value)
+
   const updateDisplayURI = (value) => configActions.updateConfig('display.uri.options', value)
 
   const createOption = () => elementActions.createElement('options')
@@ -29,27 +32,34 @@ const Options = ({ config, options, configActions, elementActions }) => {
 
       <div className="panel-body">
         <div className="row">
-          <div className="col-sm-8">
-            <FilterString value={config.filter.options.search} onChange={updateFilterString}
+          <div className={config.settings.multisite ? 'col-sm-6' : 'col-sm-8'}>
+            <FilterString value={get(config, 'filter.options.search', '')} onChange={updateFilterString}
                           placeholder={gettext('Filter options')} />
           </div>
           <div className="col-sm-4">
-            <FilterUriPrefix value={config.filter.options.uri_prefix} onChange={updateFilterUriPrefix}
+            <FilterUriPrefix value={get(config, 'filter.options.uri_prefix', '')} onChange={updateFilterUriPrefix}
                              options={getUriPrefixes(options)} />
           </div>
+          {
+            config.settings.multisite && <div className="col-sm-2">
+              <FilterSite value={get(config, 'filter.editors', '')} onChange={updateFilterEditor}
+                          options={config.sites} allLabel={gettext('All editors')} />
+            </div>
+          }
         </div>
         <div className="checkboxes">
           <span className="mr-10">{gettext('Show URIs:')}</span>
           <Checkbox label={<code className="code-options">{gettext('Options')}</code>}
-                    value={config.display.uri.options} onChange={updateDisplayURI} />
+                    value={get(config, 'display.uri.options', true)} onChange={updateDisplayURI} />
         </div>
       </div>
 
       <ul className="list-group">
       {
         options.map((option, index) => (
-          <Option key={index} config={config} option={option} elementActions={elementActions}
-                  filter={config.filter.options} />
+          <Option key={index} config={config} option={option}
+                  configActions={configActions} elementActions={elementActions}
+                  filter="options" filterEditors={true} />
         ))
       }
       </ul>
diff --git a/rdmo/management/assets/js/components/elements/Pages.js b/rdmo/management/assets/js/components/elements/Pages.js
index bf0f959761..2fdce9f585 100644
--- a/rdmo/management/assets/js/components/elements/Pages.js
+++ b/rdmo/management/assets/js/components/elements/Pages.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
-import { FilterString, FilterUriPrefix } from '../common/Filter'
+import { FilterString, FilterUriPrefix, FilterSite } from '../common/Filter'
 import { Checkbox } from '../common/Checkboxes'
 import { BackButton, NewButton } from '../common/Buttons'
 
@@ -13,6 +14,8 @@ const Pages = ({ config, pages, configActions, elementActions }) => {
 
   const updateFilterString = (value) => configActions.updateConfig('filter.pages.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.pages.uri_prefix', value)
+  const updateFilterEditor = (value) => configActions.updateConfig('filter.editors', value)
+
   const updateDisplayPagesURI = (value) => configActions.updateConfig('display.uri.pages', value)
   const updateDisplayAttributesURI = (value) => configActions.updateConfig('display.uri.attributes', value)
   const updateDisplayConditionsURI = (value) => configActions.updateConfig('display.uri.conditions', value)
@@ -31,31 +34,38 @@ const Pages = ({ config, pages, configActions, elementActions }) => {
 
       <div className="panel-body">
         <div className="row">
-          <div className="col-sm-8">
-            <FilterString value={config.filter.pages.search} onChange={updateFilterString}
+          <div className={config.settings.multisite ? 'col-sm-6' : 'col-sm-8'}>
+            <FilterString value={get(config, 'filter.pages.search', '')} onChange={updateFilterString}
                           placeholder={gettext('Filter pages')} />
           </div>
           <div className="col-sm-4">
-            <FilterUriPrefix value={config.filter.pages.uri_prefix} onChange={updateFilterUriPrefix}
+            <FilterUriPrefix value={get(config, 'filter.pages.uri_prefix', '')} onChange={updateFilterUriPrefix}
                              options={getUriPrefixes(pages)} />
           </div>
+          {
+            config.settings.multisite && <div className="col-sm-2">
+              <FilterSite value={get(config, 'filter.editors', '')} onChange={updateFilterEditor}
+                          options={config.sites} allLabel={gettext('All editors')} />
+            </div>
+          }
         </div>
         <div className="checkboxes">
           <span className="mr-10">{gettext('Show URIs:')}</span>
           <Checkbox label={<code className="code-questions">{gettext('Pages')}</code>}
-                    value={config.display.uri.pages} onChange={updateDisplayPagesURI} />
+                    value={get(config, 'display.uri.pages', true)} onChange={updateDisplayPagesURI} />
           <Checkbox label={<code className="code-domain">{gettext('Attributes')}</code>}
-                    value={config.display.uri.attributes} onChange={updateDisplayAttributesURI} />
+                    value={get(config, 'display.uri.attributes', true)} onChange={updateDisplayAttributesURI} />
           <Checkbox label={<code className="code-conditions">{gettext('Conditions')}</code>}
-                    value={config.display.uri.conditions} onChange={updateDisplayConditionsURI} />
+                    value={get(config, 'display.uri.conditions', true)} onChange={updateDisplayConditionsURI} />
         </div>
       </div>
 
       <ul className="list-group">
       {
         pages.map((page, index) => (
-          <Page key={index} config={config} page={page} elementActions={elementActions}
-                filter={config.filter.pages} />
+          <Page key={index} config={config} page={page}
+                configActions={configActions} elementActions={elementActions}
+                filter="pages" filterEditors={true} />
         ))
       }
       </ul>
diff --git a/rdmo/management/assets/js/components/elements/QuestionSets.js b/rdmo/management/assets/js/components/elements/QuestionSets.js
index a44720129b..8c1096ba89 100644
--- a/rdmo/management/assets/js/components/elements/QuestionSets.js
+++ b/rdmo/management/assets/js/components/elements/QuestionSets.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
-import { FilterString, FilterUriPrefix } from '../common/Filter'
+import { FilterString, FilterUriPrefix, FilterSite } from '../common/Filter'
 import { Checkbox } from '../common/Checkboxes'
 import { BackButton, NewButton } from '../common/Buttons'
 
@@ -13,6 +14,7 @@ const QuestionSets = ({ config, questionsets, configActions, elementActions }) =
 
   const updateFilterString = (value) => configActions.updateConfig('filter.questionsets.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.questionsets.uri_prefix', value)
+  const updateFilterEditor = (value) => configActions.updateConfig('filter.editors', value)
 
   const updateDisplayQuestionSetsURI = (value) => configActions.updateConfig('display.uri.questionsets', value)
   const updateDisplayAttributesURI = (value) => configActions.updateConfig('display.uri.attributes', value)
@@ -32,31 +34,38 @@ const QuestionSets = ({ config, questionsets, configActions, elementActions }) =
 
       <div className="panel-body">
         <div className="row">
-          <div className="col-sm-8">
-            <FilterString value={config.filter.questionsets.search} onChange={updateFilterString}
+          <div className={config.settings.multisite ? 'col-sm-6' : 'col-sm-8'}>
+            <FilterString value={get(config, 'filter.questionsets.search', '')} onChange={updateFilterString}
                           placeholder={gettext('Filter question sets')} />
           </div>
           <div className="col-sm-4">
-            <FilterUriPrefix value={config.filter.questionsets.uri_prefix} onChange={updateFilterUriPrefix}
+            <FilterUriPrefix value={get(config, 'filter.questionsets.uri_prefix', '')} onChange={updateFilterUriPrefix}
                              options={getUriPrefixes(questionsets)} />
           </div>
+          {
+            config.settings.multisite && <div className="col-sm-2">
+              <FilterSite value={get(config, 'filter.editors', '')} onChange={updateFilterEditor}
+                          options={config.sites} allLabel={gettext('All editors')} />
+            </div>
+          }
         </div>
         <div className="checkboxes">
           <span className="mr-10">{gettext('Show URIs:')}</span>
           <Checkbox label={<code className="code-questions">{gettext('Question sets')}</code>}
-                    value={config.display.uri.questionsets} onChange={updateDisplayQuestionSetsURI} />
+                    value={get(config, 'display.uri.questionsets', true)} onChange={updateDisplayQuestionSetsURI} />
           <Checkbox label={<code className="code-domain">{gettext('Attributes')}</code>}
-                    value={config.display.uri.attributes} onChange={updateDisplayAttributesURI} />
+                    value={get(config, 'display.uri.attributes', true)} onChange={updateDisplayAttributesURI} />
           <Checkbox label={<code className="code-conditions">{gettext('Conditions')}</code>}
-                    value={config.display.uri.conditions} onChange={updateDisplayConditionsURI} />
+                    value={get(config, 'display.uri.conditions', true)} onChange={updateDisplayConditionsURI} />
         </div>
       </div>
 
       <ul className="list-group">
       {
         questionsets.map((questionset, index) => (
-          <QuestionSet key={index} config={config} questionset={questionset} elementActions={elementActions}
-                       filter={config.filter.questionsets} />
+          <QuestionSet key={index} config={config} questionset={questionset}
+                       configActions={configActions} elementActions={elementActions}
+                       filter="questionsets" filterEditors={true} />
         ))
       }
       </ul>
diff --git a/rdmo/management/assets/js/components/elements/Questions.js b/rdmo/management/assets/js/components/elements/Questions.js
index 69e8f91fd8..ac6cf6e4b4 100644
--- a/rdmo/management/assets/js/components/elements/Questions.js
+++ b/rdmo/management/assets/js/components/elements/Questions.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
-import { FilterString, FilterUriPrefix } from '../common/Filter'
+import { FilterString, FilterUriPrefix, FilterSite } from '../common/Filter'
 import { Checkbox } from '../common/Checkboxes'
 import { BackButton, NewButton } from '../common/Buttons'
 
@@ -13,6 +14,8 @@ const Questions = ({ config, questions, configActions, elementActions }) => {
 
   const updateFilterString = (value) => configActions.updateConfig('filter.questions.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.questions.uri_prefix', value)
+  const updateFilterEditor = (value) => configActions.updateConfig('filter.editors', value)
+
   const updateDisplayQuestionsURI = (value) => configActions.updateConfig('display.uri.questions', value)
   const updateDisplayAttributesURI = (value) => configActions.updateConfig('display.uri.attributes', value)
   const updateDisplayConditionsURI = (value) => configActions.updateConfig('display.uri.conditions', value)
@@ -32,33 +35,40 @@ const Questions = ({ config, questions, configActions, elementActions }) => {
 
       <div className="panel-body">
         <div className="row">
-          <div className="col-sm-8">
-            <FilterString value={config.filter.questions.search} onChange={updateFilterString}
+          <div className={config.settings.multisite ? 'col-sm-6' : 'col-sm-8'}>
+            <FilterString value={get(config, 'filter.questions.search', '')} onChange={updateFilterString}
                           placeholder={gettext('Filter questions')} />
           </div>
           <div className="col-sm-4">
-            <FilterUriPrefix value={config.filter.questions.uri_prefix} onChange={updateFilterUriPrefix}
+            <FilterUriPrefix value={get(config, 'filter.questions.uri_prefix', '')} onChange={updateFilterUriPrefix}
                              options={getUriPrefixes(questions)} />
           </div>
+          {
+            config.settings.multisite && <div className="col-sm-2">
+              <FilterSite value={get(config, 'filter.editors', '')} onChange={updateFilterEditor}
+                          options={config.sites} allLabel={gettext('All editors')} />
+            </div>
+          }
         </div>
         <div className="checkboxes">
           <span className="mr-10">{gettext('Show URIs:')}</span>
           <Checkbox label={<code className="code-questions">{gettext('Questions')}</code>}
-                    value={config.display.uri.questions} onChange={updateDisplayQuestionsURI} />
+                    value={get(config, 'display.uri.questions', true)} onChange={updateDisplayQuestionsURI} />
           <Checkbox label={<code className="code-domain">{gettext('Attributes')}</code>}
-                    value={config.display.uri.attributes} onChange={updateDisplayAttributesURI} />
+                    value={get(config, 'display.uri.attributes', true)} onChange={updateDisplayAttributesURI} />
           <Checkbox label={<code className="code-conditions">{gettext('Conditions')}</code>}
-                    value={config.display.uri.conditions} onChange={updateDisplayConditionsURI} />
+                    value={get(config, 'display.uri.conditions', true)} onChange={updateDisplayConditionsURI} />
           <Checkbox label={<code className="code-options">{gettext('Option sets')}</code>}
-                    value={config.display.uri.optionsets} onChange={updateDisplayOptionSetURI} />
+                    value={get(config, 'display.uri.optionsets', true)} onChange={updateDisplayOptionSetURI} />
         </div>
       </div>
 
       <ul className="list-group">
       {
         questions.map((question, index) => (
-          <Question key={index} config={config} question={question} elementActions={elementActions}
-                    filter={config.filter.questions} />
+          <Question key={index} config={config} question={question}
+                    configActions={configActions} elementActions={elementActions}
+                    filter="questions" filterEditors={true} />
         ))
       }
       </ul>
diff --git a/rdmo/management/assets/js/components/elements/Sections.js b/rdmo/management/assets/js/components/elements/Sections.js
index 3efc968d22..cea485df27 100644
--- a/rdmo/management/assets/js/components/elements/Sections.js
+++ b/rdmo/management/assets/js/components/elements/Sections.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
-import { FilterString, FilterUriPrefix } from '../common/Filter'
+import { FilterString, FilterUriPrefix, FilterSite } from '../common/Filter'
 import { Checkbox } from '../common/Checkboxes'
 import { BackButton, NewButton } from '../common/Buttons'
 
@@ -13,6 +14,8 @@ const Sections = ({ config, sections, configActions, elementActions }) => {
 
   const updateFilterString = (value) => configActions.updateConfig('filter.sections.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.sections.uri_prefix', value)
+  const updateFilterEditor = (value) => configActions.updateConfig('filter.editors', value)
+
   const updateDisplaySectionURI = (value) => configActions.updateConfig('display.uri.sections', value)
 
   const createSection = () => elementActions.createElement('sections')
@@ -29,27 +32,34 @@ const Sections = ({ config, sections, configActions, elementActions }) => {
 
       <div className="panel-body">
         <div className="row">
-          <div className="col-sm-8">
-            <FilterString value={config.filter.sections.search} onChange={updateFilterString}
+          <div className={config.settings.multisite ? 'col-sm-6' : 'col-sm-8'}>
+            <FilterString value={get(config, 'filter.sections.search', '')} onChange={updateFilterString}
                           placeholder={gettext('Filter sections')} />
           </div>
           <div className="col-sm-4">
-            <FilterUriPrefix value={config.filter.sections.uri_prefix} onChange={updateFilterUriPrefix}
+            <FilterUriPrefix value={get(config, 'filter.sections.uri_prefix', '')} onChange={updateFilterUriPrefix}
                              options={getUriPrefixes(sections)} />
           </div>
+          {
+            config.settings.multisite && <div className="col-sm-2">
+              <FilterSite value={get(config, 'filter.editors', '')} onChange={updateFilterEditor}
+                          options={config.sites} allLabel={gettext('All editors')} />
+            </div>
+          }
         </div>
         <div className="checkboxes">
           <span className="mr-10">{gettext('Show URIs:')}</span>
           <Checkbox label={<code className="code-questions">{gettext('Sections')}</code>}
-                    value={config.display.uri.sections} onChange={updateDisplaySectionURI} />
+                    value={get(config, 'display.uri.sections', true)} onChange={updateDisplaySectionURI} />
         </div>
       </div>
 
       <ul className="list-group">
       {
         sections.map((section, index) => (
-          <Section key={index} config={config} section={section} elementActions={elementActions}
-                   filter={config.filter.sections} />
+          <Section key={index} config={config} section={section}
+                   configActions={configActions} elementActions={elementActions}
+                   filter="sections" filterEditors={true} />
         ))
       }
       </ul>
diff --git a/rdmo/management/assets/js/components/elements/Tasks.js b/rdmo/management/assets/js/components/elements/Tasks.js
index cc9f6e370a..693181538f 100644
--- a/rdmo/management/assets/js/components/elements/Tasks.js
+++ b/rdmo/management/assets/js/components/elements/Tasks.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
-import { FilterString, FilterUriPrefix, FilterSite } from '../common/Filter'
+import { FilterString, FilterUriPrefix, FilterSite} from '../common/Filter'
 import { BackButton, NewButton } from '../common/Buttons'
 
 import Task from '../element/Task'
@@ -12,7 +13,8 @@ const Tasks = ({ config, tasks, configActions, elementActions }) => {
 
   const updateFilterString = (value) => configActions.updateConfig('filter.tasks.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.tasks.uri_prefix', value)
-  const updateFilterSite = (value) => configActions.updateConfig('filter.tasks.sites', value)
+  const updateFilterSite = (value) => configActions.updateConfig('filter.sites', value)
+  const updateFilterEditor = (value) => configActions.updateConfig('filter.editors', value)
 
   const createTask = () => elementActions.createElement('tasks')
 
@@ -29,18 +31,24 @@ const Tasks = ({ config, tasks, configActions, elementActions }) => {
       <div className="panel-body">
         <div className="row">
           <div className={config.settings.multisite ? 'col-sm-4' : 'col-sm-8'}>
-            <FilterString value={config.filter.tasks.search} onChange={updateFilterString}
+            <FilterString value={get(config, 'filter.tasks.search', '')} onChange={updateFilterString}
                           placeholder={gettext('Filter tasks')} />
           </div>
           <div className="col-sm-4">
-            <FilterUriPrefix value={config.filter.tasks.uri_prefix} onChange={updateFilterUriPrefix}
+            <FilterUriPrefix value={get(config, 'filter.tasks.uri_prefix', '')} onChange={updateFilterUriPrefix}
                              options={getUriPrefixes(tasks)} />
           </div>
           {
-            config.settings.multisite && <div className="col-sm-4">
-              <FilterSite value={config.filter.tasks.sites} onChange={updateFilterSite}
-                          options={config.sites} />
-            </div>
+            config.settings.multisite && <>
+              <div className="col-sm-2">
+                <FilterSite value={get(config, 'filter.sites', '')} onChange={updateFilterSite}
+                            options={config.sites} />
+              </div>
+              <div className="col-sm-2">
+                <FilterSite value={get(config, 'filter.editors', '')} onChange={updateFilterEditor}
+                            options={config.sites} allLabel={gettext('All editors')} />
+              </div>
+            </>
           }
         </div>
       </div>
@@ -48,8 +56,9 @@ const Tasks = ({ config, tasks, configActions, elementActions }) => {
       <ul className="list-group">
       {
         tasks.map((task, index) => (
-          <Task key={index} config={config} task={task} elementActions={elementActions}
-                filter={config.filter.tasks} />
+          <Task key={index} config={config} task={task}
+                configActions={configActions} elementActions={elementActions}
+                filter="tasks" filterSites={true} filterEditors={true} />
         ))
       }
       </ul>
diff --git a/rdmo/management/assets/js/components/elements/Views.js b/rdmo/management/assets/js/components/elements/Views.js
index 8934dc4761..794f36d6ee 100644
--- a/rdmo/management/assets/js/components/elements/Views.js
+++ b/rdmo/management/assets/js/components/elements/Views.js
@@ -1,9 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
-import { FilterString, FilterUriPrefix, FilterSite } from '../common/Filter'
+import { FilterString, FilterUriPrefix, FilterSite} from '../common/Filter'
 import { BackButton, NewButton } from '../common/Buttons'
 
 import View from '../element/View'
@@ -12,7 +13,8 @@ const Views = ({ config, views, configActions, elementActions }) => {
 
   const updateFilterString = (value) => configActions.updateConfig('filter.views.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.views.uri_prefix', value)
-  const updateFilterSite = (value) => configActions.updateConfig('filter.views.sites', value)
+  const updateFilterSite = (value) => configActions.updateConfig('filter.sites', value)
+  const updateFilterEditor = (value) => configActions.updateConfig('filter.editors', value)
 
   const createView = () => elementActions.createElement('views')
 
@@ -29,18 +31,24 @@ const Views = ({ config, views, configActions, elementActions }) => {
       <div className="panel-body">
         <div className="row">
           <div className={config.settings.multisite ? 'col-sm-4' : 'col-sm-8'}>
-            <FilterString value={config.filter.views.search} onChange={updateFilterString}
+            <FilterString value={get(config, 'filter.views.search', '')} onChange={updateFilterString}
                           placeholder={gettext('Filter views')} />
           </div>
           <div className="col-sm-4">
-            <FilterUriPrefix value={config.filter.views.uri_prefix} onChange={updateFilterUriPrefix}
+            <FilterUriPrefix value={get(config, 'filter.views.uri_prefix', '')} onChange={updateFilterUriPrefix}
                              options={getUriPrefixes(views)} />
           </div>
           {
-            config.settings.multisite && <div className="col-sm-4">
-              <FilterSite value={config.filter.views.sites} onChange={updateFilterSite}
-                          options={config.sites} />
-            </div>
+            config.settings.multisite && <>
+              <div className="col-sm-2">
+                <FilterSite value={get(config, 'filter.sites', '')} onChange={updateFilterSite}
+                            options={config.sites} />
+              </div>
+              <div className="col-sm-2">
+                <FilterSite value={get(config, 'filter.editors', '')} onChange={updateFilterEditor}
+                            options={config.sites} allLabel={gettext('All editors')} />
+              </div>
+            </>
           }
         </div>
       </div>
@@ -48,8 +56,9 @@ const Views = ({ config, views, configActions, elementActions }) => {
       <ul className="list-group">
       {
         views.map((view, index) => (
-          <View key={index} config={config} view={view} elementActions={elementActions}
-                filter={config.filter.views} />
+          <View key={index} config={config} view={view}
+                configActions={configActions} elementActions={elementActions}
+                filter="views" filterSites={true} filterEditors={true} />
         ))
       }
       </ul>
diff --git a/rdmo/management/assets/js/components/main/Import.js b/rdmo/management/assets/js/components/main/Import.js
index 0bdc98f8cd..6f93e80fdc 100644
--- a/rdmo/management/assets/js/components/main/Import.js
+++ b/rdmo/management/assets/js/components/main/Import.js
@@ -37,7 +37,14 @@ const Import = ({ config, imports, importActions }) => {
                   <code className={codeClass[element.model]}>{element.uri}</code>
                   {element.created && <span className="text-success">{' '}{gettext('created')}</span>}
                   {element.updated && <span className="text-info">{' '}{gettext('updated')}</span>}
-                  {!isEmpty(element.errors) && <span className="text-danger">{' '}{gettext('could not be imported')}</span>}
+                  {
+                    !isEmpty(element.errors) && !(element.created || element.updated) &&
+                    <span className="text-danger">{' '}{gettext('could not be imported')}</span>
+                  }
+                  {
+                    !isEmpty(element.errors) && (element.created || element.updated) &&
+                    <>{', '}<span className="text-danger">{gettext('but could not be added to parent element')}</span></>
+                  }
                   {'.'}
                 </p>
                 {element.warnings.map(message => <p key={uniqueId()} className="text-warning">{message}</p>)}
diff --git a/rdmo/management/assets/js/components/nested/NestedAttribute.js b/rdmo/management/assets/js/components/nested/NestedAttribute.js
index 2f0e9bf88f..c80a43c9d1 100644
--- a/rdmo/management/assets/js/components/nested/NestedAttribute.js
+++ b/rdmo/management/assets/js/components/nested/NestedAttribute.js
@@ -1,5 +1,6 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
@@ -21,17 +22,17 @@ const NestedAttribute = ({ config, attribute, configActions, elementActions }) =
             <BackButton />
           </div>
           <Attribute config={config} attribute={attribute}
-                     elementActions={elementActions} display="plain" />
+                     configActions={configActions} elementActions={elementActions} display="plain" />
         </div>
 
         <div className="panel-body">
           <div className="row">
             <div className="col-sm-8">
-              <FilterString value={config.filter.attribute.search} onChange={updateFilterString}
+              <FilterString value={get(config, 'filter.attribute.search', '')} onChange={updateFilterString}
                             placeholder={gettext('Filter attributes')} />
             </div>
             <div className="col-sm-4">
-              <FilterUriPrefix value={config.filter.attribute.uri_prefix} onChange={updateFilterUriPrefix}
+              <FilterUriPrefix value={get(config, 'filter.attribute.uri_prefix', '')} onChange={updateFilterUriPrefix}
                                options={getUriPrefixes(attribute.elements)} />
             </div>
           </div>
@@ -40,8 +41,9 @@ const NestedAttribute = ({ config, attribute, configActions, elementActions }) =
 
       {
         attribute.elements.map((attribute, index) => (
-          <Attribute key={index} config={config} attribute={attribute} elementActions={elementActions}
-                     display="nested" filter={config.filter.attribute} indent={1} />
+          <Attribute key={index} config={config} attribute={attribute}
+                     configActions={configActions} elementActions={elementActions}
+                     display="nested" filter="attribute" indent={1} />
         ))
       }
     </>
diff --git a/rdmo/management/assets/js/components/nested/NestedCatalog.js b/rdmo/management/assets/js/components/nested/NestedCatalog.js
index 513724b8f7..3b2809f909 100644
--- a/rdmo/management/assets/js/components/nested/NestedCatalog.js
+++ b/rdmo/management/assets/js/components/nested/NestedCatalog.js
@@ -1,9 +1,11 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 import isEmpty from 'lodash/isEmpty'
 
-import { getUriPrefixes } from '../../utils/filter'
+import Link from 'rdmo/core/assets/js/components/Link'
 
+import { getUriPrefixes } from '../../utils/filter'
 import { FilterString, FilterUriPrefix } from '../common/Filter'
 import { Checkbox } from '../common/Checkboxes'
 import { BackButton } from '../common/Buttons'
@@ -17,10 +19,9 @@ const NestedCatalog = ({ config, catalog, configActions, elementActions }) => {
   const updateFilterString = (value) => configActions.updateConfig('filter.catalog.search', value)
   const updateFilterUriPrefix = (value) => configActions.updateConfig('filter.catalog.uri_prefix', value)
 
-  const updateDisplaySections = (value) => configActions.updateConfig('display.elements.sections', value)
-  const updateDisplayPages = (value) => configActions.updateConfig('display.elements.pages', value)
-  const updateDisplayQuestionSets = (value) => configActions.updateConfig('display.elements.questionsets', value)
-  const updateDisplayQuestions = (value) => configActions.updateConfig('display.elements.questions', value)
+  const toggleSections = () => configActions.toggleDescandants(catalog, 'sections')
+  const togglePages = () => configActions.toggleDescandants(catalog, 'pages')
+  const toggleQuestionSets = () => configActions.toggleDescandants(catalog, 'questionsets')
 
   const updateDisplayCatalogURI = (value) => configActions.updateConfig('display.uri.catalogs', value)
   const updateDisplaySectionsURI = (value) => configActions.updateConfig('display.uri.sections', value)
@@ -39,45 +40,44 @@ const NestedCatalog = ({ config, catalog, configActions, elementActions }) => {
             <BackButton />
           </div>
           <Catalog config={config} catalog={catalog}
-                   elementActions={elementActions} display="plain" />
+                   configActions={configActions} elementActions={elementActions} display="plain" />
         </div>
 
         <div className="panel-body">
           <div className="row">
             <div className="col-sm-8">
-              <FilterString value={config.filter.catalog.search} onChange={updateFilterString}
+              <FilterString value={get(config, 'filter.catalog.search', '')} onChange={updateFilterString}
                             placeholder={gettext('Filter catalogs')} />
             </div>
             <div className="col-sm-4">
-              <FilterUriPrefix value={config.filter.catalog.uri_prefix} onChange={updateFilterUriPrefix}
+              <FilterUriPrefix value={get(config, 'filter.catalog.uri_prefix', '')} onChange={updateFilterUriPrefix}
                                options={getUriPrefixes(catalog.elements)} />
             </div>
           </div>
-          <div className="checkboxes">
-            <span className="mr-10">{gettext('Show elements:')}</span>
-            <Checkbox label={gettext('Sections')} value={config.display.elements.sections} onChange={updateDisplaySections} />
-            <Checkbox label={gettext('Pages')} value={config.display.elements.pages} onChange={updateDisplayPages} />
-            <Checkbox label={gettext('Question sets')} value={config.display.elements.questionsets} onChange={updateDisplayQuestionSets} />
-            <Checkbox label={gettext('Questions')} value={config.display.elements.questions} onChange={updateDisplayQuestions} />
+          <div className="mt-10">
+            <span className="mr-10">{gettext('Toggle elements:')}</span>
+            <Link className="mr-10" onClick={toggleSections}>{gettext('Sections')}</Link>
+            <Link className="mr-10" onClick={togglePages}>{gettext('Pages')}</Link>
+            <Link className="mr-10" onClick={toggleQuestionSets}>{gettext('Question sets')}</Link>
           </div>
           <div className="checkboxes">
             <span className="mr-10">{gettext('Show URIs:')}</span>
             <Checkbox label={<code className="code-questions">{gettext('Catalogs')}</code>}
-                      value={config.display.uri.catalogs} onChange={updateDisplayCatalogURI} />
+                      value={get(config, 'display.uri.catalogs', true)} onChange={updateDisplayCatalogURI} />
             <Checkbox label={<code className="code-questions">{gettext('Sections')}</code>}
-                      value={config.display.uri.sections} onChange={updateDisplaySectionsURI} />
+                      value={get(config, 'display.uri.sections', true)} onChange={updateDisplaySectionsURI} />
             <Checkbox label={<code className="code-questions">{gettext('Pages')}</code>}
-                      value={config.display.uri.pages} onChange={updateDisplayPagesURI} />
+                      value={get(config, 'display.uri.pages', true)} onChange={updateDisplayPagesURI} />
             <Checkbox label={<code className="code-questions">{gettext('Question sets')}</code>}
-                      value={config.display.uri.questionsets} onChange={updateDisplayQuestionSetsURI} />
-            <Checkbox label={<code className="code-questions">{gettext('Questions')}</code>}
-                      value={config.display.uri.questions} onChange={updateDisplayQuestionsURI} />
+                      value={get(config, 'display.uri.questionsets', true)} onChange={updateDisplayQuestionSetsURI} />
+            <Checkbox label={<code className="code-questions'">{gettext('Questions')}</code>}
+                      value={get(config, 'display.uri.questions', true)} onChange={updateDisplayQuestionsURI} />
             <Checkbox label={<code className="code-domain">{gettext('Attributes')}</code>}
-                      value={config.display.uri.attributes} onChange={updateDisplayAttributesURI} />
+                      value={get(config, 'display.uri.attributes', true)} onChange={updateDisplayAttributesURI} />
             <Checkbox label={<code className="code-conditions">{gettext('Conditions')}</code>}
-                      value={config.display.uri.conditions} onChange={updateDisplayConditionsURI} />
+                      value={get(config, 'display.uri.conditions', true)} onChange={updateDisplayConditionsURI} />
             <Checkbox label={<code className="code-options">{gettext('Option sets')}</code>}
-                      value={config.display.uri.optionsets} onChange={updateDisplayOptionSetURI} />
+                      value={get(config, 'display.uri.optionsets', true)} onChange={updateDisplayOptionSetURI} />
           </div>
         </div>
       </div>
@@ -87,8 +87,9 @@ const NestedCatalog = ({ config, catalog, configActions, elementActions }) => {
       }
       {
         catalog.elements.map((section, index) => (
-          <Section key={index} config={config} section={section} elementActions={elementActions}
-                   display="nested" filter={config.filter.catalog} indent={0} />
+          <Section key={index} config={config} section={section}
+                   configActions={configActions} elementActions={elementActions}
+                   display="nested" filter="catalog" indent={0} />
         ))
       }
     </>
diff --git a/rdmo/management/assets/js/components/nested/NestedOptionSet.js b/rdmo/management/assets/js/components/nested/NestedOptionSet.js
index d74723df65..80f43a9580 100644
--- a/rdmo/management/assets/js/components/nested/NestedOptionSet.js
+++ b/rdmo/management/assets/js/components/nested/NestedOptionSet.js
@@ -1,5 +1,6 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 
 import { getUriPrefixes } from '../../utils/filter'
 
@@ -25,32 +26,33 @@ const NestedOptionSet = ({ config, optionset, configActions, elementActions }) =
             <BackButton />
           </div>
           <OptionSet config={config} optionset={optionset}
-                     elementActions={elementActions} display="plain" />
+                     configActions={configActions} elementActions={elementActions} display="plain" />
         </div>
 
         <div className="panel-body">
           <div className="row">
             <div className="col-sm-8">
-              <FilterString value={config.filter.optionset.search} onChange={updateFilterString}
+              <FilterString value={get(config, 'filter.optionset.search', '')} onChange={updateFilterString}
                             placeholder={gettext('Filter optionsets')} />
             </div>
             <div className="col-sm-4">
-              <FilterUriPrefix value={config.filter.optionset.uri_prefix} onChange={updateFilterUriPrefix}
+              <FilterUriPrefix value={get(config, 'filter.optionset.uri_prefix', '')} onChange={updateFilterUriPrefix}
                                options={getUriPrefixes(optionset.elements)} />
             </div>
           </div>
           <div className="checkboxes">
             <span className="mr-10">{gettext('Show URIs:')}</span>
             <Checkbox label={<code className="code-options">{gettext('Options')}</code>}
-                      value={config.display.uri.options} onChange={updateDisplayURI} />
+                      value={get(config, 'display.uri.options', true)} onChange={updateDisplayURI} />
           </div>
         </div>
       </div>
 
       {
         optionset.elements.map((option, index) => (
-          <Option key={index} config={config} option={option} elementActions={elementActions}
-                  display="nested" filter={config.filter.optionset} indent={1} />
+          <Option key={index} config={config} option={option}
+                  configActions={configActions} elementActions={elementActions}
+                  display="nested" filter="optionset" indent={1} />
         ))
       }
     </>
diff --git a/rdmo/management/assets/js/components/nested/NestedPage.js b/rdmo/management/assets/js/components/nested/NestedPage.js
index 270a4dbf1a..c19a212b8f 100644
--- a/rdmo/management/assets/js/components/nested/NestedPage.js
+++ b/rdmo/management/assets/js/components/nested/NestedPage.js
@@ -1,7 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 import isEmpty from 'lodash/isEmpty'
 
+import Link from 'rdmo/core/assets/js/components/Link'
+
 import { getUriPrefixes } from '../../utils/filter'
 
 import { FilterString, FilterUriPrefix } from '../common/Filter'
@@ -18,9 +21,9 @@ const NestedPage = ({ config, page, configActions, elementActions }) => {
   const updateFilterString = (uri) => configActions.updateConfig('filter.page.search', uri)
   const updateFilterUriPrefix = (uriPrefix) => configActions.updateConfig('filter.page.uri_prefix', uriPrefix)
 
+  const toggleQuestionSets = () => configActions.toggleDescandants(page, 'questionsets')
+
   const updateDisplayPagesURI = (value) => configActions.updateConfig('display.uri.pages', value)
-  const updateDisplayQuestionSets = (value) => configActions.updateConfig('display.elements.questionsets', value)
-  const updateDisplayQuestions = (value) => configActions.updateConfig('display.elements.questions', value)
   const updateDisplayQuestionSetsURI = (value) => configActions.updateConfig('display.uri.questionsets', value)
   const updateDisplayQuestionsURI = (value) => configActions.updateConfig('display.uri.questions', value)
   const updateDisplayAttributesURI = (value) => configActions.updateConfig('display.uri.attributes', value)
@@ -35,39 +38,38 @@ const NestedPage = ({ config, page, configActions, elementActions }) => {
             <BackButton />
           </div>
           <Page config={config} page={page}
-                elementActions={elementActions} display="plain" />
+                configActions={configActions} elementActions={elementActions} display="plain" />
         </div>
 
         <div className="panel-body">
           <div className="row">
             <div className="col-sm-8">
-              <FilterString value={config.filter.page.search} onChange={updateFilterString}
+              <FilterString value={get(config, 'filter.page.search', '')} onChange={updateFilterString}
                             placeholder={gettext('Filter pages')} />
             </div>
             <div className="col-sm-4">
-              <FilterUriPrefix value={config.filter.page.uri_prefix} onChange={updateFilterUriPrefix}
+              <FilterUriPrefix value={get(config, 'filter.page.uri_prefix', '')} onChange={updateFilterUriPrefix}
                                options={getUriPrefixes(page.elements)} />
             </div>
           </div>
           <div className="checkboxes">
             <span className="mr-10">{gettext('Show elements:')}</span>
-            <Checkbox label={gettext('Question sets')} value={config.display.elements.questionsets} onChange={updateDisplayQuestionSets} />
-            <Checkbox label={gettext('Questions')} value={config.display.elements.questions} onChange={updateDisplayQuestions} />
+            <Link className="mr-10" onClick={toggleQuestionSets}>{gettext('Question sets')}</Link>
           </div>
           <div className="checkboxes">
             <span className="mr-10">{gettext('Show URIs:')}</span>
             <Checkbox label={<code className="code-questions">{gettext('Pages')}</code>}
-                      value={config.display.uri.pages} onChange={updateDisplayPagesURI} />
+                      value={get(config, 'config.display.uri.pages', true)} onChange={updateDisplayPagesURI} />
             <Checkbox label={<code className="code-questions">{gettext('Question sets')}</code>}
-                      value={config.display.uri.questionsets} onChange={updateDisplayQuestionSetsURI} />
+                      value={get(config, 'config.display.uri.questionsets', true)} onChange={updateDisplayQuestionSetsURI} />
             <Checkbox label={<code className="code-questions">{gettext('Questions')}</code>}
-                      value={config.display.uri.questions} onChange={updateDisplayQuestionsURI} />
+                      value={get(config, 'config.display.uri.questions', true)} onChange={updateDisplayQuestionsURI} />
             <Checkbox label={<code className="code-domain">{gettext('Attributes')}</code>}
-                      value={config.display.uri.attributes} onChange={updateDisplayAttributesURI} />
+                      value={get(config, 'config.display.uri.attributes', true)} onChange={updateDisplayAttributesURI} />
             <Checkbox label={<code className="code-conditions">{gettext('Conditions')}</code>}
-                      value={config.display.uri.conditions} onChange={updateDisplayConditionsURI} />
+                      value={get(config, 'config.display.uri.conditions', true)} onChange={updateDisplayConditionsURI} />
             <Checkbox label={<code className="code-options">{gettext('Option sets')}</code>}
-                      value={config.display.uri.optionsets} onChange={updateDisplayOptionSetURI} />
+                      value={get(config, 'config.display.uri.optionsets', true)} onChange={updateDisplayOptionSetURI} />
           </div>
         </div>
       </div>
@@ -78,11 +80,13 @@ const NestedPage = ({ config, page, configActions, elementActions }) => {
       {
         page.elements.map((element, index) => {
           if (element.model == 'questions.questionset') {
-            return <QuestionSet key={index} config={config} questionset={element} elementActions={elementActions}
-                                display="nested" filter={config.filter.page} indent={1} />
+            return <QuestionSet key={index} config={config} questionset={element}
+                                configActions={configActions} elementActions={elementActions}
+                                display="nested" filter="page" indent={1} />
           } else {
-            return <Question key={index} config={config} question={element} elementActions={elementActions}
-                             display="nested" filter={config.filter.page} indent={1} />
+            return <Question key={index} config={config} question={element}
+                             configActions={configActions} elementActions={elementActions}
+                             display="nested" filter="page" indent={1} />
           }
         })
       }
diff --git a/rdmo/management/assets/js/components/nested/NestedQuestionSet.js b/rdmo/management/assets/js/components/nested/NestedQuestionSet.js
index 84dc1fd9a3..42a42b7709 100644
--- a/rdmo/management/assets/js/components/nested/NestedQuestionSet.js
+++ b/rdmo/management/assets/js/components/nested/NestedQuestionSet.js
@@ -1,7 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 import isEmpty from 'lodash/isEmpty'
 
+import Link from 'rdmo/core/assets/js/components/Link'
+
 import { getUriPrefixes } from '../../utils/filter'
 
 import { FilterString, FilterUriPrefix } from '../common/Filter'
@@ -17,8 +20,7 @@ const NestedQuestionSet = ({ config, questionset, configActions, elementActions
   const updateFilterString = (uri) => configActions.updateConfig('filter.questionset.search', uri)
   const updateFilterUriPrefix = (uriPrefix) => configActions.updateConfig('filter.questionset.uri_prefix', uriPrefix)
 
-  const updateDisplayQuestionSets = (value) => configActions.updateConfig('display.elements.questionsets', value)
-  const updateDisplayQuestions = (value) => configActions.updateConfig('display.elements.questions', value)
+  const toggleQuestionSets = () => configActions.toggleDescandants(questionset, 'questionsets')
 
   const updateDisplayQuestionSetsURI = (value) => configActions.updateConfig('display.uri.questionsets', value)
   const updateDisplayQuestionsURI = (value) => configActions.updateConfig('display.uri.questions', value)
@@ -34,37 +36,36 @@ const NestedQuestionSet = ({ config, questionset, configActions, elementActions
             <BackButton />
           </div>
           <QuestionSet config={config} questionset={questionset}
-                       elementActions={elementActions} display="plain" />
+                       configActions={configActions} elementActions={elementActions} display="plain" />
         </div>
 
         <div className="panel-body">
           <div className="row">
             <div className="col-sm-8">
-              <FilterString value={config.filter.questionset.search} onChange={updateFilterString}
+              <FilterString value={get(config, 'filter.questionset.search', '')} onChange={updateFilterString}
                             placeholder={gettext('Filter question sets')} />
             </div>
             <div className="col-sm-4">
-              <FilterUriPrefix value={config.filter.questionset.uri_prefix} onChange={updateFilterUriPrefix}
+              <FilterUriPrefix value={get(config, 'filter.questionset.uri_prefix', '')} onChange={updateFilterUriPrefix}
                                options={getUriPrefixes(questionset.elements)} />
             </div>
           </div>
           <div className="checkboxes">
             <span className="mr-10">{gettext('Show elements:')}</span>
-            <Checkbox label={gettext('Question sets')} value={config.display.elements.questionsets} onChange={updateDisplayQuestionSets} />
-            <Checkbox label={gettext('Questions')} value={config.display.elements.questions} onChange={updateDisplayQuestions} />
+            <Link className="mr-10" onClick={toggleQuestionSets}>{gettext('Question sets')}</Link>
           </div>
           <div className="checkboxes">
             <span className="mr-10">{gettext('Show URIs:')}</span>
             <Checkbox label={<code className="code-questions">{gettext('Question sets')}</code>}
-                      value={config.display.uri.questionsets} onChange={updateDisplayQuestionSetsURI} />
+                      value={get(config, 'display.uri.questionsets', true)} onChange={updateDisplayQuestionSetsURI} />
             <Checkbox label={<code className="code-questions">{gettext('Questions')}</code>}
-                      value={config.display.uri.questions} onChange={updateDisplayQuestionsURI} />
+                      value={get(config, 'display.uri.questions', true)} onChange={updateDisplayQuestionsURI} />
             <Checkbox label={<code className="code-domain">{gettext('Attributes')}</code>}
-                      value={config.display.uri.attributes} onChange={updateDisplayAttributesURI} />
+                      value={get(config, 'display.uri.attributes', true)} onChange={updateDisplayAttributesURI} />
             <Checkbox label={<code className="code-conditions">{gettext('Conditions')}</code>}
-                      value={config.display.uri.conditions} onChange={updateDisplayConditionsURI} />
+                      value={get(config, 'display.uri.conditions', true)} onChange={updateDisplayConditionsURI} />
             <Checkbox label={<code className="code-options">{gettext('Option sets')}</code>}
-                      value={config.display.uri.optionsets} onChange={updateDisplayOptionSetURI} />
+                      value={get(config, 'display.uri.optionsets', true)} onChange={updateDisplayOptionSetURI} />
           </div>
         </div>
       </div>
@@ -75,11 +76,13 @@ const NestedQuestionSet = ({ config, questionset, configActions, elementActions
       {
         questionset.elements.map((element, index) => {
           if (element.model == 'questions.questionset') {
-            return <QuestionSet key={index} config={config} questionset={element} elementActions={elementActions}
-                                display="nested" filter={config.filter.questionset} indent={1} />
+            return <QuestionSet key={index} config={config} questionset={element}
+                                configActions={configActions} elementActions={elementActions}
+                                display="nested" filter="questionset" indent={1} />
           } else {
-            return <Question key={index} config={config} question={element} elementActions={elementActions}
-                             display="nested" filter={config.filter.questionset} indent={1} />
+            return <Question key={index} config={config} question={element}
+                             configActions={configActions} elementActions={elementActions}
+                             display="nested" filter="questionset" indent={1} />
           }
         })
       }
diff --git a/rdmo/management/assets/js/components/nested/NestedSection.js b/rdmo/management/assets/js/components/nested/NestedSection.js
index 41ca0c7823..3a852603db 100644
--- a/rdmo/management/assets/js/components/nested/NestedSection.js
+++ b/rdmo/management/assets/js/components/nested/NestedSection.js
@@ -1,7 +1,10 @@
 import React from 'react'
 import PropTypes from 'prop-types'
+import get from 'lodash/get'
 import isEmpty from 'lodash/isEmpty'
 
+import Link from 'rdmo/core/assets/js/components/Link'
+
 import { getUriPrefixes } from '../../utils/filter'
 
 import { FilterString, FilterUriPrefix } from '../common/Filter'
@@ -17,9 +20,8 @@ const NestedCatalog = ({ config, section, configActions, elementActions }) => {
   const updateFilterString = (uri) => configActions.updateConfig('filter.section.search', uri)
   const updateFilterUriPrefix = (uriPrefix) => configActions.updateConfig('filter.section.uri_prefix', uriPrefix)
 
-  const updateDisplayPages = (value) => configActions.updateConfig('display.elements.pages', value)
-  const updateDisplayQuestionSets = (value) => configActions.updateConfig('display.elements.questionsets', value)
-  const updateDisplayQuestions = (value) => configActions.updateConfig('display.elements.questions', value)
+  const togglePages = () => configActions.toggleDescandants(section, 'pages')
+  const toggleQuestionSets = () => configActions.toggleDescandants(section, 'questionsets')
 
   const updateDisplaySectionURI = (value) => configActions.updateConfig('display.uri.sections', value)
   const updateDisplayPagesURI = (value) => configActions.updateConfig('display.uri.pages', value)
@@ -37,42 +39,41 @@ const NestedCatalog = ({ config, section, configActions, elementActions }) => {
             <BackButton />
           </div>
           <Section config={config} section={section}
-                   elementActions={elementActions} display="plain" />
+                   configActions={configActions} elementActions={elementActions} display="plain" />
         </div>
 
         <div className="panel-body">
           <div className="row">
             <div className="col-sm-8">
-              <FilterString value={config.filter.section.search} onChange={updateFilterString}
+              <FilterString value={get(config, 'filter.section.search', '')} onChange={updateFilterString}
                             placeholder={gettext('Filter sections')} />
             </div>
             <div className="col-sm-4">
-              <FilterUriPrefix value={config.filter.section.uri_prefix} onChange={updateFilterUriPrefix}
+              <FilterUriPrefix value={get(config, 'filter.section.uri_prefix', '')} onChange={updateFilterUriPrefix}
                                options={getUriPrefixes(section.elements)} />
             </div>
           </div>
           <div className="checkboxes">
             <span className="mr-10">{gettext('Show elements:')}</span>
-            <Checkbox label={gettext('Pages')} value={config.display.elements.pages} onChange={updateDisplayPages} />
-            <Checkbox label={gettext('Question sets')} value={config.display.elements.questionsets} onChange={updateDisplayQuestionSets} />
-            <Checkbox label={gettext('Questions')} value={config.display.elements.questions} onChange={updateDisplayQuestions} />
+            <Link className="mr-10" onClick={togglePages}>{gettext('Pages')}</Link>
+            <Link className="mr-10" onClick={toggleQuestionSets}>{gettext('Question sets')}</Link>
           </div>
           <div className="checkboxes">
             <span className="mr-10">{gettext('Show URIs:')}</span>
             <Checkbox label={<code className="code-questions">{gettext('Sections')}</code>}
-                      value={config.display.uri.sections} onChange={updateDisplaySectionURI} />
+                      value={get(config, 'config.display.uri.sections', true)} onChange={updateDisplaySectionURI} />
             <Checkbox label={<code className="code-questions">{gettext('Pages')}</code>}
-                      value={config.display.uri.pages} onChange={updateDisplayPagesURI} />
+                      value={get(config, 'config.display.uri.pages', true)} onChange={updateDisplayPagesURI} />
             <Checkbox label={<code className="code-questions">{gettext('Question sets')}</code>}
-                      value={config.display.uri.questionsets} onChange={updateDisplayQuestionSetsURI} />
+                      value={get(config, 'config.display.uri.questionsets', true)} onChange={updateDisplayQuestionSetsURI} />
             <Checkbox label={<code className="code-questions">{gettext('Questions')}</code>}
-                      value={config.display.uri.questions} onChange={updateDisplayQuestionsURI} />
+                      value={get(config, 'config.display.uri.questions', true)} onChange={updateDisplayQuestionsURI} />
             <Checkbox label={<code className="code-domain">{gettext('Attributes')}</code>}
-                      value={config.display.uri.attributes} onChange={updateDisplayAttributesURI} />
+                      value={get(config, 'config.display.uri.attributes', true)} onChange={updateDisplayAttributesURI} />
             <Checkbox label={<code className="code-conditions">{gettext('Conditions')}</code>}
-                      value={config.display.uri.conditions} onChange={updateDisplayConditionsURI} />
+                      value={get(config, 'config.display.uri.conditions', true)} onChange={updateDisplayConditionsURI} />
             <Checkbox label={<code className="code-options">{gettext('Option sets')}</code>}
-                      value={config.display.uri.optionsets} onChange={updateDisplayOptionSetURI} />
+                      value={get(config, 'config.display.uri.optionsets', true)} onChange={updateDisplayOptionSetURI} />
           </div>
         </div>
       </div>
@@ -82,8 +83,9 @@ const NestedCatalog = ({ config, section, configActions, elementActions }) => {
       }
       {
         section.elements.map((page, index) => (
-          <Page key={index} config={config} page={page} elementActions={elementActions}
-                display="nested" filter={config.filter.section} indent={1} />
+          <Page key={index} config={config} page={page}
+                configActions={configActions} elementActions={elementActions}
+                display="nested" filter="section" indent={1} />
         ))
       }
     </>
diff --git a/rdmo/management/assets/js/constants/config.js b/rdmo/management/assets/js/constants/config.js
deleted file mode 100644
index 3e21007bc4..0000000000
--- a/rdmo/management/assets/js/constants/config.js
+++ /dev/null
@@ -1,61 +0,0 @@
-export const lsKeys = {
-  'filter.catalogs.search': '',
-  'filter.catalogs.uri_prefix': '',
-  'filter.catalogs.sites': '',
-  'filter.sections.search': '',
-  'filter.sections.uri_prefix': '',
-  'filter.pages.search': '',
-  'filter.pages.uri_prefix': '',
-  'filter.questionsets.search': '',
-  'filter.questionsets.uri_prefix': '',
-  'filter.questions.search': '',
-  'filter.questions.uri_prefix': '',
-  'filter.attributes.search': '',
-  'filter.attributes.uri_prefix': '',
-  'filter.optionsets.search': '',
-  'filter.optionsets.uri_prefix': '',
-  'filter.options.search': '',
-  'filter.options.uri_prefix': '',
-  'filter.conditions.search': '',
-  'filter.conditions.uri_prefix': '',
-  'filter.tasks.search': '',
-  'filter.tasks.uri_prefix': '',
-  'filter.tasks.sites': '',
-  'filter.views.search': '',
-  'filter.views.uri_prefix': '',
-  'filter.views.sites': '',
-  'filter.catalog.search': '',
-  'filter.catalog.uri_prefix': '',
-  'filter.section.search': '',
-  'filter.section.uri_prefix': '',
-  'filter.page.search': '',
-  'filter.page.uri_prefix': '',
-  'filter.questionset.search': '',
-  'filter.questionset.uri_prefix': '',
-  'filter.attribute.search': '',
-  'filter.attribute.uri_prefix': '',
-  'filter.optionset.search': '',
-  'filter.optionset.uri_prefix': '',
-  'display.elements.catalogs': true,
-  'display.elements.sections': true,
-  'display.elements.pages': true,
-  'display.elements.questionsets': true,
-  'display.elements.questions': true,
-  'display.elements.attributes': true,
-  'display.elements.optionsets': true,
-  'display.elements.options': true,
-  'display.elements.conditions': true,
-  'display.elements.tasks': true,
-  'display.elements.views': true,
-  'display.uri.catalogs': true,
-  'display.uri.sections': true,
-  'display.uri.pages': true,
-  'display.uri.questionsets': true,
-  'display.uri.questions': true,
-  'display.uri.attributes': true,
-  'display.uri.optionsets': true,
-  'display.uri.options': true,
-  'display.uri.conditions': true,
-  'display.uri.tasks': true,
-  'display.uri.views': true
-}
diff --git a/rdmo/management/assets/js/factories/ViewsFactory.js b/rdmo/management/assets/js/factories/ViewsFactory.js
index 6eaee10365..cf91d27a6f 100644
--- a/rdmo/management/assets/js/factories/ViewsFactory.js
+++ b/rdmo/management/assets/js/factories/ViewsFactory.js
@@ -3,7 +3,8 @@ class ViewsFactory {
   static createView(config) {
     return {
       model: 'views.view',
-      uri_prefix: config.settings.default_uri_prefix
+      uri_prefix: config.settings.default_uri_prefix,
+      template: '{% load view_tags %}\n'
     }
   }
 
diff --git a/rdmo/management/assets/js/hooks/useScrollEffect.js b/rdmo/management/assets/js/hooks/useScrollEffect.js
index 62648246df..87fad8e611 100644
--- a/rdmo/management/assets/js/hooks/useScrollEffect.js
+++ b/rdmo/management/assets/js/hooks/useScrollEffect.js
@@ -1,17 +1,16 @@
 import { useEffect } from 'react'
-import ls from 'local-storage'
 import isNil from 'lodash/isNil'
 
 const useScrollEffect = (elementType, elementId, elementAction) => {
   useEffect(() => {
-    let lsKey = `rdmo.management.config.scroll.${elementType}`
+    let lsKey = `rdmo.management.scroll.${elementType}`
     if (!isNil(elementId)) {
-      lsKey += `_${elementId}`
+      lsKey += `.${elementId}`
     }
     if (!isNil(elementAction)) {
-      lsKey += `_${elementAction}`
+      lsKey += `.${elementAction}`
     }
-    const lsValue = ls.get(lsKey, 0)
+    const lsValue = localStorage.getItem(lsKey, 0)
 
     // scroll to the right position
     setTimeout(() => window.scrollTo(0, lsValue), 0)
@@ -19,7 +18,7 @@ const useScrollEffect = (elementType, elementId, elementAction) => {
     // add a event handler to store the scroll position
     const handleScroll = () => {
       const lsValue = window.pageYOffset
-      ls.set(lsKey, lsValue)
+      localStorage.setItem(lsKey, lsValue)
     }
     window.addEventListener('scroll', handleScroll)
     return () => {
diff --git a/rdmo/management/assets/js/reducers/configReducer.js b/rdmo/management/assets/js/reducers/configReducer.js
index d60ef9ec99..12f8196864 100644
--- a/rdmo/management/assets/js/reducers/configReducer.js
+++ b/rdmo/management/assets/js/reducers/configReducer.js
@@ -1,8 +1,5 @@
-import ls from 'local-storage'
 import set from 'lodash/set'
 
-import { lsKeys } from '../constants/config'
-
 const initialState = {
   baseUrl: '/management/',
   settings: {},
@@ -16,18 +13,12 @@ export default function configReducer(state = initialState, action) {
     case 'config/updateConfig':
       newState = {...state}
 
-      // set the value using lodash's set
       set(newState, action.path, action.value)
+      localStorage.setItem(`rdmo.management.config.${action.path}`, action.value)
 
-      // store the new value in the local storage
-      if (Object.keys(lsKeys).includes(action.path)) {
-        ls.set(`rdmo.management.config.${action.path}`, action.value)
-      }
-
-      // return the new state
       return newState
     case 'config/fetchConfigSuccess':
-      return {...state, ...action.config}
+      return {...state, ...action.config, currentSite: action.config.sites.find(site => site.current)}
     case 'elements/fetchConfigInit':
     case 'elements/fetchElementsInit':
     case 'elements/fetchElementInit':
diff --git a/rdmo/management/assets/js/reducers/elementsReducer.js b/rdmo/management/assets/js/reducers/elementsReducer.js
index d30ecdb328..02082a6f25 100644
--- a/rdmo/management/assets/js/reducers/elementsReducer.js
+++ b/rdmo/management/assets/js/reducers/elementsReducer.js
@@ -49,12 +49,6 @@ export default function elementsReducer(state = initialState, action) {
         errors: {}
       }
     case 'elements/fetchElementSuccess':
-      // remove the id when copying
-      if (state.elementAction == 'copy') {
-        action.elements.element.id = null
-        action.elements.element.read_only = false
-      }
-
       return {...state, ...action.elements}
     case 'elements/fetchElementError':
       return {...state, errors: action.error.errors}
@@ -102,7 +96,6 @@ export default function elementsReducer(state = initialState, action) {
     case 'elements/createElementError':
       return {...state, errors: action.error.errors}
 
-
     // delete element
     case 'elements/deleteElementInit':
       return state
diff --git a/rdmo/management/assets/js/store/configureStore.js b/rdmo/management/assets/js/store/configureStore.js
index 2a9713056c..e4720e7d4c 100644
--- a/rdmo/management/assets/js/store/configureStore.js
+++ b/rdmo/management/assets/js/store/configureStore.js
@@ -1,10 +1,8 @@
 import { applyMiddleware, createStore } from 'redux'
 import thunk from 'redux-thunk'
-import ls from 'local-storage'
 import isNil from 'lodash/isNil'
 
 import { parseLocation } from '../utils/location'
-import { lsKeys } from '../constants/config'
 
 import rootReducer from '../reducers/rootReducer'
 
@@ -29,19 +27,20 @@ export default function configureStore() {
 
   // load: restore the config from the local storage
   const updateConfigFromLocalStorage = () => {
-    Object.entries(lsKeys).forEach(([path, defaultValue]) => {
-      let value = ls.get(`rdmo.management.config.${path}`)
-      if (isNil(value)) {
-        switch(defaultValue) {
-          case 'true':
-            value = false
-            break
-          case 'false':
-            value = false
-            break
-          default:
-            value = defaultValue
-        }
+    const ls = {...localStorage}
+
+    Object.entries(ls).forEach(([lsPath, lsValue]) => {
+      const path = lsPath.replace('rdmo.management.config.', '')
+      let value
+      switch(lsValue) {
+        case 'true':
+          value = true
+          break
+        case 'false':
+          value = false
+          break
+        default:
+          value = lsValue
       }
       store.dispatch(configActions.updateConfig(path, value))
     })
diff --git a/rdmo/management/assets/js/utils/elements.js b/rdmo/management/assets/js/utils/elements.js
index 3d1ab41dfb..01727b19c1 100644
--- a/rdmo/management/assets/js/utils/elements.js
+++ b/rdmo/management/assets/js/utils/elements.js
@@ -1,7 +1,7 @@
 import isNil from 'lodash/isNil'
 import isUndefined from 'lodash/isUndefined'
 
-import { elementModules } from '../constants/elements'
+import { elementTypes, elementModules } from '../constants/elements'
 
 const compareElements = (element1, element2) => {
   return element1.model == element2.model && element1.id == element2.id
@@ -145,6 +145,22 @@ function updateElementElements(element) {
   }
 }
 
+function findDescendants(element, elementType) {
+  if (elementType == elementTypes[element.model]) {
+    return [element]
+  } else if (!isUndefined(element.elements)) {
+    return element.elements.reduce((agg, cur) => {
+      const descendants = findDescendants(cur, elementType)
+      if (!isNil(descendants)) {
+        agg = agg.concat(descendants)
+      }
+      return agg
+    }, [])
+  } else {
+    return null
+  }
+}
+
 const buildUri = (element) => {
   let uri = element.uri_prefix + '/' + elementModules[element.model] + '/'
 
@@ -159,4 +175,4 @@ const buildUri = (element) => {
   return uri
 }
 
-export { compareElements, updateElement, resetElement, canMoveElement, moveElement, buildUri }
+export { compareElements, updateElement, resetElement, canMoveElement, moveElement, findDescendants, buildUri }
diff --git a/rdmo/management/assets/js/utils/filter.js b/rdmo/management/assets/js/utils/filter.js
index a9405f34a6..25ecf7d306 100644
--- a/rdmo/management/assets/js/utils/filter.js
+++ b/rdmo/management/assets/js/utils/filter.js
@@ -1,9 +1,21 @@
 import isEmpty from 'lodash/isEmpty'
 import isUndefined from 'lodash/isUndefined'
 import get from 'lodash/get'
-import isNil from 'lodash/isNil'
 import toNumber from 'lodash/toNumber'
 
+const filterElement = (config, filter, filterSites, filterEditors, element) => {
+  const strings = get(config, `filter.${filter}.search`, '').trim().split(' '),
+        uriPrefix = get(config, `filter.${filter}.uri_prefix`, ''),
+        site = get(config, 'filter.sites', ''),
+        editor = get(config, 'filter.editors', '')
+  return (
+    strings.some(search => filterSearch(search, element)) &&
+    filterUriPrefix(uriPrefix, element) &&
+    (!filterSites || filterSite(site, element)) &&
+    (!filterEditors || filterEditor(editor, element))
+  )
+}
+
 const filterSearch = (search, element) => {
   return (
     isEmpty(search) ||
@@ -21,19 +33,8 @@ const filterSite = (site, element) => {
   return isEmpty(site) || element.sites.includes(toNumber(site))
 }
 
-const filterElement = (filter, element) => {
-  if (isNil(filter)) {
-    return true
-  } else {
-    const strings = get(filter, 'search', '').trim().split(' '),
-          uriPrefix = get(filter, 'uri_prefix', ''),
-          site = get(filter, 'sites', '')
-    return (
-      strings.some(search => filterSearch(search, element)) &&
-      filterUriPrefix(uriPrefix, element) &&
-      filterSite(site, element)
-    )
-  }
+const filterEditor = (editor, element) => {
+  return isEmpty(editor) || element.editors.includes(toNumber(editor))
 }
 
 const getUriPrefixes = (elements) => {
diff --git a/rdmo/management/imports.py b/rdmo/management/imports.py
index 180fb898f5..70a47a4bff 100644
--- a/rdmo/management/imports.py
+++ b/rdmo/management/imports.py
@@ -1,7 +1,6 @@
 from collections import defaultdict
 
 from rdmo.conditions.imports import import_condition
-from rdmo.core.constants import PERMISSIONS
 from rdmo.domain.imports import import_attribute
 from rdmo.options.imports import import_option, import_optionset
 from rdmo.questions.imports import (import_catalog, import_page,
@@ -11,17 +10,7 @@
 from rdmo.views.imports import import_view
 
 
-def check_permissions(elements, user):
-    model_names = set([element.get('model') for element in elements])
-
-    permissions = []
-    for model_name in model_names:
-        permissions += PERMISSIONS[model_name]
-
-    return user.has_perms(permissions)
-
-
-def import_elements(elements, save=True):
+def import_elements(elements, save=True, user=None):
     for element in elements:
         model = element.get('model')
 
@@ -33,37 +22,37 @@ def import_elements(elements, save=True):
         })
 
         if model == 'conditions.condition':
-            import_condition(element, save)
+            import_condition(element, save, user)
 
         elif model == 'domain.attribute':
-            import_attribute(element, save)
+            import_attribute(element, save, user)
 
         elif model == 'options.optionset':
-            import_optionset(element, save)
+            import_optionset(element, save, user)
 
         elif model == 'options.option':
-            import_option(element, save)
+            import_option(element, save, user)
 
         elif model == 'questions.catalog':
-            import_catalog(element, save)
+            import_catalog(element, save, user)
 
         elif model == 'questions.section':
-            import_section(element, save)
+            import_section(element, save, user)
 
         elif model == 'questions.page':
-            import_page(element, save)
+            import_page(element, save, user)
 
         elif model == 'questions.questionset':
-            import_questionset(element, save)
+            import_questionset(element, save, user)
 
         elif model == 'questions.question':
-            import_question(element, save)
+            import_question(element, save, user)
 
         elif model == 'tasks.task':
-            import_task(element, save)
+            import_task(element, save, user)
 
         elif model == 'views.view':
-            import_view(element, save)
+            import_view(element, save, user)
 
         element = filter_warnings(element, elements)
 
diff --git a/rdmo/management/rules.py b/rdmo/management/rules.py
index f48bb0380d..eb158008ed 100644
--- a/rdmo/management/rules.py
+++ b/rdmo/management/rules.py
@@ -1,9 +1,7 @@
 import logging
 
-from django.contrib.sites.models import Site
-
 import rules
-from rules.predicates import is_authenticated
+from rules.predicates import is_authenticated, is_superuser
 
 logger = logging.getLogger(__name__)
 
@@ -15,18 +13,19 @@ def is_editor(user) -> bool:
 
 
 @rules.predicate
-def is_multisite_editor(user) -> bool:
-    ''' Checks if the user is an editor for all the sites '''
+def is_editor_for_current_site(user, site) -> bool:
+    ''' Checks if any editor role exists for the user '''
     if not is_editor(user):
-        return False
-
-    return user.role.editor.count() == Site.objects.count()
+        return False  # if the user is not an editor, return False
+    return user.role.editor.filter(pk=site.pk).exists()
 
 
 @rules.predicate
 def is_element_editor(user, obj) -> bool:
     ''' Checks if the user is an editor for the sites to which this element is editable '''
-    if obj is None:
+
+    if obj.id is None:  # for _add_object permissions
+        # if the element does not exist yet, it can be created by all users with an editor role
         return is_editor(user)
 
     if not obj.editors.exists():
@@ -44,19 +43,16 @@ def is_reviewer(user) -> bool:
 
 
 @rules.predicate
-def is_multisite_reviewer(user) -> bool:
-    ''' Checks if the user is an reviewer for all the sites '''
+def is_reviewer_for_current_site(user, site) -> bool:
+    ''' Checks if any reviewer role exists for the user '''
     if not is_reviewer(user):
-        return False
-
-    return user.role.reviewer.count() == Site.objects.count()
+        return False  # if the user is not an reviewer, return False
+    return user.role.reviewer.filter(pk=site.pk).exists()
 
 
 @rules.predicate
 def is_element_reviewer(user, obj) -> bool:
     ''' Checks if the user is an reviewer for the sites to which this element is editable '''
-    if obj is None:
-        return is_reviewer(user)
 
     # if the element has no editors, it is reviewable by all reviewers
     if not obj.editors.exists():
@@ -67,7 +63,8 @@ def is_element_reviewer(user, obj) -> bool:
 
 
 # Add rules
-rules.add_rule('can_view_management', is_authenticated & ((is_editor | is_reviewer)))
+rules.add_rule('management.can_view_management',
+               is_authenticated & (is_superuser | is_editor_for_current_site | is_reviewer_for_current_site))
 
 
 # Model Permissions for sites and group
diff --git a/rdmo/management/urls/__init__.py b/rdmo/management/urls/__init__.py
index e63181ac4f..cb5ea3f506 100644
--- a/rdmo/management/urls/__init__.py
+++ b/rdmo/management/urls/__init__.py
@@ -1,9 +1,7 @@
-from django.urls import path, re_path
+from django.urls import re_path
 
-from ..views import ManagementView, ImportView, UploadView
+from ..views import ManagementView
 
 urlpatterns = [
-    path('upload/', UploadView.as_view(), name='upload'),
-    path('import/', ImportView.as_view(), name='import'),
     re_path('', ManagementView.as_view(), name='management')
 ]
diff --git a/rdmo/management/views.py b/rdmo/management/views.py
index e6daf42004..a9136f9dbd 100644
--- a/rdmo/management/views.py
+++ b/rdmo/management/views.py
@@ -1,133 +1,19 @@
 import logging
 
 from django.contrib.auth.mixins import LoginRequiredMixin
-from django.http import HttpResponseRedirect
-from django.shortcuts import render
-from django.urls import reverse
-from django.utils.translation import gettext_lazy as _
-from django.views.generic.base import View
 from django.views.generic import TemplateView
+from rules import test_rule
+from rules.contrib.views import \
+    PermissionRequiredMixin as RulesPermissionRequiredMixin
 
-from rdmo.core.imports import handle_uploaded_file
-from rdmo.core.views import CSRFViewMixin
-from rdmo.core.xml import flat_xml_to_elements, read_xml_file
-
-from .imports import check_permissions, import_elements
+from rdmo.core.views import CSRFViewMixin, PermissionRedirectMixin
 
 logger = logging.getLogger(__name__)
 
 
-class ManagementView(LoginRequiredMixin, CSRFViewMixin, TemplateView):
+class ManagementView(LoginRequiredMixin, PermissionRedirectMixin, RulesPermissionRequiredMixin, CSRFViewMixin, TemplateView):
     template_name = 'management/management.html'
 
-
-class UploadView(LoginRequiredMixin, View):
-
-    def get_success_url(self):
-        return self.request.META.get('HTTP_REFERER') or reverse('management')
-
-    def get(self, request):
-        return HttpResponseRedirect(reverse('management'))
-
-    def post(self, request):
-        try:
-            uploaded_file = request.FILES['uploaded_file']
-        except KeyError:
-            return HttpResponseRedirect(self.get_success_url())
-        else:
-            import_tmpfile_name = handle_uploaded_file(uploaded_file)
-
-        root = read_xml_file(import_tmpfile_name)
-        if root is None:
-            logger.info('Xml parsing error. Import failed.')
-            return render(request, 'core/error.html', {
-                'title': _('Import error'),
-                'errors': [_('The content of the xml file does not consist of well formed data or markup.')]
-            }, status=400)
-
-        else:
-            try:
-                elements = flat_xml_to_elements(root)
-            except (KeyError, TypeError, AttributeError):
-                return render(request, 'core/error.html', {
-                    'title': _('Import error'),
-                    'errors': [_('This is not a valid RDMO XML file.')]
-                }, status=400)
-
-            if check_permissions(elements, request.user):
-                # store information in session for ProjectCreateImportView
-                request.session['import_file_name'] = uploaded_file.name
-                request.session['import_tmpfile_name'] = import_tmpfile_name
-                request.session['import_success_url'] = self.get_success_url()
-
-                return render(request, 'management/upload.html', {
-                    'file_name': uploaded_file.name,
-                    'elements': import_elements(elements)
-                })
-            else:
-                return render(request, 'core/error.html', {
-                    'title': _('Import error'),
-                    'errors': [_('Forbidden.')]
-                }, status=403)
-
-
-class ImportView(LoginRequiredMixin, View):
-
-    def get_success_url(self):
-        return self.request.session.get('import_success_url') or reverse('management')
-
-    def get(self, request):
-        return HttpResponseRedirect(reverse('management'))
-
-    def post(self, request):
-        if 'cancel' in self.request.POST:
-            return HttpResponseRedirect(self.get_success_url())
-
-        import_file_name = request.session['import_file_name']
-        import_tmpfile_name = request.session.get('import_tmpfile_name')
-
-        # parse the form data, which is <uri: [parent, checked]> or <uri: [checked]>
-        parents = {}
-        checked = {}
-        for key, values in request.POST.lists():
-            if key.startswith('http'):
-                try:
-                    parents[key] = None if values[0] == 'null' else values[0]
-                    checked[key] = True if values[1] == 'on' else False
-                except IndexError:
-                    parents[key] = False
-                    checked[key] = True if values[0] == 'on' else False
-
-        root = read_xml_file(import_tmpfile_name)
-        if root is None:
-            logger.info('Xml parsing error. Import failed.')
-            return render(request, 'core/error.html', {
-                'title': _('Import error'),
-                'errors': [_('The content of the xml file does not consist of well formed data or markup.')]
-            }, status=400)
-
-        else:
-            try:
-                elements = flat_xml_to_elements(root)
-            except (KeyError, TypeError):
-                return render(request, 'core/error.html', {
-                    'title': _('Import error'),
-                    'errors': [_('This is not a RDMO XML file.')]
-                }, status=400)
-
-            if check_permissions(elements, request.user):
-                if checked:
-                    return render(request, 'management/import.html', {
-                        'file_name': import_file_name,
-                        'elements': import_elements(elements, parents=parents, save=checked),
-                        'success_url': self.get_success_url()
-                    })
-                else:
-                    # if nothing was checked, just return to the success_url
-                    return HttpResponseRedirect(self.get_success_url())
-
-            else:
-                return render(request, 'core/error.html', {
-                    'title': _('Import error'),
-                    'errors': [_('Forbidden.')]
-                }, status=403)
+    def has_permission(self):
+        # Use test_rule from rules for permissions check
+        return test_rule('management.can_view_management', self.request.user, self.request.site)
diff --git a/rdmo/management/viewsets.py b/rdmo/management/viewsets.py
index b3fa6ea43c..414431c583 100644
--- a/rdmo/management/viewsets.py
+++ b/rdmo/management/viewsets.py
@@ -2,14 +2,13 @@
 
 from django.utils.translation import gettext_lazy as _
 from rest_framework import viewsets
-from rest_framework.exceptions import PermissionDenied
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 from rest_framework.serializers import ValidationError
 
 from rdmo.conditions.models import Condition
 from rdmo.core.imports import handle_uploaded_file
-from rdmo.core.utils import get_model_field_meta
+from rdmo.core.utils import get_model_field_meta, is_truthy
 from rdmo.core.xml import (convert_elements, flat_xml_to_elements,
                            order_elements, read_xml_file)
 from rdmo.domain.models import Attribute
@@ -18,7 +17,7 @@
 from rdmo.tasks.models import Task
 from rdmo.views.models import View
 
-from .imports import check_permissions, import_elements
+from .imports import import_elements
 
 logger = logging.getLogger(__name__)
 
@@ -84,12 +83,8 @@ def create(self, request, *args, **kwargs):
         # step 6: convert elements to a list
         elements = elements.values()
 
-        # step 7: check if the user has access to those models
-        if not check_permissions(elements, request.user):
-            raise PermissionDenied()
-
         # step 8: import the elements if save=True is set
-        import_elements(elements, save=request.POST.get('import', '').lower() in ['true', 't', '1'])
+        import_elements(elements, save=is_truthy(request.POST.get('import')), user=request.user)
 
         # step 9: return the list of elements
         return Response(elements)
@@ -108,12 +103,8 @@ def create(self, request, *args, **kwargs):
         except TypeError:
             raise ValidationError({'elements': [_('This is not a valid RDMO import JSON.')]})
 
-        # step 2: check if the user has access to those models
-        if not check_permissions(elements, request.user):
-            raise PermissionDenied()
-
         # step 3: import the elements
-        import_elements(elements)
+        import_elements(elements, user=request.user)
 
         # step 4: return the list of elements
         return Response(elements)
diff --git a/rdmo/options/imports.py b/rdmo/options/imports.py
index dcd24d8173..fa2023a0a0 100644
--- a/rdmo/options/imports.py
+++ b/rdmo/options/imports.py
@@ -2,8 +2,9 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (set_common_fields, set_lang_field,
-                               set_m2m_instances, set_m2m_through_instances,
+from rdmo.core.imports import (check_permissions, set_common_fields,
+                               set_lang_field, set_m2m_instances,
+                               set_m2m_through_instances,
                                set_reverse_m2m_through_instance,
                                validate_instance)
 
@@ -14,7 +15,7 @@
 logger = logging.getLogger(__name__)
 
 
-def import_optionset(element, save=False):
+def import_optionset(element, save=False, user=None):
     try:
         optionset = OptionSet.objects.get(uri=element.get('uri'))
     except OptionSet.DoesNotExist:
@@ -27,6 +28,8 @@ def import_optionset(element, save=False):
 
     validate_instance(optionset, element, OptionSetLockedValidator, OptionSetUniqueURIValidator)
 
+    check_permissions(optionset, element, user)
+
     if save and not element.get('errors'):
         if optionset.id:
             element['updated'] = True
@@ -43,7 +46,7 @@ def import_optionset(element, save=False):
     return optionset
 
 
-def import_option(element, save=False):
+def import_option(element, save=False, user=None):
     try:
         option = Option.objects.get(uri=element.get('uri'))
     except Option.DoesNotExist:
@@ -57,6 +60,8 @@ def import_option(element, save=False):
 
     validate_instance(option, element, OptionLockedValidator, OptionUniqueURIValidator)
 
+    check_permissions(option, element, user)
+
     if save and not element.get('errors'):
         if option.id:
             element['updated'] = True
diff --git a/rdmo/options/models.py b/rdmo/options/models.py
index 99292c517a..993b1309bc 100644
--- a/rdmo/options/models.py
+++ b/rdmo/options/models.py
@@ -216,15 +216,6 @@ def save(self, *args, **kwargs):
         self.uri = self.build_uri(self.uri_prefix, self.uri_path)
         super().save(*args, **kwargs)
 
-    def copy(self, uri_prefix, uri_path):
-        option = copy_model(self, uri_prefix=uri_prefix, uri_path=uri_path)
-
-        # set m2m fields for copy
-        option.optionsets.set(self.optionsets.all())
-        option.editors.set([Site.objects.get_current()])
-
-        return option
-
     @property
     def text(self):
         return self.trans('text')
diff --git a/rdmo/options/serializers/v1/__init__.py b/rdmo/options/serializers/v1/__init__.py
index 9edcc6dac5..2dc884099a 100644
--- a/rdmo/options/serializers/v1/__init__.py
+++ b/rdmo/options/serializers/v1/__init__.py
@@ -1,4 +1,3 @@
-from .option import (OptionIndexSerializer, OptionListSerializer,
-                     OptionSerializer)
-from .optionset import (OptionSetIndexSerializer, OptionSetListSerializer,
-                        OptionSetNestedSerializer, OptionSetSerializer)
+from .option import OptionIndexSerializer, OptionSerializer
+from .optionset import (OptionSetIndexSerializer, OptionSetNestedSerializer,
+                        OptionSetSerializer)
diff --git a/rdmo/options/serializers/v1/option.py b/rdmo/options/serializers/v1/option.py
index 26a8f70643..d685a1d40b 100644
--- a/rdmo/options/serializers/v1/option.py
+++ b/rdmo/options/serializers/v1/option.py
@@ -1,9 +1,8 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementExportSerializerMixin,
-                                   ElementModelSerializerMixin,
+from rdmo.core.serializers import (ElementModelSerializerMixin,
                                    ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionsSerializerMixin,
+                                   ReadOnlyObjectPermissionSerializerMixin,
                                    ThroughModelSerializerMixin,
                                    TranslationSerializerMixin)
 
@@ -12,15 +11,20 @@
 
 
 class OptionSerializer(ThroughModelSerializerMixin, TranslationSerializerMixin,
-                       ElementModelSerializerMixin, ReadOnlyObjectPermissionsSerializerMixin,
-                       serializers.ModelSerializer):
+                       ElementModelSerializerMixin, ElementWarningSerializerMixin,
+                       ReadOnlyObjectPermissionSerializerMixin, serializers.ModelSerializer):
 
     model = serializers.SerializerMethodField()
+    uri_path = serializers.CharField(required=True)
+
     optionsets = serializers.PrimaryKeyRelatedField(queryset=OptionSet.objects.all(), required=False, many=True)
     conditions = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
+
+    warning = serializers.SerializerMethodField()
+    read_only = serializers.SerializerMethodField()
+
     values_count = serializers.IntegerField(read_only=True)
     projects_count = serializers.IntegerField(read_only=True)
-    read_only = serializers.SerializerMethodField()
 
     class Meta:
         model = Option
@@ -40,7 +44,8 @@ class Meta:
             'values_count',
             'projects_count',
             'editors',
-            'read_only'
+            'warning',
+            'read_only',
         )
         trans_fields = (
             'text',
@@ -52,19 +57,6 @@ class Meta:
             OptionUniqueURIValidator(),
             OptionLockedValidator()
         )
-
-
-class OptionListSerializer(ElementExportSerializerMixin, ElementWarningSerializerMixin,
-                           OptionSerializer):
-
-    warning = serializers.SerializerMethodField()
-    xml_url = serializers.SerializerMethodField()
-
-    class Meta(OptionSerializer.Meta):
-        fields = OptionSerializer.Meta.fields + (
-            'warning',
-            'xml_url'
-        )
         warning_fields = (
             'text',
         )
diff --git a/rdmo/options/serializers/v1/optionset.py b/rdmo/options/serializers/v1/optionset.py
index 5d656881bb..885a439c53 100644
--- a/rdmo/options/serializers/v1/optionset.py
+++ b/rdmo/options/serializers/v1/optionset.py
@@ -1,14 +1,13 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementExportSerializerMixin,
-                                   ElementModelSerializerMixin,
-                                   ReadOnlyObjectPermissionsSerializerMixin,
+from rdmo.core.serializers import (ElementModelSerializerMixin,
+                                   ReadOnlyObjectPermissionSerializerMixin,
                                    ThroughModelSerializerMixin)
 from rdmo.questions.models import Question
 
 from ...models import OptionSet, OptionSetOption
 from ...validators import OptionSetLockedValidator, OptionSetUniqueURIValidator
-from .option import OptionListSerializer
+from .option import OptionSerializer
 
 
 class OptionSetOptionSerializer(serializers.ModelSerializer):
@@ -22,12 +21,14 @@ class Meta:
 
 
 class OptionSetSerializer(ThroughModelSerializerMixin, ElementModelSerializerMixin,
-                          ReadOnlyObjectPermissionsSerializerMixin, serializers.ModelSerializer):
+                          ReadOnlyObjectPermissionSerializerMixin, serializers.ModelSerializer):
 
     model = serializers.SerializerMethodField()
     uri_path = serializers.CharField(required=True)
+
     options = OptionSetOptionSerializer(source='optionset_options', read_only=False, required=False, many=True)
     questions = serializers.PrimaryKeyRelatedField(queryset=Question.objects.all(), required=False, many=True)
+
     read_only = serializers.SerializerMethodField()
 
     class Meta:
@@ -47,7 +48,7 @@ class Meta:
             'conditions',
             'questions',
             'editors',
-            'read_only'
+            'read_only',
         )
         through_fields = (
             ('options', 'optionset', 'option', 'optionset_options'),
@@ -58,28 +59,18 @@ class Meta:
         )
 
 
-class OptionSetListSerializer(ElementExportSerializerMixin, OptionSetSerializer):
+class OptionSetNestedSerializer(OptionSetSerializer):
 
-    xml_url = serializers.SerializerMethodField()
+    elements = serializers.SerializerMethodField()
 
     class Meta(OptionSetSerializer.Meta):
         fields = OptionSetSerializer.Meta.fields + (
-            'xml_url',
-        )
-
-
-class OptionSetNestedSerializer(OptionSetListSerializer):
-
-    elements = serializers.SerializerMethodField()
-
-    class Meta(OptionSetListSerializer.Meta):
-        fields = OptionSetListSerializer.Meta.fields + (
             'elements',
         )
 
     def get_elements(self, obj):
         for element in obj.elements:
-            yield OptionListSerializer(element, context=self.context).data
+            yield OptionSerializer(element, context=self.context).data
 
 
 class OptionSetIndexSerializer(serializers.ModelSerializer):
diff --git a/rdmo/options/static/options/css/options.scss b/rdmo/options/static/options/css/options.scss
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/rdmo/options/static/options/js/options.js b/rdmo/options/static/options/js/options.js
deleted file mode 100644
index cf46453ac6..0000000000
--- a/rdmo/options/static/options/js/options.js
+++ /dev/null
@@ -1,175 +0,0 @@
-angular.module('options', ['core'])
-
-.factory('OptionsService', ['$resource', '$timeout', '$window', '$q', 'utils', function($resource, $timeout, $window, $q, utils) {
-
-    /* get the base url */
-
-    var baseurl = angular.element('meta[name="baseurl"]').attr('content');
-
-    /* configure resources */
-
-    var resources = {
-        optionsets: $resource(baseurl + 'api/v1/options/optionsets/:list_action/:id/:detail_action/'),
-        options: $resource(baseurl + 'api/v1/options/options/:list_action/:id/:detail_action/'),
-        conditions: $resource(baseurl + 'api/v1/conditions/conditions/:list_action/:id/'),
-        providers: $resource(baseurl + 'api/v1/options/providers/:id/'),
-        settings: $resource(baseurl + 'api/v1/core/settings/'),
-        sites: $resource(baseurl + 'api/v1/core/sites/')
-    };
-
-    /* configure factories */
-
-    var factories = {
-        optionsets: function() {
-            return {
-                order: 0,
-                uri_prefix: service.settings.default_uri_prefix
-            };
-        },
-        options: function(parent) {
-            return {
-                order: 0,
-                optionset: (angular.isDefined(parent) && parent) ? parent.id : null,
-                uri_prefix: (angular.isDefined(parent) && parent) ? parent.uri_prefix : service.settings.default_uri_prefix
-            };
-        }
-    };
-
-    /* create the options service */
-
-    var service = {};
-
-    service.init = function(options) {
-        service.providers = resources.providers.query();
-        service.settings = resources.settings.get();
-        service.sites = resources.sites.query();
-        service.uri_prefixes = [];
-        service.uri_prefix = '';
-        service.filter = sessionStorage.getItem('options_filter') || '';
-        service.showOptions = !(sessionStorage.getItem('options_showOptions') === 'false');
-
-        service.initView().then(function () {
-            var current_scroll_pos = sessionStorage.getItem('options_scroll_pos');
-            if (current_scroll_pos) {
-                $timeout(function() {
-                    $window.scrollTo(0, current_scroll_pos);
-                });
-            }
-        });
-
-        $window.addEventListener('beforeunload', function() {
-            sessionStorage.setItem('options_scroll_pos', $window.scrollY);
-            sessionStorage.setItem('options_filter', service.filter);
-            sessionStorage.setItem('options_showOptions', service.showOptions);
-        });
-    };
-
-    service.initView = function(options) {
-        return resources.optionsets.query({list_action: 'nested'}, function(response) {
-            service.optionsets = response;
-
-            // construct list of uri_prefixes
-            service.uri_prefixes = []
-            service.optionsets.map(function(optionset) {
-                if (service.uri_prefixes.indexOf(optionset.uri_prefix) < 0) {
-                    service.uri_prefixes.push(optionset.uri_prefix)
-                }
-                optionset.options.map(function(option) {
-                    if (service.uri_prefixes.indexOf(option.uri_prefix) < 0) {
-                        service.uri_prefixes.push(option.uri_prefix)
-                    }
-                });
-            });
-        }).$promise;
-    };
-
-    service.openFormModal = function(resource, obj, create, copy) {
-        var fetch_resource = (resource === 'conditions') ? 'optionsets': resource;
-
-        service.errors = {};
-        service.values = utils.fetchValues(resources[fetch_resource], factories[resource], obj, create, copy);
-        service.conditions = resources.conditions.query({list_action: 'index'});
-
-        $q.when([
-            service.values.$promise,
-            service.conditions.$promise
-        ]).then(function() {
-            $('#' + resource + '-form-modal').modal('show');
-            $timeout(function() {
-                $('formgroup[data-quicksearch="true"]').trigger('refresh');
-            });
-        });
-    };
-
-    service.submitFormModal = function(resource) {
-        var submit_resource = (resource === 'conditions') ? 'optionsets': resource;
-
-        utils.storeValues(resources[submit_resource], service.values).then(function() {
-            $('#' + resource + '-form-modal').modal('hide');
-            service.initView();
-        }, function(result) {
-            service.errors = result.data;
-        });
-    };
-
-    service.openDeleteModal = function(resource, obj) {
-        utils.fetchValues(resources[resource], factories[resource], obj).$promise.then(function(response) {
-            service.values = response;
-            service.values.options = obj.options;
-            $('#' + resource + '-delete-modal').modal('show');
-        });
-    };
-
-    service.submitDeleteModal = function(resource) {
-        resources[resource].delete({id: service.values.id}, function() {
-            $('#' + resource + '-delete-modal').modal('hide');
-            service.initView();
-        });
-    };
-
-    service.openShowModal = function(resource, obj) {
-        service.values = utils.fetchValues(resources[resource], factories[resource], obj)
-
-        $q.when(service.values.$promise).then(function() {
-            $('#' + resource + '-show-modal').modal('show');
-        });
-    };
-
-    service.hideOptionSet = function(item) {
-        var hide = false;
-
-        if (service.filter && item.key.indexOf(service.filter) < 0) {
-            hide = true;
-        }
-        if (service.uri_prefix && item.uri_prefix != service.uri_prefix) {
-            hide = true;
-        }
-
-        if (hide === true) {
-            // hide only if all options of this optionsset are hidden
-            return item.options.every(function(option) {
-                return service.hideOption(option) === true;
-            });
-        }
-    };
-
-    service.hideOption = function(item) {
-        if (service.filter && item.uri.indexOf(service.filter) < 0
-                           && item.text.indexOf(service.filter) < 0) {
-            return true;
-        }
-        if (service.uri_prefix && item.uri_prefix != service.uri_prefix) {
-            return true;
-        }
-    };
-
-    return service;
-
-}])
-
-.controller('OptionsController', ['$scope', 'OptionsService', function($scope, OptionsService) {
-
-    $scope.service = OptionsService;
-    $scope.service.init();
-
-}]);
diff --git a/rdmo/options/templates/options/options.html b/rdmo/options/templates/options/options.html
deleted file mode 100644
index bd5883f510..0000000000
--- a/rdmo/options/templates/options/options.html
+++ /dev/null
@@ -1,246 +0,0 @@
-{% extends 'core/page.html' %}
-{% load static %}
-{% load compress %}
-{% load i18n %}
-{% load core_tags %}
-
-{% block head %}
-    {% vendor 'angular' %}
-    {% vendor 'select2' %}
-    {% vendor 'select2-bootstrap-theme' %}
-
-    {% compress css %}
-    <link rel="stylesheet" type="text/x-scss" href="{% static 'options/css/options.scss' %}" />
-    {% endcompress %}
-
-    {% compress js %}
-    <script type="text/javascript" src="{% static 'core/js/core.js' %}" ></script>
-    <script type="text/javascript" src="{% static 'options/js/options.js' %}" ></script>
-    {% endcompress %}
-{% endblock %}
-
-{% block bodyattr %} ng-app="options" ng-controller="OptionsController" {% endblock %}
-
-{% block sidebar %}
-
-    <h2>{% trans 'Filter options' %}</h2>
-
-    <div class="form-group">
-        <div class="input-group">
-            <input type="text" class="form-control" ng-model="service.filter"
-                   placeholder="{% trans 'Search' %}"></input>
-            <span class="input-group-btn" ng-click="service.filter = ''">
-                <button class="btn btn-default">
-                    <span class="fa fa-times"></span>
-                </button>
-            </span>
-        </div>
-    </div>
-
-    <div class="form-group">
-        <select class="form-control" ng-model="service.uri_prefix">
-            <option value="">{% trans 'All URI prefixes' %}</option>
-            <option ng-repeat="uri_prefix in service.uri_prefixes" value="{$ uri_prefix $}">
-                {$ uri_prefix $}
-            </option>
-        </select>
-    </div>
-
-    <div class="form-group">
-        <div class="checkbox">
-            <label>
-                <input type="checkbox" ng-model="service.showOptions">
-                {% trans 'Show options' %}
-            </label>
-        </div>
-    </div>
-
-    <h2>{% trans 'Options' %}</h2>
-
-    <ul class="list-unstyled">
-        <li>
-            <a href="" ng-click="service.openFormModal('optionsets', null, true)">
-                {% trans 'Create new option set' %}
-            </a>
-        </li>
-        <li>
-            <a href="" ng-click="service.openFormModal('options', null, true)">
-                {% trans 'Create new option' %}
-            </a>
-        </li>
-    </ul>
-
-    <h2>{% trans 'Export' %}</h2>
-
-    <ul class="list-unstyled">
-        {% for format, text in export_formats %}
-        <li >
-            <a href="{% url 'options_export' format %}" target="_blank">
-                {{ text }}
-            </a>
-        </li>
-        {% endfor %}
-    </ul>
-
-    <ul class="list-unstyled">
-        <li >
-            <a href="{% url 'options_export' 'xml' %}" target="_blank">
-                {% trans 'XML' %}
-            </a>
-        </li>
-    </ul>
-
-    <h2>{% trans 'Import' %}</h2>
-    <ul class="list-unstyled">
-        <li>
-            {% url 'upload' as upload_url %}
-            {% include 'core/upload_form.html' with upload_url=upload_url %}
-        </li>
-    </ul>
-
-{% endblock %}
-
-{% block page %}
-
-    <h1>{% trans 'Options' %}</h1>
-
-    <div ng-repeat="optionset in service.optionsets" ng-cloak>
-
-        <div class="panel panel-default" ng-hide="service.hideOptionSet(optionset)">
-            <div class="panel-heading">
-                <div class="pull-right">
-                    <i class="fa fa-lock text-danger" ng-show="optionset.locked"></i>
-                    <i class="fa fa-ban text-muted" ng-show="optionset.read_only"
-                        title="{% trans 'Read only' %}">
-                    </i>
-                    <a href="" class="fa fa-plus"
-                        title="{% trans 'Add option' %}"
-                        ng-click="service.openFormModal('options', optionset, true)">
-                    </a>
-                    <a href="" class="fa fa-pencil"
-                        title="{% trans 'Update option set' %}"
-                        ng-click="service.openFormModal('optionsets', optionset)">
-                    </a>
-                    <a href="" class="fa fa-copy"
-                        title="{% trans 'Copy option set' %}"
-                        ng-click="service.openFormModal('optionsets', optionset, false, true)">
-                    </a>
-                    <a href="" class="fa fa-question-circle"
-                        title="{% trans 'Update conditions' %}"
-                        ng-click="service.openFormModal('conditions', optionset)">
-                    </a>
-                    <a href="" class="fa fa-eye"
-                        title="{% trans 'Show references' %}"
-                        ng-click="service.openShowModal('optionsets', optionset)">
-                    </a>
-                    <a href="{$ optionset.xml_url $}" class="fa fa-download"
-                        title="{% trans 'Export option set as XML' %}"
-                        target="blank">
-                    </a>
-                    <a href="" class="fa fa-trash"
-                        title="{% trans 'Delete option set' %}"
-                        ng-click="service.openDeleteModal('optionsets', optionset)">
-                    </a>
-                </div>
-                <p ng-class="{'text-muted': optionset.read_only}">
-                    <strong >{% trans 'Option set' %}</strong>
-                </p>
-                <div>
-                    <code class="code-options" title="{% trans 'URI' %}">{$ optionset.uri $}</code>
-                    <code class="code-order">{$ optionset.order $}</code>
-                </div>
-                <div ng-show="optionset.conditions" ng-repeat="condition in optionset.conditions">
-                    <code class="code-conditions" title="{% trans 'Condition' %}">{$ condition.uri $}</code>
-                </div>
-                </div>
-                {% if settings.MULTISITE %}
-                <div class="panel-body">
-                    <div class="row">
-                        <div class="col-sm-3">
-                            <strong>{% trans 'Editors for this Option Set' %}</strong>
-                        </div>
-                        <div class="col-sm-9">
-                            <ul>
-                                <li ng-show="optionset.editors.length > 0 && optionset.editors.length < service.sites.length"
-                                    ng-repeat="editor in optionset.editors">
-                                    {$ editor.name $}
-                                </li>
-                                <li ng-show="optionset.editors.length == 0 || optionset.editors.length == service.sites.length">
-                                    {% trans 'All sites' %}
-                                </li>
-                            </ul>
-                        </div>
-                    </div>
-                </div>
-                {% endif %}
-                
-
-            <ul class="list-group" ng-if="optionset.provider">
-                <li class="list-group-item">
-                    <strong>{% trans 'Provider' %}</strong>
-                    <code class="code-options-provider">{$ optionset.provider.class_name $}</code>
-                    <span>{$ optionset.provider.label $}</span>
-                </li>
-            </ul>
-
-            <ul class="list-group" ng-if="optionset.options && service.showOptions">
-                <li class="list-group-item" ng-repeat="option in optionset.options"
-                    ng-hide="service.hideOption(option)">
-
-                    <div class="pull-right">
-                        <span class="fa fa-warning text-warning"
-                            title="{% trans 'Some languages are not configured' %}"
-                            ng-show="option.warning"></span>
-
-                        <i class="fa fa-lock text-muted" ng-show="optionset.locked"></i>
-                        <i class="fa fa-lock text-danger" ng-show="option.locked"></i>
-                        <i class="fa fa-ban text-muted" ng-show="option.read_only"
-                        title="{% trans 'Read only' %}">
-                        </i>
-
-                        <a href="" class="fa fa-pencil"
-                            title="{% trans 'Update option' %}"
-                            ng-click="service.openFormModal('options', option)">
-                        </a>
-                        <a href="" class="fa fa-copy"
-                            title="{% trans 'Copy option' %}"
-                            ng-click="service.openFormModal('options', option, false, true)">
-                        </a>
-                        <a href="" class="fa fa-eye"
-                            title="{% trans 'Show references' %}"
-                            ng-click="service.openShowModal('options', option)">
-                        </a>
-                        <a href="{$ option.xml_url $}" class="fa fa-download"
-                            title="{% trans 'Export option as XML' %}"
-                            target="blank">
-                        </a>
-                        <a href="" class="fa fa-trash"
-                            title="{% trans 'Delete option' %}"
-                            ng-click="service.openDeleteModal('options', option)">
-                        </a>
-                    </div>
-                    <p ng-class="{'text-muted': option.read_only}">
-                        <strong>{% trans 'Option' %}</strong>
-                        <span>{$ option.text $}</span>
-                    </p>
-                    <div>
-                        <code class="code-options" title="{% trans 'URI' %}">{$ option.uri $}</code>
-                        <code class="code-order">{$ option.order $}</code>
-                    </div>
-                </li>
-            </ul>
-        </div>
-
-    </div>
-
-    {% include 'options/options_modal_form_optionsets.html' %}
-    {% include 'options/options_modal_form_options.html' %}
-    {% include 'options/options_modal_form_conditions.html' %}
-
-    {% include 'options/options_modal_show_optionsets.html' %}
-    {% include 'options/options_modal_show_options.html' %}
-
-    {% include 'options/options_modal_delete_optionsets.html' %}
-    {% include 'options/options_modal_delete_options.html' %}
-
-{% endblock %}
diff --git a/rdmo/options/templates/options/options_modal_delete_options.html b/rdmo/options/templates/options/options_modal_delete_options.html
deleted file mode 100644
index 76e80d4847..0000000000
--- a/rdmo/options/templates/options/options_modal_delete_options.html
+++ /dev/null
@@ -1,54 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="options-delete-modal" tabindex="-1">
-        <div class="modal-dialog">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Delete option' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with object='{$ service.values.uri $}' %}
-                        You are about to permanently delete the option <code>{{ object }}</code>.
-                        {% endblocktrans %}
-                    </p>
-
-                    <p>
-                        {% blocktrans trimmed with values_count='{$ service.values.values_count $}' projects_count='{$ service.values.projects_count $}' %}
-                        This option is used for <b>{{ values_count }} values</b> in <b>{{ projects_count }} projects</b>.
-                        {% endblocktrans %}
-                    </p>
-
-                    <p ng-show="service.values.conditions.length">
-                        {% blocktrans trimmed with conditions_count='{$ service.values.conditions.length $}' %}
-                        This option is also used in <b>{{ conditions_count }} conditions</b>.
-                        {% endblocktrans %}
-                    </p>
-
-                    <p class="text-danger">
-                        {% trans 'This action cannot be undone!' %}
-                    </p>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-danger"
-                            ng-disabled="service.values.read_only"
-                            ng-click="service.submitDeleteModal('options')">
-                        {% trans 'Delete' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/options/templates/options/options_modal_delete_optionsets.html b/rdmo/options/templates/options/options_modal_delete_optionsets.html
deleted file mode 100644
index b2f31e2128..0000000000
--- a/rdmo/options/templates/options/options_modal_delete_optionsets.html
+++ /dev/null
@@ -1,67 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="optionsets-delete-modal" tabindex="-1">
-        <div class="modal-dialog">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Delete option set' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with object='{$ service.values.uri $}' %}
-                        You are about to permanently delete the option set <code>{{ object }}</code>.
-                        {% endblocktrans %}
-                    </p>
-
-                    <p ng-show="service.values.options.length">
-                        {% blocktrans trimmed %}
-                        <strong>Important!</strong> This will also delete the following options:
-                        {% endblocktrans %}
-                    </p>
-
-                    <ul class="list-unstyled">
-                        <li ng-repeat="option in service.values.options">
-                            <strong>{% trans 'Option' %}</strong>
-                            <code>{$ option.uri $}</code>
-                        </li>
-                    </ul>
-
-                    <div ng-show="service.values.questions.length">
-                        <p>
-                            {% blocktrans trimmed with questions_count='{$ service.values.questions.length $}' %}
-                            This option set is also used in <b>{{ questions_count }} questions</b>.
-                            {% endblocktrans %}
-                        </p>
-
-                        <p ng-show="service.values.options.length">
-                            <b>{% trans 'This does not include references from the options above. Please check carefully.' %}</b>
-                        </p>
-                    </div>
-
-                    <p class="text-danger">
-                        {% trans 'This action cannot be undone!' %}
-                    </p>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-danger"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitDeleteModal('optionsets')">
-                        {% trans 'Delete' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/options/templates/options/options_modal_form_conditions.html b/rdmo/options/templates/options/options_modal_form_conditions.html
deleted file mode 100644
index 38472020fd..0000000000
--- a/rdmo/options/templates/options/options_modal_form_conditions.html
+++ /dev/null
@@ -1,43 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="conditions-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Conditions' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <formgroup
-                        data-id="conditions"
-                        data-type="selectmultiple"
-                        data-label="{% trans 'Conditions' %}"
-                        data-model="service.values.conditions"
-                        data-errors="service.errors.conditions"
-                        data-options="service.conditions"
-                        data-options-label="uri"
-                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                    </formgroup>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-class="{'disabled': service.catalog.read_only}"
-                            ng-click="service.submitFormModal('conditions')">
-                        {% trans 'Save' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/options/templates/options/options_modal_form_options.html b/rdmo/options/templates/options/options_modal_form_options.html
deleted file mode 100644
index 3253c02c6e..0000000000
--- a/rdmo/options/templates/options/options_modal_form_options.html
+++ /dev/null
@@ -1,217 +0,0 @@
-{% load i18n %}
-{% get_available_languages as languages %}
-
-    <div class="modal" id="options-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        <span ng-hide="service.values.id">{% trans 'Create option' %}</span>
-                        <span ng-show="service.values.copy">{% trans 'Copy option' %}</span>
-                        <span ng-show="service.values.id && !service.values.copy">{% trans 'Update option' %}</span>
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <div class="copy-block" ng-show="service.values.copy">
-                        <p>
-                            {% trans 'Please enter the values for the copy of the option below and change at least the <strong>key</strong>.' %}
-                        </p>
-                        <p>
-                            {% trans 'Other properties can be changed once the new option is created.' %}
-                        </p>
-                    </div>
-                    <div class="row">
-                        <div class="col-sm-6">
-                            <div class="form-group" ng-class="{'has-error': service.errors.uri_prefix }">
-                                <label class="control-label" for="uri_prefix">
-                                    {{ meta.Option.uri_prefix.verbose_name }}
-                                </label>
-
-                                <div class="input-group">
-                                    <input class="form-control"
-                                           id="option_uri_prefix"
-                                           type="text"
-                                           ng-model="service.values.uri_prefix" />
-
-                                    <span class="input-group-btn">
-                                        <button type="button" class="btn btn-default"
-                                            title="{% trans 'Insert default URI Prefix' %}"
-                                            ng-click="service.values.uri_prefix = service.settings.default_uri_prefix">
-
-                                            <span class="fa fa-magic"></span>
-                                        </button>
-                                    </span>
-                                </div>
-
-                                <p class="help-block">
-                                    {{ meta.Option.uri_prefix.help_text }}
-                                </p>
-
-                                <ul class="help-block list-unstyled" ng-show="service.errors.uri_prefix">
-                                    <li ng-repeat="error in service.errors.uri_prefix">
-                                        {$ error $}
-                                    </li>
-                                </ul>
-                            </div>
-                        </div>
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="option_key"
-                                data-type="text"
-                                data-label="{{ meta.Option.key.verbose_name }}"
-                                data-help="{{ meta.Option.key.help_text }}"
-                                data-model="service.values.key"
-                                data-errors="service.errors.key">
-                            </formgroup>
-                        </div>
-                    </div>
-                    <div ng-show="service.values.copy">
-                        <formgroup
-                            data-id="option_optionset"
-                            data-type="selectnumber"
-                            data-label="{{ meta.Option.optionset.verbose_name }}"
-                            data-help="{{ meta.Option.optionset.help_text }}"
-                            data-model="service.values.optionset"
-                            data-errors="service.errors.optionset"
-                            data-options="service.optionsets"
-                            data-options-label="uri"
-                            data-options-null="1"
-                            data-quicksearch="true">
-                        </formgroup>
-                    </div>
-                    <div ng-hide="service.values.copy">
-                        <formgroup
-                            data-id="option_comment"
-                            data-type="textarea"
-                            data-label="{{ meta.Option.comment.verbose_name }}"
-                            data-help="{{ meta.Option.comment.help_text }}"
-                            data-model="service.values.comment"
-                            data-errors="service.errors.comment">
-                        </formgroup>
-                        <formgroup
-                            data-id="option_locked"
-                            data-type="checkbox"
-                            data-label="{{ meta.Option.locked.verbose_name }}"
-                            data-help="{{ meta.Option.locked.help_text }}"
-                            data-model="service.values.locked"
-                            data-errors="service.errors.locked">
-                        </formgroup>
-                        <div class="row">
-                            <div class="col-sm-9">
-                                <formgroup
-                                    data-id="option_optionset"
-                                    data-type="selectnumber"
-                                    data-label="{{ meta.Option.optionset.verbose_name }}"
-                                    data-help="{{ meta.Option.optionset.help_text }}"
-                                    data-model="service.values.optionset"
-                                    data-errors="service.errors.optionset"
-                                    data-options="service.optionsets"
-                                    data-options-label="uri"
-                                    data-options-null="1"
-                                    data-quicksearch="true">
-                                </formgroup>
-                            </div>
-                            <div class="col-sm-3">
-                                <formgroup
-                                    data-id="option_order"
-                                    data-type="number"
-                                    data-label="{{ meta.Option.order.verbose_name }}"
-                                    data-help="{{ meta.Option.order.help_text }}"
-                                    data-model="service.values.order"
-                                    data-errors="service.errors.order">
-                                </formgroup>
-                            </div>
-                        </div>
-                        <formgroup
-                            data-id="option_additional_input"
-                            data-type="checkbox"
-                            data-label="{{ meta.Option.additional_input.verbose_name }}"
-                            data-help="{{ meta.Option.additional_input.help_text }}"
-                            data-model="service.values.additional_input"
-                            data-errors="service.errors.additional_input">
-                        </formgroup>
-                        <div>
-                            <ul class="nav nav-tabs" role="tablist">
-                                {% for lang_code, lang in languages %}
-                                <li role="presentation" class="{{ forloop.first|yesno:'active,' }}" ng-class="{
-                                    'has-error':
-                                        service.errors.text_{{ lang_code }},
-                                    'has-warning':
-                                        service.values.id && !service.values.text_{{ lang_code }}
-                                }">
-                                    <a role="tab" class="control-label" data-toggle="tab"
-                                        href="#options-{{ lang_code }}" aria-controls="options-{{ lang_code }}">
-
-                                        {{ lang }}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endfor %}
-                                {% if settings.MULTISITE %}
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.editors,
-                                }">
-                                    <a class="control-label" href="#options-editors"
-                                        role="tab" data-toggle="tab" aria-controls="options-editors">
-
-                                        {% trans 'Editors' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endif %}
-                            </ul>
-                            <div class="tab-content">
-                                {% for lang_code, lang in languages %}
-                                <div role="tabpanel" id="options-{{ lang_code }}"
-                                    class="tab-pane {{ forloop.first|yesno:'active,' }}">
-
-                                    <formgroup
-                                        data-id="option_text_{{ lang_code }}"
-                                        data-type="text"
-                                        data-label="{% trans 'Text' %} ({{ lang }})"
-                                        data-help="{% trans 'The title for this option.' %}"
-                                        data-model="service.values.text_{{ lang_code }}"
-                                        data-errors="service.errors.text_{{ lang_code }}"
-                                        data-warning="service.values.id && !service.values.text_{{ lang_code }}">
-                                    </formgroup>
-                                </div>
-                                {% endfor %}
-                            {% if settings.MULTISITE %}
-                            <div role="tabpanel" class="tab-pane" id="options-editors">
-                                <formgroup
-                                    data-id="options_editors"
-                                    data-type="selectmultiple"
-                                    data-label="{% trans 'Editors' %}"
-                                    data-model="service.values.editors"
-                                    data-errors="service.errors.editors"
-                                    data-options="service.sites"
-                                    data-options-label="name"
-                                    data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                </formgroup>
-                            </div>
-                            {% endif %}
-                            </div>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitFormModal('options')">
-                        {% trans 'Save' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/options/templates/options/options_modal_form_optionsets.html b/rdmo/options/templates/options/options_modal_form_optionsets.html
deleted file mode 100644
index 427209df61..0000000000
--- a/rdmo/options/templates/options/options_modal_form_optionsets.html
+++ /dev/null
@@ -1,141 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="optionsets-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        <span ng-hide="service.values.id">{% trans 'Create option set' %}</span>
-                        <span ng-show="service.values.copy">{% trans 'Copy option set' %}</span>
-                        <span ng-show="service.values.id && !service.values.copy">{% trans 'Update option set' %}</span>
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <div class="copy-block" ng-show="service.values.copy">
-                        <p>
-                            {% trans 'Please enter the values for the copy of the option set below and change at least the <strong>key</strong>.' %}
-                        </p>
-                        <p>
-                            {% trans 'Other properties can be changed once the new option set is created.' %}
-                        </p>
-                    </div>
-                    <div class="row">
-                        <div class="col-sm-6">
-                            <div class="form-group" ng-class="{'has-error': service.errors.uri_prefix }">
-                                <label class="control-label" for="uri_prefix">
-                                    {{ meta.OptionSet.uri_prefix.verbose_name }}
-                                </label>
-
-                                <div class="input-group">
-                                    <input class="form-control"
-                                           id="optionset_uri_prefix"
-                                           type="text"
-                                           ng-model="service.values.uri_prefix" />
-
-                                    <span class="input-group-btn">
-                                        <button type="button" class="btn btn-default"
-                                            title="{% trans 'Insert default URI Prefix' %}"
-                                            ng-click="service.values.uri_prefix = service.settings.default_uri_prefix">
-
-                                            <span class="fa fa-magic"></span>
-                                        </button>
-                                    </span>
-                                </div>
-
-                                <p class="help-block">
-                                    {{ meta.OptionSet.uri_prefix.help_text }}
-                                </p>
-
-                                <ul class="help-block list-unstyled" ng-show="service.errors.uri_prefix">
-                                    <li ng-repeat="error in service.errors.uri_prefix">
-                                        {$ error $}
-                                    </li>
-                                </ul>
-                            </div>
-                        </div>
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="optionset_key"
-                                data-type="text"
-                                data-label="{{ meta.OptionSet.key.verbose_name }}"
-                                data-help="{{ meta.OptionSet.key.help_text }}"
-                                data-model="service.values.key"
-                                data-errors="service.errors.key">
-                            </formgroup>
-                        </div>
-                    </div>
-
-                    <div ng-hide="service.values.copy">
-                        <formgroup
-                            data-id="optionset_comment"
-                            data-type="textarea"
-                            data-label="{{ meta.OptionSet.comment.verbose_name }}"
-                            data-help="{{ meta.OptionSet.comment.help_text }}"
-                            data-model="service.values.comment"
-                            data-errors="service.errors.comment">
-                        </formgroup>
-                        <formgroup
-                            data-id="optionset_locked"
-                            data-type="checkbox"
-                            data-label="{{ meta.OptionSet.locked.verbose_name }}"
-                            data-help="{{ meta.OptionSet.locked.help_text }}"
-                            data-model="service.values.locked"
-                            data-errors="service.errors.locked">
-                        </formgroup>
-                        <formgroup
-                            data-id="optionset_order"
-                            data-type="number"
-                            data-label="{{ meta.OptionSet.order.verbose_name }}"
-                            data-help="{{ meta.OptionSet.order.help_text }}"
-                            data-model="service.values.order"
-                            data-errors="service.errors.order">
-                        </formgroup>
-                        <formgroup
-                            ng-show="service.providers.length"
-                            data-id="optionset_provider"
-                            data-type="select"
-                            data-label="{{ meta.OptionSet.provider_key.verbose_name }}"
-                            data-help="{{ meta.OptionSet.provider_key.help_text }}"
-                            data-model="service.values.provider_key"
-                            data-errors="service.errors.provider_key"
-                            data-options="service.providers"
-                            data-options-label="text"
-                            data-options-null="1">
-                        </formgroup>
-                        {% if settings.MULTISITE %}
-                        <div role="tabpanel" class="tab-pane" id="optionset-editors">
-                            <formgroup
-                                data-id="optionset_editors"
-                                data-type="selectmultiple"
-                                data-label="{% trans 'Editors' %}"
-                                data-model="service.values.editors"
-                                data-errors="service.errors.editors"
-                                data-options="service.sites"
-                                data-options-label="name"
-                                data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                            </formgroup>
-                        </div>
-                        {% endif %}
-                    </div>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitFormModal('optionsets')">
-                        {% trans 'Save' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/options/templates/options/options_modal_show_options.html b/rdmo/options/templates/options/options_modal_show_options.html
deleted file mode 100644
index 14597d1dea..0000000000
--- a/rdmo/options/templates/options/options_modal_show_options.html
+++ /dev/null
@@ -1,39 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="options-show-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">{% trans 'Option references' %}</h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with values_count='{$ service.values.values_count $}' projects_count='{$ service.values.projects_count $}' %}
-                        This option is used for <b>{{ values_count }} values</b> in <b>{{ projects_count }} projects</b>.
-                        {% endblocktrans %}
-                        {% trans 'Furthermore, this attribute is used in the following elements:' %}
-                    </p>
-
-                    <h6>{% trans 'Conditions' %}</h6>
-                    <ul>
-                        <li ng-repeat="condition in service.values.conditions">
-                            <code>{$ condition.uri $}</code>
-                        </li>
-                        <li ng-hide="service.values.conditions.length">
-                            {% trans 'none' %}
-                        </li>
-                    </ul>
-                </div>
-
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/options/templates/options/options_modal_show_optionsets.html b/rdmo/options/templates/options/options_modal_show_optionsets.html
deleted file mode 100644
index 58428b82e4..0000000000
--- a/rdmo/options/templates/options/options_modal_show_optionsets.html
+++ /dev/null
@@ -1,36 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="optionsets-show-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">{% trans 'Option set references' %}</h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% trans 'This option set is used in the following elements:' %}
-                    </p>
-
-                    <h6>{% trans 'Questions' %}</h6>
-                    <ul>
-                        <li ng-repeat="question in service.values.questions">
-                            <code>{$ question.uri $}</code>
-                        </li>
-                        <li ng-hide="service.values.questions.length">
-                            {% trans 'none' %}
-                        </li>
-                    </ul>
-                </div>
-
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/options/urls/__init__.py b/rdmo/options/urls/__init__.py
index 791b137203..e69de29bb2 100644
--- a/rdmo/options/urls/__init__.py
+++ b/rdmo/options/urls/__init__.py
@@ -1,8 +0,0 @@
-from django.urls import re_path
-
-from ..views import OptionsExportView, OptionsView
-
-urlpatterns = [
-    re_path(r'^$', OptionsView.as_view(), name='options'),
-    re_path(r'^export/(?P<format>[a-z]+)/$', OptionsExportView.as_view(), name='options_export'),
-]
diff --git a/rdmo/options/views.py b/rdmo/options/views.py
deleted file mode 100644
index 8fe0b237fb..0000000000
--- a/rdmo/options/views.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import logging
-
-from django.conf import settings
-from django.utils.translation import gettext_lazy as _
-from django.views.generic import ListView, TemplateView
-
-from rdmo.core.exports import XMLResponse
-from rdmo.core.utils import get_model_field_meta, render_to_format
-from rdmo.core.views import CSRFViewMixin, ModelPermissionMixin
-
-from .models import Option, OptionSet
-from .renderers import OptionSetRenderer
-from .serializers.export import OptionSetExportSerializer
-
-log = logging.getLogger(__name__)
-
-
-class OptionsView(ModelPermissionMixin, CSRFViewMixin, TemplateView):
-    template_name = 'options/options.html'
-    permission_required = 'options.view_option'
-
-    def get_context_data(self, **kwargs):
-        context = super(OptionsView, self).get_context_data(**kwargs)
-        context['export_formats'] = settings.EXPORT_FORMATS
-        context['meta'] = {
-            'OptionSet': get_model_field_meta(OptionSet),
-            'Option': get_model_field_meta(Option)
-        }
-        return context
-
-
-class OptionsExportView(ModelPermissionMixin, ListView):
-    model = OptionSet
-    context_object_name = 'optionsets'
-    permission_required = 'options.view_optionset'
-
-    def render_to_response(self, context, **response_kwargs):
-        format = self.kwargs.get('format')
-        if format == 'xml':
-            serializer = OptionSetExportSerializer(context['optionsets'], many=True)
-            xml = OptionSetRenderer().render(serializer.data)
-            return XMLResponse(xml, name='options')
-        else:
-            return render_to_format(self.request, format, _('Options'), 'options/options_export.html', context)
diff --git a/rdmo/options/viewsets.py b/rdmo/options/viewsets.py
index d249702b27..8e423b85de 100644
--- a/rdmo/options/viewsets.py
+++ b/rdmo/options/viewsets.py
@@ -11,26 +11,25 @@
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
 from rdmo.core.utils import is_truthy, render_to_format
 from rdmo.core.views import ChoicesViewSet
-from rdmo.core.viewsets import CopyModelMixin
 
 from .models import Option, OptionSet
 from .renderers import OptionRenderer, OptionSetRenderer
 from .serializers.export import (OptionExportSerializer,
                                  OptionSetExportSerializer)
 from .serializers.v1 import (OptionIndexSerializer, OptionSerializer,
-                             OptionSetIndexSerializer, OptionSetListSerializer,
+                             OptionSetIndexSerializer,
                              OptionSetNestedSerializer, OptionSetSerializer)
 
 
-class OptionSetViewSet(CopyModelMixin, ModelViewSet):
+class OptionSetViewSet(ModelViewSet):
     permission_classes = (HasModelPermission | HasObjectPermission, )
+    serializer_class = OptionSetSerializer
     queryset = OptionSet.objects.prefetch_related(
         'optionset_options__option',
         'conditions',
         'questions',
         'editors'
     )
-    serializer_class = OptionSetSerializer
 
     filter_backends = (SearchFilter, DjangoFilterBackend)
     search_fields = ('uri', )
@@ -41,9 +40,6 @@ class OptionSetViewSet(CopyModelMixin, ModelViewSet):
         'comment'
     )
 
-    def get_serializer_class(self):
-        return OptionSetListSerializer if self.action == 'list' else OptionSetSerializer
-
     @action(detail=False)
     def index(self, request):
         queryset = self.filter_queryset(self.get_queryset())
@@ -87,12 +83,12 @@ def get_export_renderer_context(self, request):
         }
 
 
-class OptionViewSet(CopyModelMixin, ModelViewSet):
+class OptionViewSet(ModelViewSet):
     permission_classes = (HasModelPermission | HasObjectPermission, )
+    serializer_class = OptionSerializer
     queryset = Option.objects.annotate(values_count=models.Count('values')) \
                              .annotate(projects_count=models.Count('values__project', distinct=True)) \
                              .prefetch_related('optionsets', 'conditions', 'editors')
-    serializer_class = OptionSerializer
 
     filter_backends = (SearchFilter, DjangoFilterBackend)
     search_fields = ('uri', 'text')
diff --git a/rdmo/projects/viewsets.py b/rdmo/projects/viewsets.py
index d0e98f92cc..e982704bc8 100644
--- a/rdmo/projects/viewsets.py
+++ b/rdmo/projects/viewsets.py
@@ -405,10 +405,10 @@ def get_continue(self, request, pk=None, parent_lookup_project=None):
             try:
                 page = Page.objects.filter_by_catalog(self.project.catalog).get(id=continuation.page_id)
             except Page.DoesNotExist:
-                page = self.get_queryset().first()
+                page = self.project.catalog.pages[0]
 
         except Continuation.DoesNotExist:
-            page = self.get_queryset().first()
+            page = self.project.catalog.pages[0]
 
         serializer = self.get_serializer(page)
         return Response(serializer.data)
diff --git a/rdmo/questions/imports.py b/rdmo/questions/imports.py
index 7a9480223c..4616312036 100644
--- a/rdmo/questions/imports.py
+++ b/rdmo/questions/imports.py
@@ -2,9 +2,9 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (set_common_fields, set_foreign_field,
-                               set_lang_field, set_m2m_instances,
-                               set_m2m_through_instances,
+from rdmo.core.imports import (check_permissions, set_common_fields,
+                               set_foreign_field, set_lang_field,
+                               set_m2m_instances, set_m2m_through_instances,
                                set_reverse_m2m_through_instance,
                                validate_instance)
 
@@ -20,7 +20,7 @@
 logger = logging.getLogger(__name__)
 
 
-def import_catalog(element, save=False):
+def import_catalog(element, save=False, user=None):
     try:
         catalog = Catalog.objects.get(uri=element.get('uri'))
     except Catalog.DoesNotExist:
@@ -37,6 +37,8 @@ def import_catalog(element, save=False):
 
     validate_instance(catalog, element, CatalogLockedValidator, CatalogUniqueURIValidator)
 
+    check_permissions(catalog, element, user)
+
     if save and not element.get('errors'):
         if catalog.id:
             element['updated'] = True
@@ -53,7 +55,7 @@ def import_catalog(element, save=False):
     return catalog
 
 
-def import_section(element, save=False):
+def import_section(element, save=False, user=None):
     try:
         section = Section.objects.get(uri=element.get('uri'))
     except Section.DoesNotExist:
@@ -65,6 +67,8 @@ def import_section(element, save=False):
 
     validate_instance(section, element, SectionLockedValidator, SectionUniqueURIValidator)
 
+    check_permissions(section, element, user)
+
     if save and not element.get('errors'):
         if section.id:
             element['updated'] = True
@@ -81,7 +85,7 @@ def import_section(element, save=False):
     return section
 
 
-def import_page(element, save=False):
+def import_page(element, save=False, user=None):
     try:
         page = Page.objects.get(uri=element.get('uri'))
     except Page.DoesNotExist:
@@ -99,6 +103,8 @@ def import_page(element, save=False):
 
     validate_instance(page, element, PageLockedValidator, PageUniqueURIValidator)
 
+    check_permissions(page, element, user)
+
     if save and not element.get('errors'):
         if page.id:
             element['updated'] = True
@@ -117,7 +123,7 @@ def import_page(element, save=False):
     return page
 
 
-def import_questionset(element, save=False):
+def import_questionset(element, save=False, user=None):
     try:
         questionset = QuestionSet.objects.get(uri=element.get('uri'))
     except QuestionSet.DoesNotExist:
@@ -135,6 +141,8 @@ def import_questionset(element, save=False):
 
     validate_instance(questionset, element, QuestionSetLockedValidator, QuestionSetUniqueURIValidator)
 
+    check_permissions(questionset, element, user)
+
     if save and not element.get('errors'):
         if questionset.id:
             element['updated'] = True
@@ -154,7 +162,7 @@ def import_questionset(element, save=False):
     return questionset
 
 
-def import_question(element, save=False):
+def import_question(element, save=False, user=None):
     try:
         question = Question.objects.get(uri=element.get('uri'))
     except Question.DoesNotExist:
@@ -190,6 +198,8 @@ def import_question(element, save=False):
 
     validate_instance(question, element, QuestionLockedValidator, QuestionUniqueURIValidator)
 
+    check_permissions(question, element, user)
+
     if save and not element.get('errors'):
         if question.id:
             element['updated'] = True
diff --git a/rdmo/questions/models/catalog.py b/rdmo/questions/models/catalog.py
index 5f7634f822..03e18f8643 100644
--- a/rdmo/questions/models/catalog.py
+++ b/rdmo/questions/models/catalog.py
@@ -149,24 +149,6 @@ def save(self, *args, **kwargs):
         self.uri = self.build_uri(self.uri_prefix, self.uri_path)
         super().save(*args, **kwargs)
 
-    def copy(self, uri_prefix, uri_path):
-        # create a new title
-        kwargs = {}
-        for field in get_language_fields('title'):
-            kwargs[field] = getattr(self, field) + '*'
-
-        # copy instance
-        catalog = copy_model(self, uri_prefix=uri_prefix, uri_path=uri_path, **kwargs)
-
-        # copy m2m fields
-        catalog.sections.set(self.sections.all())
-        catalog.sites.set(self.sites.all())
-
-        # for groups, copy all
-        catalog.groups.set(self.groups.all())
-
-        return catalog
-
     @property
     def title(self):
         return self.trans('title')
diff --git a/rdmo/questions/models/page.py b/rdmo/questions/models/page.py
index 15c3b3f4f2..22f3d0cfd1 100644
--- a/rdmo/questions/models/page.py
+++ b/rdmo/questions/models/page.py
@@ -199,17 +199,6 @@ def save(self, *args, **kwargs):
         self.uri = self.build_uri(self.uri_prefix, self.uri_path)
         super().save(*args, **kwargs)
 
-    def copy(self, uri_prefix, uri_path):
-        page = copy_model(self, uri_prefix=uri_prefix, uri_path=uri_path,
-                          attribute=self.attribute)
-
-        # copy m2m fields
-        page.questionsets.set(self.questionsets.all())
-        page.questions.set(self.questions.all())
-        page.conditions.set(self.conditions.all())
-
-        return page
-
     @property
     def title(self):
         return self.trans('title')
diff --git a/rdmo/questions/models/question.py b/rdmo/questions/models/question.py
index d955b17b2b..2b96a52354 100644
--- a/rdmo/questions/models/question.py
+++ b/rdmo/questions/models/question.py
@@ -261,15 +261,6 @@ def save(self, *args, **kwargs):
         self.uri = self.build_uri(self.uri_prefix, self.uri_path)
         super().save(*args, **kwargs)
 
-    def copy(self, uri_prefix, uri_path):
-        question = copy_model(self, uri_prefix=uri_prefix, uri_path=uri_path, attribute=self.attribute)
-
-        # copy m2m fields
-        question.optionsets.set(self.optionsets.all())
-        question.conditions.set(self.conditions.all())
-
-        return question
-
     @property
     def text(self):
         return self.trans('text')
diff --git a/rdmo/questions/models/questionset.py b/rdmo/questions/models/questionset.py
index af0d7af336..7b60247c55 100644
--- a/rdmo/questions/models/questionset.py
+++ b/rdmo/questions/models/questionset.py
@@ -194,16 +194,6 @@ def save(self, *args, **kwargs):
         self.uri = self.build_uri(self.uri_prefix, self.uri_path)
         super().save(*args, **kwargs)
 
-    def copy(self, uri_prefix, uri_path):
-        questionset = copy_model(self, uri_prefix=uri_prefix, uri_path=uri_path, attribute=self.attribute)
-
-        # copy m2m fields
-        questionset.questionsets.set(self.questionsets.all())
-        questionset.questions.set(self.questions.all())
-        questionset.conditions.set(self.conditions.all())
-
-        return questionset
-
     @property
     def title(self):
         return self.trans('title')
diff --git a/rdmo/questions/models/section.py b/rdmo/questions/models/section.py
index 067dc123f2..6a634e54e5 100644
--- a/rdmo/questions/models/section.py
+++ b/rdmo/questions/models/section.py
@@ -102,14 +102,6 @@ def save(self, *args, **kwargs):
         self.uri = self.build_uri(self.uri_prefix, self.uri_path)
         super().save(*args, **kwargs)
 
-    def copy(self, uri_prefix, uri_path):
-        section = copy_model(self, uri_prefix=uri_prefix, uri_path=uri_path)
-
-        # copy m2m fields
-        section.pages.set(self.pages.all())
-
-        return section
-
     @property
     def title(self):
         return self.trans('title')
diff --git a/rdmo/questions/serializers/v1/__init__.py b/rdmo/questions/serializers/v1/__init__.py
index e898e8ae36..0fde7e19c0 100644
--- a/rdmo/questions/serializers/v1/__init__.py
+++ b/rdmo/questions/serializers/v1/__init__.py
@@ -1,11 +1,8 @@
-from .catalog import (CatalogIndexSerializer, CatalogListSerializer,
-                      CatalogNestedSerializer, CatalogSerializer)
-from .page import (PageIndexSerializer, PageListSerializer,
-                   PageNestedSerializer, PageSerializer)
-from .question import (QuestionIndexSerializer, QuestionListSerializer,
-                       QuestionSerializer)
+from .catalog import (CatalogIndexSerializer, CatalogNestedSerializer,
+                      CatalogSerializer)
+from .page import PageIndexSerializer, PageNestedSerializer, PageSerializer
+from .question import QuestionIndexSerializer, QuestionSerializer
 from .questionset import (QuestionSetIndexSerializer,
-                          QuestionSetListSerializer,
                           QuestionSetNestedSerializer, QuestionSetSerializer)
-from .section import (SectionIndexSerializer, SectionListSerializer,
-                      SectionNestedSerializer, SectionSerializer)
+from .section import (SectionIndexSerializer, SectionNestedSerializer,
+                      SectionSerializer)
diff --git a/rdmo/questions/serializers/v1/catalog.py b/rdmo/questions/serializers/v1/catalog.py
index f901f52da9..4152f0df45 100644
--- a/rdmo/questions/serializers/v1/catalog.py
+++ b/rdmo/questions/serializers/v1/catalog.py
@@ -1,9 +1,8 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementExportSerializerMixin,
-                                   ElementModelSerializerMixin,
+from rdmo.core.serializers import (ElementModelSerializerMixin,
                                    ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionsSerializerMixin,
+                                   ReadOnlyObjectPermissionSerializerMixin,
                                    ThroughModelSerializerMixin,
                                    TranslationSerializerMixin)
 
@@ -23,15 +22,19 @@ class Meta:
 
 
 class CatalogSerializer(ThroughModelSerializerMixin, TranslationSerializerMixin,
-                        ElementModelSerializerMixin, ReadOnlyObjectPermissionsSerializerMixin,
-                        serializers.ModelSerializer):
+                        ElementModelSerializerMixin, ElementWarningSerializerMixin,
+                        ReadOnlyObjectPermissionSerializerMixin, serializers.ModelSerializer):
 
     model = serializers.SerializerMethodField()
     uri_path = serializers.CharField(required=True)
-    projects_count = serializers.IntegerField(read_only=True)
+
     sections = CatalogSectionSerializer(source='catalog_sections', read_only=False, required=False, many=True)
+
+    warning = serializers.SerializerMethodField()
     read_only = serializers.SerializerMethodField()
 
+    projects_count = serializers.IntegerField(read_only=True)
+
     class Meta:
         model = Catalog
         fields = (
@@ -47,10 +50,11 @@ class Meta:
             'sections',
             'sites',
             'editors',
-            'read_only',
             'groups',
             'title',
             'help',
+            'warning',
+            'read_only',
             'projects_count',
         )
         trans_fields = (
@@ -64,30 +68,17 @@ class Meta:
             CatalogUniqueURIValidator(),
             CatalogLockedValidator(),
         )
-
-
-class CatalogListSerializer(ElementExportSerializerMixin, ElementWarningSerializerMixin,
-                            CatalogSerializer):
-
-    warning = serializers.SerializerMethodField()
-    xml_url = serializers.SerializerMethodField()
-
-    class Meta(CatalogSerializer.Meta):
-        fields = CatalogSerializer.Meta.fields + (
-            'warning',
-            'xml_url'
-        )
         warning_fields = (
             'title',
         )
 
 
-class CatalogNestedSerializer(CatalogListSerializer):
+class CatalogNestedSerializer(CatalogSerializer):
 
     elements = serializers.SerializerMethodField()
 
-    class Meta(CatalogListSerializer.Meta):
-        fields = CatalogListSerializer.Meta.fields + (
+    class Meta(CatalogSerializer.Meta):
+        fields = CatalogSerializer.Meta.fields + (
             'elements',
         )
 
diff --git a/rdmo/questions/serializers/v1/page.py b/rdmo/questions/serializers/v1/page.py
index 7ef7743a39..970ea65251 100644
--- a/rdmo/questions/serializers/v1/page.py
+++ b/rdmo/questions/serializers/v1/page.py
@@ -1,15 +1,14 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementExportSerializerMixin,
-                                   ElementModelSerializerMixin,
+from rdmo.core.serializers import (ElementModelSerializerMixin,
                                    ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionsSerializerMixin,
+                                   ReadOnlyObjectPermissionSerializerMixin,
                                    ThroughModelSerializerMixin,
                                    TranslationSerializerMixin)
 
 from ...models import Page, PageQuestion, PageQuestionSet, QuestionSet, Section
 from ...validators import PageLockedValidator, PageUniqueURIValidator
-from .question import QuestionListSerializer
+from .question import QuestionSerializer
 from .questionset import QuestionSetNestedSerializer
 
 
@@ -34,16 +33,22 @@ class Meta:
 
 
 class PageSerializer(ThroughModelSerializerMixin, TranslationSerializerMixin,
-                     ElementModelSerializerMixin, ReadOnlyObjectPermissionsSerializerMixin,
-                     serializers.ModelSerializer):
+                     ElementModelSerializerMixin, ElementWarningSerializerMixin,
+                     ReadOnlyObjectPermissionSerializerMixin, serializers.ModelSerializer):
 
     model = serializers.SerializerMethodField()
     uri_path = serializers.CharField(required=True)
+
     sections = serializers.PrimaryKeyRelatedField(queryset=Section.objects.all(), required=False, many=True)
     questionsets = PageQuestionSetSerializer(source='page_questionsets', read_only=False, required=False, many=True)
     questions = PageQuestionSerializer(source='page_questions', read_only=False, required=False, many=True)
+
+    warning = serializers.SerializerMethodField()
     read_only = serializers.SerializerMethodField()
 
+    attribute_uri = serializers.CharField(source='attribute.uri', read_only=True)
+    condition_uris = serializers.SerializerMethodField()
+
     class Meta:
         model = Page
         fields = (
@@ -65,7 +70,10 @@ class Meta:
             'questions',
             'conditions',
             'editors',
-            'read_only'
+            'warning',
+            'read_only',
+            'attribute_uri',
+            'condition_uris'
         )
         trans_fields = (
             'title',
@@ -84,23 +92,6 @@ class Meta:
             PageUniqueURIValidator(),
             PageLockedValidator()
         )
-
-
-class PageListSerializer(ElementExportSerializerMixin, ElementWarningSerializerMixin,
-                         PageSerializer):
-
-    attribute_uri = serializers.CharField(source='attribute.uri', read_only=True)
-    condition_uris = serializers.SerializerMethodField()
-    warning = serializers.SerializerMethodField()
-    xml_url = serializers.SerializerMethodField()
-
-    class Meta(PageSerializer.Meta):
-        fields = PageSerializer.Meta.fields + (
-            'attribute_uri',
-            'condition_uris',
-            'warning',
-            'xml_url'
-        )
         warning_fields = (
             'title',
         )
@@ -109,12 +100,12 @@ def get_condition_uris(self, obj):
         return [condition.uri for condition in obj.conditions.all()]
 
 
-class PageNestedSerializer(PageListSerializer):
+class PageNestedSerializer(PageSerializer):
 
     elements = serializers.SerializerMethodField()
 
-    class Meta(PageListSerializer.Meta):
-        fields = PageListSerializer.Meta.fields + (
+    class Meta(PageSerializer.Meta):
+        fields = PageSerializer.Meta.fields + (
             'elements',
         )
 
@@ -123,7 +114,7 @@ def get_elements(self, obj):
             if isinstance(element, QuestionSet):
                 yield QuestionSetNestedSerializer(element, context=self.context).data
             else:
-                yield QuestionListSerializer(element, context=self.context).data
+                yield QuestionSerializer(element, context=self.context).data
 
 
 class PageIndexSerializer(serializers.ModelSerializer):
diff --git a/rdmo/questions/serializers/v1/question.py b/rdmo/questions/serializers/v1/question.py
index 1c615edd9e..fdf27c1203 100644
--- a/rdmo/questions/serializers/v1/question.py
+++ b/rdmo/questions/serializers/v1/question.py
@@ -1,9 +1,8 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementExportSerializerMixin,
-                                   ElementModelSerializerMixin,
+from rdmo.core.serializers import (ElementModelSerializerMixin,
                                    ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionsSerializerMixin,
+                                   ReadOnlyObjectPermissionSerializerMixin,
                                    ThroughModelSerializerMixin,
                                    TranslationSerializerMixin)
 
@@ -12,15 +11,22 @@
 
 
 class QuestionSerializer(ThroughModelSerializerMixin, TranslationSerializerMixin,
-                         ElementModelSerializerMixin, ReadOnlyObjectPermissionsSerializerMixin,
-                         serializers.ModelSerializer):
+                         ElementModelSerializerMixin, ElementWarningSerializerMixin,
+                         ReadOnlyObjectPermissionSerializerMixin, serializers.ModelSerializer):
 
     model = serializers.SerializerMethodField()
     uri_path = serializers.CharField(required=True)
+
     pages = serializers.PrimaryKeyRelatedField(queryset=Page.objects.all(), required=False, many=True)
     questionsets = serializers.PrimaryKeyRelatedField(queryset=QuestionSet.objects.all(), required=False, many=True)
+
+    warning = serializers.SerializerMethodField()
     read_only = serializers.SerializerMethodField()
 
+    attribute_uri = serializers.CharField(source='attribute.uri', read_only=True)
+    condition_uris = serializers.SerializerMethodField()
+    optionset_uris = serializers.SerializerMethodField()
+
     class Meta:
         model = Question
         fields = (
@@ -53,7 +59,11 @@ class Meta:
             'optionsets',
             'conditions',
             'editors',
-            'read_only'
+            'warning',
+            'read_only',
+            'attribute_uri',
+            'condition_uris',
+            'optionset_uris'
         )
         trans_fields = (
             'text',
@@ -70,6 +80,9 @@ class Meta:
             QuestionUniqueURIValidator(),
             QuestionLockedValidator()
         )
+        warning_fields = (
+            'text',
+        )
 
     def to_internal_value(self, data):
         # handles an empty width, maximum, minimum, or step field
@@ -79,28 +92,6 @@ def to_internal_value(self, data):
 
         return super().to_internal_value(data)
 
-
-class QuestionListSerializer(ElementExportSerializerMixin, ElementWarningSerializerMixin,
-                             QuestionSerializer):
-
-    attribute_uri = serializers.CharField(source='attribute.uri', read_only=True)
-    condition_uris = serializers.SerializerMethodField()
-    optionset_uris = serializers.SerializerMethodField()
-    warning = serializers.SerializerMethodField()
-    xml_url = serializers.SerializerMethodField()
-
-    class Meta(QuestionSerializer.Meta):
-        fields = QuestionSerializer.Meta.fields + (
-            'attribute_uri',
-            'condition_uris',
-            'optionset_uris',
-            'warning',
-            'xml_url'
-        )
-        warning_fields = (
-            'text',
-        )
-
     def get_condition_uris(self, obj):
         return [condition.uri for condition in obj.conditions.all()]
 
diff --git a/rdmo/questions/serializers/v1/questionset.py b/rdmo/questions/serializers/v1/questionset.py
index d0f884c28a..0a7d83c35f 100644
--- a/rdmo/questions/serializers/v1/questionset.py
+++ b/rdmo/questions/serializers/v1/questionset.py
@@ -1,9 +1,8 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementExportSerializerMixin,
-                                   ElementModelSerializerMixin,
+from rdmo.core.serializers import (ElementModelSerializerMixin,
                                    ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionsSerializerMixin,
+                                   ReadOnlyObjectPermissionSerializerMixin,
                                    ThroughModelSerializerMixin,
                                    TranslationSerializerMixin)
 
@@ -12,7 +11,7 @@
 from ...validators import (QuestionSetLockedValidator,
                            QuestionSetQuestionSetValidator,
                            QuestionSetUniqueURIValidator)
-from .question import QuestionListSerializer
+from .question import QuestionSerializer
 
 
 class QuestionSetQuestionSetSerializer(serializers.ModelSerializer):
@@ -36,17 +35,23 @@ class Meta:
 
 
 class QuestionSetSerializer(ThroughModelSerializerMixin, TranslationSerializerMixin,
-                            ElementModelSerializerMixin, ReadOnlyObjectPermissionsSerializerMixin,
-                            serializers.ModelSerializer):
+                            ElementModelSerializerMixin, ElementWarningSerializerMixin,
+                            ReadOnlyObjectPermissionSerializerMixin, serializers.ModelSerializer):
 
     model = serializers.SerializerMethodField()
     uri_path = serializers.CharField(required=True)
+
     pages = serializers.PrimaryKeyRelatedField(queryset=Page.objects.all(), required=False, many=True)
     parents = serializers.PrimaryKeyRelatedField(queryset=QuestionSet.objects.all(), required=False, many=True)
     questionsets = QuestionSetQuestionSetSerializer(source='questionset_questionsets', read_only=False, required=False, many=True)
     questions = QuestionSetQuestionSerializer(source='questionset_questions', read_only=False, required=False, many=True)
+
+    warning = serializers.SerializerMethodField()
     read_only = serializers.SerializerMethodField()
 
+    attribute_uri = serializers.CharField(source='attribute.uri', read_only=True)
+    condition_uris = serializers.SerializerMethodField()
+
     class Meta:
         model = QuestionSet
         fields = (
@@ -69,7 +74,10 @@ class Meta:
             'questions',
             'conditions',
             'editors',
-            'read_only'
+            'warning',
+            'read_only',
+            'attribute_uri',
+            'condition_uris'
         )
         trans_fields = (
             'title',
@@ -90,23 +98,6 @@ class Meta:
             QuestionSetQuestionSetValidator(),
             QuestionSetLockedValidator()
         )
-
-
-class QuestionSetListSerializer(ElementExportSerializerMixin, ElementWarningSerializerMixin,
-                                QuestionSetSerializer):
-
-    attribute_uri = serializers.CharField(source='attribute.uri', read_only=True)
-    condition_uris = serializers.SerializerMethodField()
-    warning = serializers.SerializerMethodField()
-    xml_url = serializers.SerializerMethodField()
-
-    class Meta(QuestionSetSerializer.Meta):
-        fields = QuestionSetSerializer.Meta.fields + (
-            'attribute_uri',
-            'condition_uris',
-            'warning',
-            'xml_url'
-        )
         warning_fields = (
             'title',
         )
@@ -115,12 +106,12 @@ def get_condition_uris(self, obj):
         return [condition.uri for condition in obj.conditions.all()]
 
 
-class QuestionSetNestedSerializer(QuestionSetListSerializer):
+class QuestionSetNestedSerializer(QuestionSetSerializer):
 
     elements = serializers.SerializerMethodField()
 
-    class Meta(QuestionSetListSerializer.Meta):
-        fields = QuestionSetListSerializer.Meta.fields + (
+    class Meta(QuestionSetSerializer.Meta):
+        fields = QuestionSetSerializer.Meta.fields + (
             'elements',
         )
 
@@ -129,7 +120,7 @@ def get_elements(self, obj):
             if isinstance(element, QuestionSet):
                 yield QuestionSetNestedSerializer(element, context=self.context).data
             else:
-                yield QuestionListSerializer(element, context=self.context).data
+                yield QuestionSerializer(element, context=self.context).data
 
 
 class QuestionSetIndexSerializer(serializers.ModelSerializer):
diff --git a/rdmo/questions/serializers/v1/section.py b/rdmo/questions/serializers/v1/section.py
index be08756c89..34d14fe6ac 100644
--- a/rdmo/questions/serializers/v1/section.py
+++ b/rdmo/questions/serializers/v1/section.py
@@ -1,9 +1,8 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementExportSerializerMixin,
-                                   ElementModelSerializerMixin,
+from rdmo.core.serializers import (ElementModelSerializerMixin,
                                    ElementWarningSerializerMixin,
-                                   ReadOnlyObjectPermissionsSerializerMixin,
+                                   ReadOnlyObjectPermissionSerializerMixin,
                                    ThroughModelSerializerMixin,
                                    TranslationSerializerMixin)
 
@@ -23,13 +22,16 @@ class Meta:
 
 
 class SectionSerializer(ThroughModelSerializerMixin, TranslationSerializerMixin,
-                        ElementModelSerializerMixin, ReadOnlyObjectPermissionsSerializerMixin,
-                        serializers.ModelSerializer):
+                        ElementModelSerializerMixin, ElementWarningSerializerMixin,
+                        ReadOnlyObjectPermissionSerializerMixin, serializers.ModelSerializer):
 
     model = serializers.SerializerMethodField()
     uri_path = serializers.CharField(required=True)
+
     catalogs = serializers.PrimaryKeyRelatedField(queryset=Catalog.objects.all(), required=False, many=True)
     pages = SectionPageSerializer(source='section_pages', read_only=False, required=False, many=True)
+
+    warning = serializers.SerializerMethodField()
     read_only = serializers.SerializerMethodField()
 
     class Meta:
@@ -46,6 +48,7 @@ class Meta:
             'catalogs',
             'pages',
             'editors',
+            'warning',
             'read_only'
         )
         trans_fields = (
@@ -61,30 +64,17 @@ class Meta:
             SectionUniqueURIValidator(),
             SectionLockedValidator()
         )
-
-
-class SectionListSerializer(ElementExportSerializerMixin, ElementWarningSerializerMixin,
-                            SectionSerializer):
-
-    warning = serializers.SerializerMethodField()
-    xml_url = serializers.SerializerMethodField()
-
-    class Meta(SectionSerializer.Meta):
-        fields = SectionSerializer.Meta.fields + (
-            'warning',
-            'xml_url'
-        )
         warning_fields = (
             'title',
         )
 
 
-class SectionNestedSerializer(SectionListSerializer):
+class SectionNestedSerializer(SectionSerializer):
 
     elements = serializers.SerializerMethodField()
 
-    class Meta(SectionListSerializer.Meta):
-        fields = SectionListSerializer.Meta.fields + (
+    class Meta(SectionSerializer.Meta):
+        fields = SectionSerializer.Meta.fields + (
             'elements',
         )
 
diff --git a/rdmo/questions/static/questions/css/catalogs.scss b/rdmo/questions/static/questions/css/catalogs.scss
deleted file mode 100644
index 8f73efc261..0000000000
--- a/rdmo/questions/static/questions/css/catalogs.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-.modal table {
-    margin-bottom: 5px;
-}
-
-.panel-sites {
-    ul {
-        margin: 0;
-    }
-}
-
-.indent {
-    margin-left: 20px;
-}
-
-.list-showURI-checkboxes {
-    list-style: none;
-    padding-left: 0;
-    column-count: 2;
-    margin-bottom: 0;
-}
-
-.showURI-checkbox {
-    margin-top: 0 !important;
-    margin-bottom: 0 !important;
-
-    &:nth-child(odd) {
-        margin-bottom: 15px !important;
-    }
-}
diff --git a/rdmo/questions/static/questions/js/catalogs.js b/rdmo/questions/static/questions/js/catalogs.js
deleted file mode 100644
index 6da8a21f5e..0000000000
--- a/rdmo/questions/static/questions/js/catalogs.js
+++ /dev/null
@@ -1,386 +0,0 @@
-angular.module('catalogs', ['core'])
-
-.config(['$locationProvider', function($locationProvider) {
-
-    // set $location to not use #
-    $locationProvider.html5Mode(true);
-
-}])
-
-.factory('CatalogsService', ['$resource', '$timeout', '$window', '$q', '$location', '$filter', 'utils', function($resource, $timeout, $window, $q, $location, $filter, utils) {
-
-    /* get the base url and the site_id */
-
-    var baseurl = angular.element('meta[name="baseurl"]').attr('content');
-    var site_id = angular.element('meta[name="site_id"]').attr('content') || 1;
-
-    /* configure resources */
-
-    var resources = {
-        catalogs: $resource(baseurl + 'api/v1/questions/catalogs/:list_action/:id/:detail_action/'),
-        sections: $resource(baseurl + 'api/v1/questions/sections/:list_action/:id/:detail_action/'),
-        questionsets: $resource(baseurl + 'api/v1/questions/questionsets/:list_action/:id/:detail_action/'),
-        questions: $resource(baseurl + 'api/v1/questions/questions/:list_action/:id/:detail_action/'),
-        attributes: $resource(baseurl + 'api/v1/domain/attributes/:list_action/:id/'),
-        optionsets: $resource(baseurl + 'api/v1/options/optionsets/:list_action/:id/'),
-        conditions: $resource(baseurl + 'api/v1/conditions/conditions/:list_action/:id/'),
-        widgettypes: $resource(baseurl + 'api/v1/questions/widgettypes/'),
-        valuetypes: $resource(baseurl + 'api/v1/questions/valuetypes/'),
-        settings: $resource(baseurl + 'api/v1/core/settings/'),
-        sites: $resource(baseurl + 'api/v1/core/sites/'),
-        groups: $resource(baseurl + 'api/v1/core/groups/'),
-    };
-
-    /* configure factories */
-
-    var factories = {
-        catalogs: function(parent) {
-            return {
-                order: 0,
-                sites: [1],
-                uri_prefix: service.settings.default_uri_prefix,
-                available: false
-            };
-        },
-        sections: function(parent) {
-            return {
-                catalog: (angular.isDefined(parent) && parent) ? parent.id : null,
-                order: 0,
-                uri_prefix: (angular.isDefined(parent) && parent) ? parent.uri_prefix : service.settings.default_uri_prefix
-            };
-        },
-        questionsets: function(parent) {
-            var section = null, questionset = null;
-            if (angular.isDefined(parent) && parent) {
-                if (parent.section) {
-                    section = parent.section
-                    questionset = parent.id;
-                } else {
-                    section = parent.id;
-                }
-            }
-            return {
-                section: section,
-                questionset: questionset,
-                attribute: null,
-                order: 0,
-                uri_prefix: (angular.isDefined(parent) && parent) ? parent.uri_prefix : service.settings.default_uri_prefix
-            };
-        },
-        questions: function(parent) {
-            return {
-                questionset: (angular.isDefined(parent) && parent) ? parent.id : null,
-                attribute: null,
-                order: 0,
-                uri_prefix: (angular.isDefined(parent) && parent) ? parent.uri_prefix : service.settings.default_uri_prefix,
-                default_option: null
-            };
-        }
-    };
-
-    /* create the questions service */
-
-    var service = {};
-
-    service.init = function() {
-        service.widgettypes = resources.widgettypes.query();
-        service.valuetypes = resources.valuetypes.query();
-        service.settings = resources.settings.get();
-        service.sites = resources.sites.query();
-        service.groups = resources.groups.query();
-        service.uri_prefixes = []
-        service.uri_prefix = ''
-        service.options = []
-        service.filter = sessionStorage.getItem('questions_filter') || '';
-        service.showQuestionSets = !(sessionStorage.getItem('options_showQuestionSets') === 'false');
-        service.showQuestions = !(sessionStorage.getItem('options_showOptions') === 'false');
-        // showURI* attributes for blending out the URIs via checkboxes
-        service.showURIs = Boolean(sessionStorage.getItem('options_showURIs'));
-        service.showURIQuestions = Boolean(sessionStorage.getItem('options_showURIQuestions'));
-        service.showURIConditions = Boolean(sessionStorage.getItem('options_showURIConditions'));
-        service.showURIOptions = Boolean(sessionStorage.getItem('options_showURIOptions'));
-        service.showURIAttributes = Boolean(sessionStorage.getItem('options_showURIAttributes'));
-
-        if (angular.isUndefined(service.filterCatalogsCurrentSite)) {
-            if (sessionStorage.getItem('options_filterCatalogsCurrentSite') === null) {
-                // when user arrives on page for the first time, set filterCatalogsCurrentSite to true
-                service.filterCatalogsCurrentSite = true;
-            } else if (sessionStorage.getItem('options_filterCatalogsCurrentSite') === 'false') {
-                // when user switches language and options_filterCatalogsCurrentSite was false before
-                service.filterCatalogsCurrentSite = false;
-            } else if (sessionStorage.getItem('options_filterCatalogsCurrentSite') === 'true') {
-                // when user switches language and options_filterCatalogsCurrentSite was true before
-                service.filterCatalogsCurrentSite = true;
-            }
-        }
-
-        var catalogs_params = {
-            list_action: 'index'
-        }
-        if (service.filterCatalogsCurrentSite) {
-            // query only catalogs for this site
-            catalogs_params['sites'] = site_id
-        }
-
-        resources.catalogs.query(catalogs_params, function(response) {
-            service.catalogs = response;
-
-            // try to get the catalog from the address bar
-            var catalog_id = $location.path().replace(/\//g,'');
-
-            // check if catalog_id is in the array of catalogs
-            if (catalog_id) {
-                // check if catalog_id is in catalogs
-                // required for when user switches filterCatalogsCurrentSite
-                var catalog_id_isin_catalogs = $filter('filter')(service.catalogs, {'id': catalog_id});
-                if (catalog_id_isin_catalogs.length > 0) {
-                    service.current_catalog_id = catalog_id;
-                } else {
-                    // the catalog is (propably) not available for this site
-                    service.current_catalog_id = service.catalogs[0].id;
-                }
-            } else if (service.catalogs.length > 0) {
-                service.current_catalog_id = service.catalogs[0].id;
-            } else {
-                // no catalogs are available
-                service.current_catalog_id = null;
-            };
-
-            service.initView().then(function() {
-                var current_scroll_pos = sessionStorage.getItem('questions_scroll_pos');
-                if (current_scroll_pos) {
-                    $timeout(function() {
-                        $window.scrollTo(0, current_scroll_pos);
-                    });
-                }
-            });
-        });
-
-        $window.addEventListener('beforeunload', function() {
-            sessionStorage.setItem('questions_scroll_pos', $window.scrollY);
-            sessionStorage.setItem('questions_filter', service.filter);
-            sessionStorage.setItem('options_showQuestionSets', service.showQuestionSets);
-            sessionStorage.setItem('options_showOptions', service.showQuestions);
-            sessionStorage.setItem('options_showURIs', service.showURIs);
-            sessionStorage.setItem('options_showURIQuestions', service.showURIQuestions);
-            sessionStorage.setItem('options_showURIConditions', service.showURIConditions);
-            sessionStorage.setItem('options_showURIOptions', service.showURIOptions);
-            sessionStorage.setItem('options_showURIAttributes', service.showURIAttributes);
-            sessionStorage.setItem('options_filterCatalogsCurrentSite', service.filterCatalogsCurrentSite);
-        });
-    };
-
-    service.initView = function() {
-        if (service.current_catalog_id) {
-            $location.path('/' + service.current_catalog_id + '/');
-
-            service.sections = resources.sections.query({list_action: 'index'});
-            service.questionsets = resources.questionsets.query({list_action: 'index'});
-
-            var catalog_promise = resources.catalogs.get({
-                id: service.current_catalog_id,
-                detail_action: 'nested'
-            }, function(response) {
-                service.catalog = response;
-                service.uri_prefixes = [service.catalog.uri_prefix];
-
-                // loop over all elements in the catalog to
-                // (a) construct list of uri_prefixes
-                // (b) sort questionsets and questions by order in one list called elements
-                // using recursive functions!
-                service.catalog.sections.map(service.initSection);
-
-            }).$promise;
-
-            return $q.all([
-                service.sections.$promise,
-                service.questionsets.$promise,
-                catalog_promise
-            ]);
-        } else {
-            $location.path('/');
-            service.catalog = {};
-            service.sections = [];
-            service.questionsets = [];
-            return $q.resolve();
-        }
-    };
-
-    service.initSection = function(section) {
-        if (service.uri_prefixes.indexOf(section.uri_prefix) < 0) {
-            service.uri_prefixes.push(section.uri_prefix)
-        }
-        section.questionsets.map(service.initQuestionSet);
-    };
-
-    service.initQuestionSet = function(questionset) {
-        if (service.uri_prefixes.indexOf(questionset.uri_prefix) < 0) {
-            service.uri_prefixes.push(questionset.uri_prefix)
-        }
-        questionset.questionset = true;
-        questionset.elements = questionset.questionsets.map(service.initQuestionSet)
-                       .concat(questionset.questions.map(service.initQuestion))
-                       .sort(function(a, b) {
-                           return a.order - b.order;
-                       });
-        return questionset
-    };
-
-    service.initQuestion = function(question) {
-        if (service.uri_prefixes.indexOf(question.uri_prefix) < 0) {
-            service.uri_prefixes.push(question.uri_prefix)
-        }
-        question.question = true;
-        return question
-    };
-
-    service.openFormModal = function(resource, obj, create, copy) {
-        var promises = [];
-
-        service.errors = {};
-        service.values = utils.fetchValues(resources[resource], factories[resource], obj, create, copy);
-        promises.push(service.values.$promise);
-
-        if (resource === 'questionsets' || resource === 'questions') {
-            service.attributes = resources.attributes.query({list_action: 'index'});
-            promises.push(service.attributes.$promise);
-
-            service.conditions = resources.conditions.query({list_action: 'index'});
-            promises.push(service.conditions.$promise);
-        }
-
-        if (resource === 'questions') {
-            service.optionsets = resources.optionsets.query({list_action: 'index'});
-            promises.push(service.optionsets.$promise);
-        }
-
-        $q.all(promises).then(function() {
-            service.updateOptions();
-            $('#' + resource + '-form-modal').modal('show');
-            $timeout(function() {
-                $('formgroup[data-quicksearch="true"]').trigger('refresh');
-            });
-        });
-    };
-
-    service.submitFormModal = function(resource) {
-        utils.storeValues(resources[resource], service.values).then(function(response) {
-            if (resource === 'catalogs') {
-                resources.catalogs.query({list_action: 'index'}, function(catalogs) {
-                    service.catalogs = catalogs;
-                    service.current_catalog_id = response.id;
-                    service.initView();
-                });
-            } else {
-                service.initView();
-            }
-
-            $('#' + resource + '-form-modal').modal('hide');
-        }, function(result) {
-            service.errors = result.data;
-        });
-    };
-
-    service.openDeleteModal = function(resource, obj) {
-        service.values = obj;
-        service.errors = {};
-        $('#' + resource + '-delete-modal').modal('show');
-    };
-
-    service.submitDeleteModal = function(resource) {
-        resources[resource].delete({id: service.values.id}, function() {
-            if (resource === 'catalogs') {
-                resources.catalogs.query({list_action: 'index'}, function(catalogs) {
-                    service.catalogs = catalogs;
-
-                    if (service.catalogs.length) {
-                        service.current_catalog_id = service.catalogs[0].id;
-                    } else {
-                        service.current_catalog_id = null;
-                    }
-
-                    service.initView();
-                });
-            } else {
-                service.initView();
-            }
-
-            $('#' + resource + '-delete-modal').modal('hide');
-        }
-        , function(result) {
-            service.errors = result.data;
-        });
-    };
-
-    service.hideCatalog = function(item) {
-        if (service.filter && item.uri.indexOf(service.filter) < 0
-                           && item.title.indexOf(service.filter) < 0) {
-            return true;
-        }
-        if (service.uri_prefix && item.uri_prefix != service.uri_prefix) {
-            return true;
-        }
-    };
-
-    service.hideSection = function(item) {
-        if (service.filter && item.uri.indexOf(service.filter) < 0
-                           && item.title.indexOf(service.filter) < 0) {
-            return true;
-        }
-        if (service.uri_prefix && item.uri_prefix != service.uri_prefix) {
-            return true;
-        }
-    };
-
-    service.hideQuestionSet = function(item) {
-        if (service.filter && item.uri.indexOf(service.filter) < 0
-                           && item.title.indexOf(service.filter) < 0) {
-            return true;
-        }
-        if (service.uri_prefix && item.uri_prefix != service.uri_prefix) {
-            return true;
-        }
-    };
-
-    service.hideQuestion = function(item) {
-        if (service.filter && item.uri.indexOf(service.filter) < 0
-                           && item.text.indexOf(service.filter) < 0) {
-            return true;
-        }
-        if (service.uri_prefix && item.uri_prefix != service.uri_prefix) {
-            return true;
-        }
-    };
-
-    service.updateOptions = function() {
-        if (angular.isDefined(service.optionsets) && angular.isDefined(service.values.optionsets))
-        service.options = service.optionsets.reduce(function (options, optionset) {
-            if (service.values.optionsets.indexOf(optionset.id) > -1) {
-                options = options.concat(optionset.options);
-            }
-            return options;
-        }, []);
-    }
-
-    return service;
-
-}])
-
-.controller('CatalogsController', ['$scope', 'CatalogsService', function($scope, CatalogsService) {
-
-    $scope.service = CatalogsService;
-    $scope.service.init();
-
-    // watch service.values.optionsets to recompute service.options
-    $scope.$watch(function() {
-        // to be evaluated each $digest cycle
-        if (angular.isDefined($scope.service.values)) {
-            return $scope.service.values.optionsets;
-        }
-    }, function(newValue, oldValue) {
-        if (angular.isDefined(newValue)) {
-            $scope.service.updateOptions();
-        }
-    });
-
-}]);
diff --git a/rdmo/questions/templates/questions/catalogs.html b/rdmo/questions/templates/questions/catalogs.html
deleted file mode 100644
index 1f952ed893..0000000000
--- a/rdmo/questions/templates/questions/catalogs.html
+++ /dev/null
@@ -1,147 +0,0 @@
-{% extends 'core/page.html' %}
-{% load static %}
-{% load compress %}
-{% load i18n %}
-{% load core_tags %}
-
-{% block head %}
-    <base href="{% url 'catalogs' %}" />
-
-    {% vendor 'angular' %}
-    {% vendor 'moment' %}
-    {% vendor 'select2' %}
-    {% vendor 'select2-bootstrap-theme' %}
-
-    {% compress css %}
-    <link rel="stylesheet" type="text/x-scss" href="{% static 'questions/css/catalogs.scss' %}" />
-    {% endcompress %}
-
-    {% compress js %}
-    <script type="text/javascript" src="{% static 'core/js/core.js' %}" ></script>
-    <script type="text/javascript" src="{% static 'questions/js/catalogs.js' %}" ></script>
-    {% endcompress %}
-{% endblock %}
-
-{% block bodyattr %} ng-app="catalogs" ng-controller="CatalogsController"{% endblock %}
-
-{% block sidebar %}
-
-    {% include 'questions/catalogs_sidebar.html' %}
-
-{% endblock %}
-
-{% block page %}
-
-    {% get_available_languages as LANGUAGES %}
-
-    <h1>{% trans 'Questions' %}</h1>
-
-    <div ng-show="service.catalog.id" ng-cloak>
-
-        <div class="panel panel-default panel-sites" ng-hide="service.hideCatalog(service.catalog)">
-            <div class="panel-heading">
-                <div class="pull-right">
-                    <span class="fa fa-warning text-warning"
-                        title="{% trans 'Some languages are not configured' %}"
-                        ng-show="service.catalog.warning"></span>
-
-                    {% include 'questions/catalogs_options_catalog.html' %}
-                </div>
-                <p>
-                    <strong ng-class="{'text-muted': service.catalog.read_only}">
-                        {% trans 'Catalog' %}
-                    </strong>
-                    <span>{$ service.catalog.title $}</span>
-                </p>
-                <p>
-                    {$ service.catalog.help $}
-                </p>
-                <div ng-show="service.showURIs">
-                    <code class="code-questions" title="{% trans 'URI' %}">{$ service.catalog.uri $}</code>
-                    <code class="code-order">{$ service.catalog.order $}</code>
-                </div>
-            </div>
-
-            {% if settings.MULTISITE %}
-            <div class="panel-body">
-                <div class="row">
-                    <div class="col-sm-3">
-                        <strong>{% trans 'Sites for this catalog' %}</strong>
-                    </div>
-                    <div class="col-sm-9">
-                         <ul>
-                            <li ng-show="service.catalog.sites.length > 0 && service.catalog.sites.length < service.sites.length"
-                                ng-repeat="site in service.catalog.sites">
-                                {$ site.name $}
-                            </li>
-                            <li ng-show="service.catalog.sites.length == 0 || service.catalog.sites.length == service.sites.length">
-                                {% trans 'All sites' %}
-                            </li>
-                        </ul>
-                    </div>
-                </div>
-            </div>
-            <div class="panel-body">
-                <div class="row">
-                    <div class="col-sm-3">
-                        <strong>{% trans 'Editors for this catalog' %}</strong>
-                    </div>
-                    <div class="col-sm-9">
-                         <ul>
-                            <li ng-show="service.catalog.editors.length > 0 && service.catalog.editors.length < service.sites.length"
-                                ng-repeat="site in service.catalog.editors">
-                                {$ site.name $}
-                            </li>
-                            <li ng-show="service.catalog.editors.length == 0 || service.catalog.editors.length == service.sites.length">
-                                {% trans 'All sites' %}
-                            </li>
-                        </ul>
-                    </div>
-                </div>
-            </div>
-            {% endif %}
-        </div>
-
-        <div class="indent" ng-repeat="section in service.catalog.sections">
-
-            <div class="panel panel-default panel-section" ng-hide="service.hideSection(section)">
-                <div class="panel-heading">
-                    <div class="pull-right">
-                        <span class="fa fa-warning text-warning"
-                            title="{% trans 'Some languages are not configured' %}"
-                            ng-show="section.warning"></span>
-
-                        {% include 'questions/catalogs_options_sections.html' %}
-                    </div>
-                    <p ng-style="(!service.showURIs) && {'margin-bottom': '0px'}">
-                        <strong ng-class="{'text-muted': section.read_only}">
-                            {% trans 'Section' %}
-                        </strong>
-                        <span>{$ section.title $}</span>
-                    </p>
-                    <div ng-show="service.showURIs">
-                        <code class="code-questions" title="{% trans 'URI' %}">{$ section.uri $}</code>
-                        <code class="code-order">{$ section.order $}</code>
-                    </div>
-                </div>
-            </div>
-
-            <div class="indent" ng-repeat="element in section.questionsets" ng-include src="'element.html'"></div>
-        </div>
-    </div>
-
-    <script type="text/ng-template" id="element.html">
-        {% include 'questions/catalogs_element.html' %}   
-    </script>
-
-    {% include 'questions/catalogs_modal_form_catalogs.html' %}
-    {% include 'questions/catalogs_modal_form_sections.html' %}
-    {% include 'questions/catalogs_modal_form_questionsets.html' %}
-    {% include 'questions/catalogs_modal_form_questions.html' %}
-
-    {% include 'questions/catalogs_modal_delete_catalogs.html' %}
-    {% include 'questions/catalogs_modal_delete_sections.html' %}
-    {% include 'questions/catalogs_modal_delete_questionsets.html' %}
-    {% include 'questions/catalogs_modal_delete_questions.html' %}
-
-{% endblock %}
diff --git a/rdmo/questions/templates/questions/catalogs_element.html b/rdmo/questions/templates/questions/catalogs_element.html
deleted file mode 100644
index 799b21020b..0000000000
--- a/rdmo/questions/templates/questions/catalogs_element.html
+++ /dev/null
@@ -1,73 +0,0 @@
-{% load i18n %}
-
-<div class="panel panel-default panel-questionset" ng-init="questionset = element"
-     ng-hide="!element.section || service.hideQuestionSet(element) || !service.showQuestionSets">
-
-    <div class="panel-heading">
-        <div class="pull-right">
-            <span class="fa fa-warning text-danger"
-                title="{% trans 'No attribute set' %}"
-                ng-show="questionset.is_collection && !questionset.attribute"></span>
-            <span class="fa fa-warning text-warning"
-                title="{% trans 'Some languages are not configured' %}"
-                ng-show="questionset.warning"></span>
-
-            {% include 'questions/catalogs_options_questionsets.html' %}
-        </div>
-
-        <p ng-style="!service.showURIs && {'margin-bottom':'0px'}">
-            <strong ng-class="{'text-muted': questionset.read_only}">
-                {% trans 'Question set' %}
-            </strong>
-            <span>{$ questionset.title $}</span>
-        </p>
-        <div ng-show="service.showURIs">
-            <code class="code-questions" title="{% trans 'URI' %}">{$ questionset.uri $}</code>
-            <code class="code-order">{$ questionset.order $}</code>
-        </div>
-        <div ng-show="questionset.is_collection && service.showURIs && service.showURIAttributes">
-            <code title="{% trans 'Attribute' %}">{$ questionset.attribute.uri $}</code>
-        </div>
-        <div ng-show="questionset.conditions && service.showURIs && service.showURIConditions" ng-repeat="condition in questionset.conditions">
-            <code class="code-conditions" title="{% trans 'Condition' %}">{$ condition.uri $}</code>
-        </div>
-    </div>
-</div>
-
-<div class="panel panel-default panel-question" ng-init="question = element"
-     ng-hide="element.section || service.hideQuestion(element) || !service.showQuestionSets || !service.showQuestions">
-
-    <div class="panel-body">
-        <div class="pull-right">
-            <span class="fa fa-warning text-danger"
-                title="{% trans 'No attribute set' %}"
-                ng-show="!question.attribute" ></span>
-            <span class="fa fa-warning text-warning"
-                title="{% trans 'Some languages are not configured' %}"
-                ng-show="question.warning" ></span>
-            <span class="fa fa-asterisk text-muted"
-                title="{% trans 'Optional' %}"
-                ng-show="question.is_optional" ></span>
-            {% include 'questions/catalogs_options_questions.html' %}
-        </div>
-        <p ng-style="(!service.showURIs) && {'margin-bottom':'0px'}">
-            <strong ng-class="{'text-muted': question.is_optional || question.read_only}">{% trans 'Question' %}</strong>
-            <span>{$ question.text $}</span>
-        </p>
-        <div ng-show="service.showURIs && service.showURIQuestions">
-            <code class="code-questions" title="{% trans 'URI' %}">{$ question.uri $}</code>
-            <code class="code-order">{$ question.order $}</code>
-        </div>
-        <div ng-show="question.attribute && service.showURIs && service.showURIAttributes">
-            <code title="{% trans 'Attribute' %}">{$ question.attribute.uri $}</code>
-        </div>
-        <div ng-show="question.conditions && service.showURIs && service.showURIConditions" ng-repeat="condition in question.conditions">
-            <code class="code-conditions" title="{% trans 'Condition' %}">{$ condition.uri $}</code>
-        </div>
-        <div ng-show="question.optionsets && service.showURIs && service.showURIOptions" ng-repeat="optionset in question.optionsets">
-            <code class="code-options" title="{% trans 'Option set' %}">{$ optionset.uri $}</code>
-        </div>
-    </div>
-</div>
-
-<div class="indent" ng-repeat="element in element.elements" ng-include src="'element.html'"></div>
diff --git a/rdmo/questions/templates/questions/catalogs_modal_delete_catalogs.html b/rdmo/questions/templates/questions/catalogs_modal_delete_catalogs.html
deleted file mode 100644
index a250937e42..0000000000
--- a/rdmo/questions/templates/questions/catalogs_modal_delete_catalogs.html
+++ /dev/null
@@ -1,78 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="catalogs-delete-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Catalog' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with object='{$ service.catalog.uri $}' %}
-                        You are about to permanently delete the catalog
-                        <code class="text-primary">{{ object }}</code>.
-                        {% endblocktrans %}
-                    </p>
-
-                    <p>
-                        {% blocktrans trimmed with projects_count='{$ service.catalog.projects_count $}' %}
-                        This catalog is used in <b>{{ projects_count }} projects</b>, which will not be usable afterwards.
-                        {% endblocktrans %}
-                    </p>
-
-                    <div ng-show="service.values.sections.length">
-                        <p>
-                            {% blocktrans trimmed %}
-                            <strong>Important!</strong> This will also delete the following items:
-                            {% endblocktrans %}
-                        </p>
-
-                        <ul class="list-unstyled">
-                            <li ng-repeat="section in service.values.sections">
-                                <strong>{% trans 'Section' %}</strong>
-                                <code class="text-primary">{$ section.uri $}</code>
-                                <ul class="list-unstyled">
-                                    <li ng-repeat="questionset in section.questionsets">
-                                        <strong>
-                                            {% trans 'Question set' %}
-                                        </strong>
-                                        <code class="text-primary">{$ questionset.uri $}</code>
-                                        <ul class="list-unstyled">
-                                            <li ng-repeat="question in questionset.questions">
-                                                <strong>Question</strong>
-                                                <code class="text-primary">{$ question.uri $}</code>
-                                            </li>
-                                        </ul>
-                                    </li>
-                                </ul>
-                            </li>
-                        </ul>
-                    </div>
-
-                    <p class="text-danger">
-                        {% trans 'This action cannot be undone!' %}
-                    </p>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-danger"
-                            ng-class="{'disabled': service.catalog.read_only}"
-                            ng-click="service.submitDeleteModal('catalogs')">
-                        {% trans 'Delete' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/questions/templates/questions/catalogs_modal_delete_questions.html b/rdmo/questions/templates/questions/catalogs_modal_delete_questions.html
deleted file mode 100644
index e81173ebc7..0000000000
--- a/rdmo/questions/templates/questions/catalogs_modal_delete_questions.html
+++ /dev/null
@@ -1,41 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="questions-delete-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Question' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with object='{$ service.values.uri $}' %}
-                        You are about to permanently delete the question <code class="text-primary">{{ object }}</code>.
-                        {% endblocktrans %}
-                    </p>
-                    <p class="text-danger">
-                        {% trans 'This action cannot be undone!' %}
-                    </p>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-danger"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitDeleteModal('questions')">
-                        {% trans 'Delete' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/questions/templates/questions/catalogs_modal_delete_questionsets.html b/rdmo/questions/templates/questions/catalogs_modal_delete_questionsets.html
deleted file mode 100644
index 3bb8e800c1..0000000000
--- a/rdmo/questions/templates/questions/catalogs_modal_delete_questionsets.html
+++ /dev/null
@@ -1,57 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="questionsets-delete-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Question set' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with object='{$ service.values.uri $}' %}
-                        You are about to permanently delete the question set <code class="text-primary">{{ object }}</code>.
-                        {% endblocktrans %}
-                    </p>
-
-                    <div ng-show="service.values.questions.length">
-                        <p>
-                            {% blocktrans trimmed %}
-                            <strong>Important!</strong> This will also delete the following items:
-                            {% endblocktrans %}
-                        </p>
-
-                        <ul class="list-unstyled">
-                            <li ng-repeat="question in service.values.questions">
-                                <strong>{% trans 'Question' %}</strong>
-                                <code class="text-primary">{$ question.uri $}</code>
-                            </li>
-                        </ul>
-                    </div>
-
-                    <p class="text-danger">
-                        {% trans 'This action cannot be undone!' %}
-                    </p>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-danger"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitDeleteModal('questionsets')">
-                        {% trans 'Delete' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/questions/templates/questions/catalogs_modal_delete_sections.html b/rdmo/questions/templates/questions/catalogs_modal_delete_sections.html
deleted file mode 100644
index 6c245bca48..0000000000
--- a/rdmo/questions/templates/questions/catalogs_modal_delete_sections.html
+++ /dev/null
@@ -1,65 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="sections-delete-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Section' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with object='{$ service.values.uri $}' %}
-                        You are about to permanently delete the section <code class="text-primary">{{ object }}</code>.
-                        {% endblocktrans %}
-                    </p>
-
-                    <div ng-show="service.values.questionsets.length">
-                        <p>
-                            {% blocktrans trimmed %}
-                            <strong>Important!</strong> This will also delete the following items:
-                            {% endblocktrans %}
-                        </p>
-
-                        <ul class="list-unstyled">
-                            <li ng-repeat="questionset in service.values.questionsets">
-                                <strong>
-                                    {% trans 'Question set' %}
-                                </strong>
-                                <code class="text-primary">{$ questionset.uri $}</code>
-                                <ul class="list-unstyled">
-                                    <li ng-repeat="question in questionset.questions">
-                                        <strong>Question</strong>
-                                        <code class="text-primary">{$ question.uri $}</code>
-                                    </li>
-                                </ul>
-                            </li>
-                        </ul>
-                    </div>
-
-                    <p class="text-danger">
-                        {% trans 'This action cannot be undone!' %}
-                    </p>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-danger"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitDeleteModal('sections')">
-                        {% trans 'Delete' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/questions/templates/questions/catalogs_modal_form_catalogs.html b/rdmo/questions/templates/questions/catalogs_modal_form_catalogs.html
deleted file mode 100644
index 1870229e64..0000000000
--- a/rdmo/questions/templates/questions/catalogs_modal_form_catalogs.html
+++ /dev/null
@@ -1,247 +0,0 @@
-{% load i18n %}
-{% get_available_languages as languages %}
-
-    <div class="modal" id="catalogs-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        <span ng-hide="service.values.id">{% trans 'Create catalog' %}</span>
-                        <span ng-show="service.values.copy">{% trans 'Copy catalog' %}</span>
-                        <span ng-show="service.values.id && !service.values.copy">{% trans 'Update catalog' %}</span>
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <div class="copy-block" ng-show="service.values.copy">
-                        <p>
-                            {% trans 'Please enter the values for the copy of the catalog below and change at least the <strong>key</strong>.' %}
-                        </p>
-                        <p>
-                            {% trans 'Other properties can be changed once the new catalog is created.' %}
-                        </p>
-                    </div>
-                    <div class="row">
-                        <div class="col-sm-6">
-                            <div class="form-group" ng-class="{'has-error': service.errors.uri_prefix }">
-                                <label class="control-label" for="uri_prefix">
-                                    {{ meta.Catalog.uri_prefix.verbose_name }}
-                                </label>
-
-                                <div class="input-group">
-                                    <input class="form-control"
-                                           id="catalog_uri_prefix"
-                                           type="text"
-                                           ng-model="service.values.uri_prefix" />
-
-                                    <span class="input-group-btn">
-                                        <button type="button" class="btn btn-default"
-                                            title="{% trans 'Insert default URI Prefix' %}"
-                                            ng-click="service.values.uri_prefix = service.settings.default_uri_prefix">
-
-                                            <span class="fa fa-magic"></span>
-                                        </button>
-                                    </span>
-                                </div>
-
-                                <p class="help-block">
-                                    {{ meta.Catalog.uri_prefix.help_text }}
-                                </p>
-
-                                <ul class="help-block list-unstyled" ng-show="service.errors.uri_prefix">
-                                    <li ng-repeat="error in service.errors.uri_prefix">
-                                        {$ error $}
-                                    </li>
-                                </ul>
-                            </div>
-                        </div>
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="catalog_key"
-                                data-type="text"
-                                data-label="{{ meta.Catalog.key.verbose_name }}"
-                                data-help="{{ meta.Catalog.key.help_text }}"
-                                data-model="service.values.key"
-                                data-errors="service.errors.key">
-                            </formgroup>
-                        </div>
-                    </div>
-
-                    <div ng-hide="service.values.copy">
-                        <formgroup
-                            data-id="catalog_comment"
-                            data-type="textarea"
-                            data-label="{{ meta.Catalog.comment.verbose_name }}"
-                            data-help="{{ meta.Catalog.comment.help_text }}"
-                            data-model="service.values.comment"
-                            data-errors="service.errors.comment">
-                        </formgroup>
-                        <formgroup
-                            data-id="catalog_locked"
-                            data-type="checkbox"
-                            data-label="{{ meta.Catalog.locked.verbose_name }}"
-                            data-help="{{ meta.Catalog.locked.help_text }}"
-                            data-model="service.values.locked"
-                            data-errors="service.errors.locked">
-                        </formgroup>
-                        <div>
-                            <ul class="nav nav-tabs" role="tablist">
-                                <li role="presentation" class="active">
-                                    <a class="control-label" href="#catalogs-general"
-                                        role="tab" data-toggle="tab" aria-controls="catalogs-general">
-
-                                        {% trans 'General' %}
-                                    </a>
-                                </li>
-                                {% for lang_code, lang in languages %}
-                                <li role="presentation" ng-class="{
-                                    'has-error':
-                                        service.errors.title_{{ lang_code }} ||
-                                        service.errors.help_{{ lang_code }},
-                                    'has-warning':
-                                        service.values.id && !service.values.title_{{ lang_code }}
-                                }">
-                                    <a role="tab" class="control-label" data-toggle="tab"
-                                        href="#catalogs-{{ lang_code }}" aria-controls="catalogs-{{ lang_code }}">
-
-                                        {{ lang }}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endfor %}
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.groups,
-                                }">
-                                    <a class="control-label" href="#catalogs-groups"
-                                        role="tab" data-toggle="tab" aria-controls="catalogs-groups">
-
-                                        {% trans 'Groups' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% if settings.MULTISITE %}
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.sites,
-                                }">
-                                    <a class="control-label" href="#catalogs-sites"
-                                        role="tab" data-toggle="tab" aria-controls="catalogs-sites">
-
-                                        {% trans 'Sites' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.editors,
-                                }">
-                                    <a class="control-label" href="#catalogs-editors"
-                                        role="tab" data-toggle="tab" aria-controls="catalogs-editors">
-
-                                        {% trans 'Editors' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endif %}
-                            </ul>
-                            <div class="tab-content">
-                                <div role="tabpanel" class="tab-pane active" id="catalogs-general">
-                                    <formgroup
-                                        data-id="catalog_order"
-                                        data-type="number"
-                                        data-label="{{ meta.Catalog.order.verbose_name }}"
-                                        data-help="{{ meta.Catalog.order.help_text }}"
-                                        data-model="service.values.order"
-                                        data-errors="service.errors.order">
-                                    </formgroup>
-                                    <formgroup
-                                        data-id="catalog_available"
-                                        data-type="checkbox"
-                                        data-label="{{ meta.Catalog.available.verbose_name }}"
-                                        data-help="{{ meta.Catalog.available.help_text }}"
-                                        data-model="service.values.available"
-                                        data-errors="service.errors.available">
-                                    </formgroup>
-                                </div>
-                                {% for lang_code, lang in languages %}
-                                <div role="tabpanel" id="catalogs-{{ lang_code }}"
-                                    class="tab-pane">
-                                    <formgroup
-                                        data-id="catalog_title_{{ lang_code }}"
-                                        data-type="text"
-                                        data-label="{% trans 'Title' %} ({{ lang }})"
-                                        data-help="{% trans 'The title for this catalog.' %}"
-                                        data-model="service.values.title_{{ lang_code }}"
-                                        data-errors="service.errors.title_{{ lang_code }}"
-                                        data-warning="service.values.id && !service.values.title_{{ lang_code }}">
-                                    </formgroup>
-                                    <formgroup
-                                        data-id="catalog_help_{{ lang_code }}"
-                                        data-type="textarea"
-                                        data-label="{% trans 'Help' %} ({{ lang }})"
-                                        data-help="{% trans 'The help text for this catalog.' %}"
-                                        data-model="service.values.help_{{ lang_code }}"
-                                        data-errors="service.errors.help_{{ lang_code }}">
-                                    </formgroup>
-                                </div>
-                                {% endfor %}
-                                <div role="tabpanel" class="tab-pane" id="catalogs-groups">
-                                    <formgroup
-                                        data-id="catalog_groups"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Groups' %}"
-                                        data-model="service.values.groups"
-                                        data-errors="service.errors.groups"
-                                        data-options="service.groups"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                {% if settings.MULTISITE %}
-                                <div role="tabpanel" class="tab-pane" id="catalogs-sites">
-                                    <formgroup
-                                        data-id="catalog_sites"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Sites' %}"
-                                        data-model="service.values.sites"
-                                        data-errors="service.errors.sites"
-                                        data-options="service.sites"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                <div role="tabpanel" class="tab-pane" id="catalogs-editors">
-                                    <formgroup
-                                        data-id="catalog_editors"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Editors' %}"
-                                        data-model="service.values.editors"
-                                        data-errors="service.errors.editors"
-                                        data-options="service.sites"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                {% endif %}
-                            </div>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary" data-dismiss="alert" 
-                            ng-class="{'disabled': service.catalog.read_only}"
-                            ng-click="service.submitFormModal('catalogs')">
-                        {% trans 'Save' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/questions/templates/questions/catalogs_modal_form_questions.html b/rdmo/questions/templates/questions/catalogs_modal_form_questions.html
deleted file mode 100644
index 865055a985..0000000000
--- a/rdmo/questions/templates/questions/catalogs_modal_form_questions.html
+++ /dev/null
@@ -1,499 +0,0 @@
-{% load i18n %}
-{% get_available_languages as languages %}
-
-    <div class="modal" id="questions-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        <span ng-hide="service.values.id">{% trans 'Create question' %}</span>
-                        <span ng-show="service.values.copy">{% trans 'Copy question' %}</span>
-                        <span ng-show="service.values.id && !service.values.copy">{% trans 'Update question' %}</span>
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <div class="copy-block" ng-show="service.values.copy">
-                        <p>
-                            {% trans 'Please enter the values for the copy of the question below and change at least the <strong>key</strong>.' %}
-                        </p>
-                        <p>
-                            {% trans 'Other properties can be changed once the new question is created.' %}
-                        </p>
-                    </div>
-                    <div class="row">
-                        <div class="col-sm-6">
-                            <div class="form-group" ng-class="{'has-error': service.errors.uri_prefix }">
-                                <label class="control-label" for="uri_prefix">
-                                    {{ meta.Question.uri_prefix.verbose_name }}
-                                </label>
-
-                                <div class="input-group">
-                                    <input class="form-control"
-                                           id="question_uri_prefix"
-                                           type="text"
-                                           ng-model="service.values.uri_prefix" />
-
-                                    <span class="input-group-btn">
-                                        <button type="button" class="btn btn-default"
-                                            title="{% trans 'Insert default URI Prefix' %}"
-                                            ng-click="service.values.uri_prefix = service.settings.default_uri_prefix">
-
-                                            <span class="fa fa-magic"></span>
-                                        </button>
-                                    </span>
-                                </div>
-
-                                <p class="help-block">
-                                    {{ meta.Question.uri_prefix.help_text }}
-                                </p>
-
-                                <ul class="help-block list-unstyled" ng-show="service.errors.uri_prefix">
-                                    <li ng-repeat="error in service.errors.uri_prefix">
-                                        {$ error $}
-                                    </li>
-                                </ul>
-                            </div>
-                        </div>
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="question_key"
-                                data-type="text"
-                                data-label="{{ meta.Question.key.verbose_name }}"
-                                data-help="{% trans 'The internal identifier of this question.' %}"
-                                data-model="service.values.key"
-                                data-errors="service.errors.key">
-                            </formgroup>
-                        </div>
-                    </div>
-                    <div ng-show="service.values.copy">
-                        <formgroup
-                            data-id="question_questionset"
-                            data-type="selectnumber"
-                            data-label="{{ meta.Question.questionset.verbose_name }}"
-                            data-help="{{ meta.Question.questionset.help_text }}"
-                            data-model="service.values.questionset"
-                            data-errors="service.errors.questionset"
-                            data-options="service.questionsets"
-                            data-options-label="uri"
-                            data-options-null="1"
-                            data-quicksearch="true">
-                        </formgroup>
-                    </div>
-                    <div ng-hide="service.values.copy">
-                        <formgroup
-                            data-id="question_comment"
-                            data-type="textarea"
-                            data-label="{{ meta.Question.comment.verbose_name }}"
-                            data-help="{% trans 'Additional internal information about this question.' %}"
-                            data-model="service.values.comment"
-                            data-errors="service.errors.comment">
-                        </formgroup>
-                        <formgroup
-                            data-id="question_locked"
-                            data-type="checkbox"
-                            data-label="{{ meta.Question.locked.verbose_name }}"
-                            data-help="{{ meta.Question.locked.help_text }}"
-                            data-model="service.values.locked"
-                            data-errors="service.errors.locked">
-                        </formgroup>
-                        <div>
-                            <ul class="nav nav-tabs" role="tablist">
-                                <li role="presentation" class="active" ng-class="{
-                                    'has-error':
-                                        service.errors.questionset ||
-                                        service.errors.order ||
-                                        service.errors.attribute ||
-                                        service.errors.is_collection ||
-                                        service.errors.widget_type ||
-                                        service.errors.value_type ||
-                                        service.errors.unit ||
-                                        service.errors.width
-                                }">
-                                    <a class="control-label" href="#questions-general"
-                                        role="tab" data-toggle="tab" aria-controls="questions-general">
-
-                                        {% trans 'General' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% for lang_code, lang in languages %}
-                                <li role="presentation" ng-class="{
-                                    'has-error':
-                                        service.errors.text_{{ lang_code }} ||
-                                        service.errors.help_{{ lang_code }} ||
-                                        service.errors.verbose_name_{{ lang_code }} ||
-                                        service.errors.verbose_name_plural_{{ lang_code }},
-                                    'has-warning':
-                                        service.values.id && !service.values.text_{{ lang_code }}
-                                }">
-                                    <a class="control-label" href="#questions-{{ lang_code }}"
-                                        role="tab" data-toggle="tab" aria-controls="questions-{{ lang_code }}">
-
-                                        {{ lang }}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endfor %}
-                                <li role="presentation" ng-class="{
-                                    'has-error':
-                                        service.errors.optionsets
-                                }">
-                                    <a class="control-label" href="#questions-optionsets"
-                                        role="tab" data-toggle="tab" aria-controls="questions-optionsets">
-
-                                        {% trans 'Option sets' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                <li role="presentation" ng-class="{
-                                    'has-error':
-                                        service.errors.conditions
-                                }">
-                                    <a class="control-label" href="#questions-conditions"
-                                        role="tab" data-toggle="tab" aria-controls="questions-conditions">
-
-                                        {% trans 'Conditions' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                <li role="presentation" ng-class="{
-                                    'has-error':
-                                        service.errors.minimum ||
-                                        service.errors.maximum ||
-                                        service.errors.step
-                                }">
-                                    <a class="control-label" href="#questions-range"
-                                        role="tab" data-toggle="tab" aria-controls="questions-range">
-
-                                        {% trans 'Range' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                <li role="presentation" ng-class="{
-                                    'has-error':
-                                        {% for lang_code, lang in languages %}
-                                        service.errors.text_{{ lang_code }} ||
-                                        {% endfor %}
-                                        service.errors.default_option ||
-                                        service.errors.default_external_id
-                                }">
-                                    <a class="control-label" href="#questions-default"
-                                        role="tab" data-toggle="tab" aria-controls="questions-default">
-
-                                        {% trans 'Default' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% if settings.MULTISITE %}
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.editors,
-                                }">
-                                    <a class="control-label" href="#questions-editors"
-                                        role="tab" data-toggle="tab" aria-controls="questions-editors">
-
-                                        {% trans 'Editors' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endif %}
-                            </ul>
-
-                            <div class="tab-content">
-                                <div role="tabpanel" class="tab-pane active" id="questions-general">
-                                    <div class="row">
-                                        <div class="col-sm-8">
-                                            <formgroup
-                                                data-id="question_questionset"
-                                                data-type="selectnumber"
-                                                data-label="{{ meta.Question.questionset.verbose_name }}"
-                                                data-help="{{ meta.Question.questionset.help_text }}"
-                                                data-model="service.values.questionset"
-                                                data-errors="service.errors.questionset"
-                                                data-options="service.questionsets"
-                                                data-options-label="uri"
-                                                data-options-null="1"
-                                                data-quicksearch="true">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-4">
-                                            <formgroup
-                                                data-id="question_order"
-                                                data-type="number"
-                                                data-label="{{ meta.Question.order.verbose_name }}"
-                                                data-help="{{ meta.Question.order.help_text }}"
-                                                data-model="service.values.order"
-                                                data-errors="service.errors.order">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                    <div class="row">
-                                        <div class="col-sm-12">
-                                            <formgroup
-                                                data-id="question_attribute"
-                                                data-type="selectnumber"
-                                                data-label="{{ meta.Question.attribute.verbose_name }}"
-                                                data-help="{{ meta.Question.attribute.help_text }}"
-                                                data-model="service.values.attribute"
-                                                data-errors="service.errors.attribute"
-                                                data-options="service.attributes"
-                                                data-options-label="uri"
-                                                data-options-null="1"
-                                                data-quicksearch="true">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                    <div class="row">
-                                        <div class="col-sm-6">
-                                            <formgroup
-                                                data-id="question_is_collection"
-                                                data-type="checkbox"
-                                                data-label="{{ meta.Question.is_collection.verbose_name }}"
-                                                data-help="{{ meta.Question.is_collection.help_text }}"
-                                                data-model="service.values.is_collection"
-                                                data-errors="service.errors.is_collection">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-6">
-                                            <formgroup
-                                                data-id="question_is_optional"
-                                                data-type="checkbox"
-                                                data-label="{{ meta.Question.is_optional.verbose_name }}"
-                                                data-help="{{ meta.Question.is_optional.help_text }}"
-                                                data-model="service.values.is_optional"
-                                                data-errors="service.errors.is_optional">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                    <div class="row">
-                                        <div class="col-sm-3">
-                                            <formgroup
-                                                data-id="question_widget_type"
-                                                data-type="select"
-                                                data-label="{{ meta.Question.widget_type.verbose_name }}"
-                                                data-help="{{ meta.Question.widget_type.help_text }}"
-                                                data-model="service.values.widget_type"
-                                                data-errors="service.errors.widget_type"
-                                                data-options="service.widgettypes"
-                                                data-options-label="text">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-3">
-                                            <formgroup
-                                                data-id="question_value_type"
-                                                data-type="select"
-                                                data-label="{{ meta.Question.value_type.verbose_name }}"
-                                                data-help="{{ meta.Question.value_type.help_text }}"
-                                                data-model="service.values.value_type"
-                                                data-errors="service.errors.value_type"
-                                                data-options="service.valuetypes"
-                                                data-options-label="text">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-3">
-                                            <formgroup
-                                                data-id="question_unit"
-                                                data-type="text"
-                                                data-label="{{ meta.Question.unit.verbose_name }}"
-                                                data-help="{{ meta.Question.unit.help_text }}"
-                                                data-model="service.values.unit"
-                                                data-errors="service.errors.unit">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-3">
-                                            <formgroup
-                                                data-id="question_width"
-                                                data-type="text"
-                                                data-label="{{ meta.Question.width.verbose_name }}"
-                                                data-help="{{ meta.Question.width.help_text }}"
-                                                data-model="service.values.width"
-                                                data-errors="service.errors.width">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                </div>
-                                {% for lang_code, lang in languages %}
-                                <div role="tabpanel" class="tab-pane" id="questions-{{ lang_code }}">
-                                    <div class="row">
-                                        <div class="col-sm-12">
-                                            <formgroup
-                                                data-id="question_text_{{ lang_code }}"
-                                                data-type="text"
-                                                data-label="{% trans 'Text' %} ({{ lang }})"
-                                                data-help="{% trans 'The text for this question.' %}"
-                                                data-model="service.values.text_{{ lang_code }}"
-                                                data-errors="service.errors.text_{{ lang_code }}"
-                                                data-warning="service.values.id && !service.values.text_{{ lang_code }}">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-12">
-                                            <formgroup
-                                                data-id="question_help_{{ lang_code }}"
-                                                data-type="textarea"
-                                                data-label="{% trans 'Help' %} ({{ lang }})"
-                                                data-help="{% trans 'The help text for this question.' %}"
-                                                data-model="service.values.help_{{ lang_code }}"
-                                                data-errors="service.errors.help_{{ lang_code }}">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-6">
-                                            <formgroup
-                                                data-id="question_verbose_name_{{ lang_code }}"
-                                                data-type="text"
-                                                data-label="{% trans 'Verbose name' %} ({{ lang }})"
-                                                data-help="{% trans 'The verbose name for this question.' %}"
-                                                data-model="service.values.verbose_name_{{ lang_code }}"
-                                                data-errors="service.errors.verbose_name_{{ lang_code }}">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-6">
-                                            <formgroup
-                                                data-id="question_verbose_name_plural_{{ lang_code }}"
-                                                data-type="text"
-                                                data-label="{% trans 'Verbose name plural' %} ({{ lang }})"
-                                                data-help="{% trans 'The plural verbose name for this question.' %}"
-                                                data-model="service.values.verbose_name_plural_{{ lang_code }}"
-                                                data-errors="service.errors.verbose_name_plural_{{ lang_code }}">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                </div>
-                                {% endfor %}
-                                <div role="tabpanel" class="tab-pane" id="questions-optionsets">
-                                    <formgroup
-                                        data-id="question_optionsets"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Option sets' %}"
-                                        data-model="service.values.optionsets"
-                                        data-errors="service.errors.optionsets"
-                                        data-options="service.optionsets"
-                                        data-options-label="uri"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                <div role="tabpanel" class="tab-pane" id="questions-conditions">
-                                    <formgroup
-                                        data-id="question_conditions"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Conditions' %}"
-                                        data-model="service.values.conditions"
-                                        data-errors="service.errors.conditions"
-                                        data-options="service.conditions"
-                                        data-options-label="uri"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                <div role="tabpanel" class="tab-pane" id="questions-range">
-                                    <div class="row">
-                                        <div class="col-sm-4">
-                                            <formgroup
-                                                data-id="question_minimum"
-                                                data-type="text"
-                                                data-label="{{ meta.Question.minimum.verbose_name }}"
-                                                data-help="{{ meta.Question.minimum.help_text }}"
-                                                data-model="service.values.minimum"
-                                                data-errors="service.errors.minimum">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-4">
-                                            <formgroup
-                                                data-id="question_maximum"
-                                                data-type="text"
-                                                data-label="{{ meta.Question.maximum.verbose_name }}"
-                                                data-help="{{ meta.Question.maximum.help_text }}"
-                                                data-model="service.values.maximum"
-                                                data-errors="service.errors.maximum">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-4">
-                                            <formgroup
-                                                data-id="question_step"
-                                                data-type="text"
-                                                data-label="{{ meta.Question.step.verbose_name }}"
-                                                data-help="{{ meta.Question.step.help_text }}"
-                                                data-model="service.values.step"
-                                                data-errors="service.errors.step">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                </div>
-                                <div role="tabpanel" class="tab-pane" id="questions-default">
-                                    <div class="row">
-                                        {% for lang_code, lang in languages %}
-                                        <div class="col-sm-12">
-                                            <formgroup
-                                                data-id="question_default_text_{{ lang_code }}"
-                                                data-type="textarea"
-                                                data-label="{% trans 'Default answer text' %} ({{ lang }})"
-                                                data-help="{% trans 'The default answer text for this question.' %}"
-                                                data-model="service.values.default_text_{{ lang_code }}"
-                                                data-errors="service.errors.default_text_{{ lang_code }}"
-                                                rows="2">
-                                            </formgroup>
-                                        </div>
-                                        {% endfor %}
-                                        <div class="col-sm-8">
-                                            <formgroup
-                                                data-id="question_default_option"
-                                                data-type="selectnumber"
-                                                data-label="{{ meta.Question.default_option.verbose_name }}"
-                                                data-help="{{ meta.Question.default_option.help_text }}"
-                                                data-model="service.values.default_option"
-                                                data-errors="service.errors.default_option"
-                                                data-options="service.options"
-                                                data-options-label="uri"
-                                                data-options-null="1"
-                                                data-quicksearch="true">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-4">
-                                            <formgroup
-                                                data-id="question_default_external_id"
-                                                data-type="text"
-                                                data-label="{{ meta.Question.default_external_id.verbose_name }}"
-                                                data-help="{{ meta.Question.default_external_id.help_text }}"
-                                                data-model="service.values.default_external_id"
-                                                data-errors="service.errors.default_external_id">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                </div>
-                                {% if settings.MULTISITE %}
-                                <div role="tabpanel" class="tab-pane" id="questions-editors">
-                                    <formgroup
-                                        data-id="question_editors"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Editors' %}"
-                                        data-model="service.values.editors"
-                                        data-errors="service.errors.editors"
-                                        data-options="service.sites"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                {% endif %}
-                            </div>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitFormModal('questions')">
-                        <span ng-show="service.copy">{% trans 'Copy' %}</span>
-                        <span ng-hide="service.copy">{% trans 'Save' %}</span>
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/questions/templates/questions/catalogs_modal_form_questionsets.html b/rdmo/questions/templates/questions/catalogs_modal_form_questionsets.html
deleted file mode 100644
index 48159634df..0000000000
--- a/rdmo/questions/templates/questions/catalogs_modal_form_questionsets.html
+++ /dev/null
@@ -1,343 +0,0 @@
-{% load i18n %}
-{% get_available_languages as languages %}
-
-    <div class="modal" id="questionsets-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        <span ng-hide="service.values.id">{% trans 'Create questionset' %}</span>
-                        <span ng-show="service.values.copy">{% trans 'Copy questionset' %}</span>
-                        <span ng-show="service.values.id && !service.values.copy">{% trans 'Update questionset' %}</span>
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <div class="copy-block" ng-show="service.values.copy">
-                        <p>
-                            {% trans 'Please enter the values for the copy of the question set below and change at least the <strong>key</strong>.' %}
-                        </p>
-                        <p>
-                            {% trans 'Other properties can be changed once the new question set is created.' %}
-                        </p>
-                    </div>
-                    <div class="row">
-                        <div class="col-sm-6">
-                            <div class="form-group" ng-class="{'has-error': service.errors.uri_prefix }">
-                                <label class="control-label" for="uri_prefix">
-                                    {{ meta.Question.uri_prefix.verbose_name }}
-                                </label>
-
-                                <div class="input-group">
-                                    <input class="form-control"
-                                           id="questionset_uri_prefix"
-                                           type="text"
-                                           ng-model="service.values.uri_prefix" />
-
-                                    <span class="input-group-btn">
-                                        <button type="button" class="btn btn-default"
-                                            title="{% trans 'Insert default URI Prefix' %}"
-                                            ng-click="service.values.uri_prefix = service.settings.default_uri_prefix">
-
-                                            <span class="fa fa-magic"></span>
-                                        </button>
-                                    </span>
-                                </div>
-
-                                <p class="help-block">
-                                    {{ meta.Question.uri_prefix.help_text }}
-                                </p>
-
-                                <ul class="help-block list-unstyled" ng-show="service.errors.uri_prefix">
-                                    <li ng-repeat="error in service.errors.uri_prefix">
-                                        {$ error $}
-                                    </li>
-                                </ul>
-                            </div>
-                        </div>
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="questionset_key"
-                                data-type="text"
-                                data-label="{{ meta.Question.key.verbose_name }}"
-                                data-help="{% trans 'The internal identifier of this questionset.' %}"
-                                data-model="service.values.key"
-                                data-errors="service.errors.key">
-                            </formgroup>
-                        </div>
-                    </div>
-                    <div ng-show="service.values.copy">
-                        <formgroup
-                            data-id="questionset_section"
-                            data-type="selectnumber"
-                            data-label="{{ meta.QuestionSet.section.verbose_name }}"
-                            data-help="{{ meta.QuestionSet.section.help_text }}"
-                            data-model="service.values.section"
-                            data-errors="service.errors.section"
-                            data-options="service.sections"
-                            data-options-label="uri"
-                            data-options-null="1"
-                            data-quicksearch="true">
-                        </formgroup>
-                        <formgroup
-                            data-id="questionset_questionset"
-                            data-type="selectnumber"
-                            data-label="{{ meta.QuestionSet.questionset.verbose_name }}"
-                            data-help="{{ meta.QuestionSet.questionset.help_text }}"
-                            data-model="service.values.questionset"
-                            data-errors="service.errors.questionset"
-                            data-options="service.questionsets"
-                            data-options-label="uri"
-                            data-options-null="1"
-                            data-quicksearch="true">
-                        </formgroup>
-                    </div>
-                    <div ng-hide="service.values.copy">
-                        <formgroup
-                            data-id="questionset_comment"
-                            data-type="textarea"
-                            data-label="{{ meta.QuestionSet.comment.verbose_name }}"
-                            data-help="{% trans 'Additional internal information about this questionset.' %}"
-                            data-model="service.values.comment"
-                            data-errors="service.errors.comment">
-                        </formgroup>
-                        <formgroup
-                            data-id="questionset_locked"
-                            data-type="checkbox"
-                            data-label="{{ meta.QuestionSet.locked.verbose_name }}"
-                            data-help="{{ meta.QuestionSet.locked.help_text }}"
-                            data-model="service.values.locked"
-                            data-errors="service.errors.locked">
-                        </formgroup>
-                        <div>
-                            <ul class="nav nav-tabs" role="tablist">
-                                <li role="presentation" class="active" ng-class="{
-                                    'has-error':
-                                        service.errors.section ||
-                                        service.errors.order ||
-                                        service.errors.attribute ||
-                                        service.errors.is_collection
-                                }">
-                                    <a class="control-label" href="#questionsets-general"
-                                        role="tab" data-toggle="tab" aria-controls="questionsets-general">
-
-                                        {% trans 'General' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% for lang_code, lang in languages %}
-                                <li role="presentation" ng-class="{
-                                        'has-error':
-                                            service.errors.title_{{ lang_code }} ||
-                                            service.errors.help_{{ lang_code }} ||
-                                            service.errors.verbose_name_{{ lang_code }} ||
-                                            service.errors.verbose_name_plural_{{ lang_code }},
-                                        'has-warning':
-                                            service.values.id && !service.values.title_{{ lang_code }}
-                                    }">
-
-                                    <a class="control-label" href="#questionsets-{{ lang_code }}"
-                                        role="tab" data-toggle="tab" aria-controls="questionsets-{{ lang_code }}">
-
-                                        {{ lang }}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endfor %}
-                                <li role="presentation" ng-class="{
-                                        'has-error':
-                                            service.errors.conditions
-                                    }">
-
-                                    <a class="control-label" href="#questionsets-conditions"
-                                        role="tab" data-toggle="tab" aria-controls="questionsets-conditions">
-
-                                        {% trans 'Conditions' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% if settings.MULTISITE %}
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.editors,
-                                }">
-                                    <a class="control-label" href="#questionsets-editors"
-                                        role="tab" data-toggle="tab" aria-controls="questionsets-editors">
-
-                                        {% trans 'Editors' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endif %}
-                            </ul>
-
-                            <div class="tab-content">
-                                <div role="tabpanel" class="tab-pane active" id="questionsets-general">
-                                    <div class="row">
-                                        <div class="col-sm-8">
-                                            <formgroup
-                                                data-id="questionset_section"
-                                                data-type="selectnumber"
-                                                data-label="{{ meta.QuestionSet.section.verbose_name }}"
-                                                data-help="{{ meta.QuestionSet.section.help_text }}"
-                                                data-model="service.values.section"
-                                                data-errors="service.errors.section"
-                                                data-options="service.sections"
-                                                data-options-label="uri"
-                                                data-options-null="1"
-                                                data-quicksearch="true">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-4">
-                                            <formgroup
-                                                data-id="questionset_order"
-                                                data-type="number"
-                                                data-label="{{ meta.QuestionSet.order.verbose_name }}"
-                                                data-help="{{ meta.QuestionSet.order.help_text }}"
-                                                data-model="service.values.order"
-                                                data-errors="service.errors.order">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                    <div class="row">
-                                        <div class="col-sm-12">
-                                            <formgroup
-                                                data-id="questionset_questionset"
-                                                data-type="selectnumber"
-                                                data-label="{{ meta.QuestionSet.questionset.verbose_name }}"
-                                                data-help="{{ meta.QuestionSet.questionset.help_text }}"
-                                                data-model="service.values.questionset"
-                                                data-errors="service.errors.questionset"
-                                                data-options="service.questionsets"
-                                                data-options-label="uri"
-                                                data-options-null="1"
-                                                data-quicksearch="true">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                    <div class="row">
-                                        <div class="col-sm-12">
-                                            <formgroup
-                                                data-id="questionset_attribute"
-                                                data-type="selectnumber"
-                                                data-label="{{ meta.QuestionSet.attribute.verbose_name }}"
-                                                data-help="{{ meta.QuestionSet.attribute.help_text }}"
-                                                data-model="service.values.attribute"
-                                                data-errors="service.errors.attribute"
-                                                data-options="service.attributes"
-                                                data-options-label="uri"
-                                                data-options-null="1"
-                                                data-quicksearch="true">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                    <div class="row">
-                                        <div class="col-sm-6">
-                                            <formgroup
-                                                data-id="questionset_is_collection"
-                                                data-type="checkbox"
-                                                data-label="{{ meta.QuestionSet.is_collection.verbose_name }}"
-                                                data-help="{{ meta.QuestionSet.is_collection.help_text }}"
-                                                data-model="service.values.is_collection"
-                                                data-errors="service.errors.is_collection">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                </div>
-                                {% for lang_code, lang in languages %}
-                                <div role="tabpanel" class="tab-pane" id="questionsets-{{ lang_code }}">
-                                    <div class="row">
-                                        <div class="col-sm-12">
-                                            <formgroup
-                                                data-id="questionset_title_{{ lang_code }}"
-                                                data-type="text"
-                                                data-label="{% trans 'Title' %} ({{ lang }})"
-                                                data-help="{% trans 'The title for this question set.' %}"
-                                                data-model="service.values.title_{{ lang_code }}"
-                                                data-errors="service.errors.title_{{ lang_code }}"
-                                                data-warning="service.values.id && !service.values.title_{{ lang_code }}">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-12">
-                                            <formgroup
-                                                data-id="questionset_help_{{ lang_code }}"
-                                                data-type="textarea"
-                                                data-label="{% trans 'Help' %} ({{ lang }})"
-                                                data-help="{% trans 'The help text for this question set.' %}"
-                                                data-model="service.values.help_{{ lang_code }}"
-                                                data-errors="service.errors.help_{{ lang_code }}">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-6">
-                                            <formgroup
-                                                data-id="questionset_verbose_name_{{ lang_code }}"
-                                                data-type="text"
-                                                data-label="{% trans 'Verbose name' %} ({{ lang }})"
-                                                data-help="{% trans 'The verbose name for this question set.' %}"
-                                                data-model="service.values.verbose_name_{{ lang_code }}"
-                                                data-errors="service.errors.verbose_name_{{ lang_code }}">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-6">
-                                            <formgroup
-                                                data-id="questionset_verbose_name_plural_{{ lang_code }}"
-                                                data-type="text"
-                                                data-label="{% trans 'Verbose name plural' %} ({{ lang }})"
-                                                data-help="{% trans 'The plural verbose name for this question set.' %}"
-                                                data-model="service.values.verbose_name_plural_{{ lang_code }}"
-                                                data-errors="service.errors.verbose_name_plural_{{ lang_code }}">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                </div>
-                                {% endfor %}
-                                <div role="tabpanel" class="tab-pane" id="questionsets-conditions">
-                                    <formgroup
-                                        data-id="questionset_conditions"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Conditions' %}"
-                                        data-model="service.values.conditions"
-                                        data-errors="service.errors.conditions"
-                                        data-options="service.conditions"
-                                        data-options-label="uri"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                {% if settings.MULTISITE %}
-                                <div role="tabpanel" class="tab-pane" id="questionsets-editors">
-                                    <formgroup
-                                        data-id="questionsets_editors"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Editors' %}"
-                                        data-model="service.values.editors"
-                                        data-errors="service.errors.editors"
-                                        data-options="service.sites"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                {% endif %}
-                            </div>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitFormModal('questionsets')">
-                        <span ng-show="service.copy">{% trans 'Copy' %}</span>
-                        <span ng-hide="service.copy">{% trans 'Save' %}</span>
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/questions/templates/questions/catalogs_modal_form_sections.html b/rdmo/questions/templates/questions/catalogs_modal_form_sections.html
deleted file mode 100644
index 23e5aee037..0000000000
--- a/rdmo/questions/templates/questions/catalogs_modal_form_sections.html
+++ /dev/null
@@ -1,218 +0,0 @@
-{% load i18n %}
-{% get_available_languages as languages %}
-
-    <div class="modal" id="sections-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        <span ng-hide="service.values.id">{% trans 'Create section' %}</span>
-                        <span ng-show="service.values.copy">{% trans 'Copy section' %}</span>
-                        <span ng-show="service.values.id && !service.values.copy">{% trans 'Update section' %}</span>
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <div class="copy-block" ng-show="service.values.copy">
-                        <p>
-                            {% trans 'Please enter the values for the copy of the section below and change at least the <strong>key</strong>.' %}
-                        </p>
-                        <p>
-                            {% trans 'Other properties can be changed once the new section is created.' %}
-                        </p>
-                    </div>
-                    <div class="row">
-                        <div class="col-sm-6">
-                            <div class="form-group" ng-class="{'has-error': service.errors.uri_prefix }">
-                                <label class="control-label" for="uri_prefix">
-                                    {{ meta.Section.uri_prefix.verbose_name }}
-                                </label>
-
-                                <div class="input-group">
-                                    <input class="form-control"
-                                           id="section_uri_prefix"
-                                           type="text"
-                                           ng-model="service.values.uri_prefix" />
-
-                                    <span class="input-group-btn">
-                                        <button type="button" class="btn btn-default"
-                                            title="{% trans 'Insert default URI Prefix' %}"
-                                            ng-click="service.values.uri_prefix = service.settings.default_uri_prefix">
-
-                                            <span class="fa fa-magic"></span>
-                                        </button>
-                                    </span>
-                                </div>
-
-                                <p class="help-block">
-                                    {{ meta.Section.uri_prefix.help_text }}
-                                </p>
-
-                                <ul class="help-block list-unstyled" ng-show="service.errors.uri_prefix">
-                                    <li ng-repeat="error in service.errors.uri_prefix">
-                                        {$ error $}
-                                    </li>
-                                </ul>
-                            </div>
-                        </div>
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="section_key"
-                                data-type="text"
-                                data-label="{{ meta.Section.key.verbose_name }}"
-                                data-help="{{ meta.Section.key.help_text }}"
-                                data-model="service.values.key"
-                                data-errors="service.errors.key">
-                            </formgroup>
-                        </div>
-                    </div>
-                    <div ng-show="service.values.copy">
-                        <formgroup
-                            data-id="section_catalog"
-                            data-type="selectnumber"
-                            data-label="{{ meta.Section.catalog.verbose_name }}"
-                            data-help="{{ meta.Section.catalog.help_text }}"
-                            data-model="service.values.catalog"
-                            data-errors="service.errors.catalog"
-                            data-options="service.catalogs"
-                            data-options-label="uri"
-                            data-quicksearch="true">
-                        </formgroup>
-                    </div>
-                    <div ng-hide="service.values.copy">
-                        <formgroup
-                            data-id="section_comment"
-                            data-type="textarea"
-                            data-label="{{ meta.Section.comment.verbose_name }}"
-                            data-help="{{ meta.Section.comment.help_text }}"
-                            data-model="service.values.comment"
-                            data-errors="service.errors.comment">
-                        </formgroup>
-                        <formgroup
-                            data-id="section_locked"
-                            data-type="checkbox"
-                            data-label="{{ meta.Section.locked.verbose_name }}"
-                            data-help="{{ meta.Section.locked.help_text }}"
-                            data-model="service.values.locked"
-                            data-errors="service.errors.locked">
-                        </formgroup>
-                        <div>
-                            <ul class="nav nav-tabs" role="tablist">
-                                <li role="presentation" class="active" ng-class="{
-                                    'has-error': service.errors.catalog || service.errors.order
-                                }">
-                                    <a class="control-label" href="#sections-general"
-                                        role="tab" data-toggle="tab" aria-controls="sections-general">
-
-                                        {% trans 'General' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% for lang_code, lang in languages %}
-                                <li role="presentation" ng-class="{
-                                    'has-error':
-                                        service.errors.title_{{ lang_code }},
-                                    'has-warning':
-                                        service.values.id && !service.values.title_{{ lang_code }}
-                                }">
-                                    <a role="tab" class="control-label" data-toggle="tab"
-                                        href="#sections-{{ lang_code }}" aria-controls="sections-{{ lang_code }}">
-
-                                        {{ lang }}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endfor %}
-                                {% if settings.MULTISITE %}
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.editors,
-                                }">
-                                    <a class="control-label" href="#sections-editors"
-                                        role="tab" data-toggle="tab" aria-controls="sections-editors">
-
-                                        {% trans 'Editors' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endif %}
-                            </ul>
-                            <div class="tab-content">
-                                <div role="tabpanel" class="tab-pane active" id="sections-general">
-                                    <div class="row">
-                                        <div class="col-sm-8">
-                                            <formgroup
-                                                data-id="section_catalog"
-                                                data-type="selectnumber"
-                                                data-label="{{ meta.Section.catalog.verbose_name }}"
-                                                data-help="{{ meta.Section.catalog.help_text }}"
-                                                data-model="service.values.catalog"
-                                                data-errors="service.errors.catalog"
-                                                data-options="service.catalogs"
-                                                data-options-label="uri"
-                                                data-quicksearch="true">
-                                            </formgroup>
-                                        </div>
-                                        <div class="col-sm-4">
-                                            <formgroup
-                                                data-id="section_order"
-                                                data-type="number"
-                                                data-label="{{ meta.Section.order.verbose_name }}"
-                                                data-help="{{ meta.Section.order.help_text }}"
-                                                data-model="service.values.order"
-                                                data-errors="service.errors.order">
-                                            </formgroup>
-                                        </div>
-                                    </div>
-                                </div>
-                                {% for lang_code, lang in languages %}
-                                <div role="tabpanel" id="sections-{{ lang_code }}" class="tab-pane">
-
-                                    <formgroup
-                                        data-id="section_title_{{ lang_code }}"
-                                        data-type="text"
-                                        data-label="{% trans 'Title' %} ({{ lang }})"
-                                        data-help="{% trans 'The title for this section.' %}"
-                                        data-model="service.values.title_{{ lang_code }}"
-                                        data-errors="service.errors.title_{{ lang_code }}"
-                                        data-warning="service.values.id && !service.values.title_{{ lang_code }}">
-                                    </formgroup>
-                                </div>
-                                {% endfor %}
-                                {% if settings.MULTISITE %}
-                                <div role="tabpanel" class="tab-pane" id="sections-editors">
-                                    <formgroup
-                                        data-id="section_editors"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Editors' %}"
-                                        data-model="service.values.editors"
-                                        data-errors="service.errors.editors"
-                                        data-options="service.sites"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                {% endif %}
-                            </div>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitFormModal('sections')">
-                        {% trans 'Save' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/questions/templates/questions/catalogs_options_catalog.html b/rdmo/questions/templates/questions/catalogs_options_catalog.html
deleted file mode 100644
index 9d787df88d..0000000000
--- a/rdmo/questions/templates/questions/catalogs_options_catalog.html
+++ /dev/null
@@ -1,31 +0,0 @@
-{% load i18n %}
-    
-    <i class="fa fa-lock text-muted" ng-show="service.catalog.locked"></i>
-    <i class="fa fa-ban text-muted" ng-show="service.catalog.read_only"
-        title="{% trans 'Read only' %}">
-    </i>
-
-    <a href="" class="fa fa-plus"
-        title="{% trans 'Add section' %}"
-        ng-click="service.openFormModal('sections', service.catalog, true)">
-    </a>
-
-    <a href="" class="fa fa-pencil"
-        title="{% trans 'Update catalog' %}"
-        ng-click="service.openFormModal('catalogs', service.catalog)">
-    </a>
-
-    <a href="" class="fa fa-copy"
-        title="{% trans 'Copy catalog' %}"
-        ng-click="service.openFormModal('catalogs', service.catalog, false, true)">
-    </a>
-
-    <a href="{$ service.catalog.xml_url $}" class="fa fa-download"
-        title="{% trans 'Export catalog as XML' %}"
-        target="blank">
-    </a>
-
-    <a href="" class="fa fa-trash"
-        title="{% trans 'Delete catalog' %}"
-        ng-click="service.openDeleteModal('catalogs', service.catalog)">
-    </a>
diff --git a/rdmo/questions/templates/questions/catalogs_options_questions.html b/rdmo/questions/templates/questions/catalogs_options_questions.html
deleted file mode 100644
index 39b0dee357..0000000000
--- a/rdmo/questions/templates/questions/catalogs_options_questions.html
+++ /dev/null
@@ -1,27 +0,0 @@
-{% load i18n %}
-
-    <i class="fa fa-lock text-muted" ng-show="questionset.locked || section.locked || service.catalog.locked"></i>
-    <i class="fa fa-lock text-danger" ng-show="question.locked"></i>
-    <i class="fa fa-ban text-muted" ng-show="question.read_only"
-        title="{% trans 'Read only' %}">
-    </i>
-
-    <a href="" class="fa fa-pencil"
-        title="{% trans 'Update question' %}"
-        ng-click="service.openFormModal('questions', question)">
-    </a>
-
-    <a href="" class="fa fa-copy"
-        title="{% trans 'Copy question' %}"
-        ng-click="service.openFormModal('questions', question, false, true)">
-    </a>
-
-    <a href="{$ question.xml_url $}" class="fa fa-download"
-        title="{% trans 'Export question as XML' %}"
-        target="blank">
-    </a>
-
-    <a href="" class="fa fa-trash"
-        title="{% trans 'Delete question' %}"
-        ng-click="service.openDeleteModal('questions', question)">
-    </a>
diff --git a/rdmo/questions/templates/questions/catalogs_options_questionsets.html b/rdmo/questions/templates/questions/catalogs_options_questionsets.html
deleted file mode 100644
index c8dc7cf86e..0000000000
--- a/rdmo/questions/templates/questions/catalogs_options_questionsets.html
+++ /dev/null
@@ -1,37 +0,0 @@
-{% load i18n %}
-
-    <i class="fa fa-lock text-muted" ng-show="section.locked || service.catalog.locked"></i>
-    <i class="fa fa-lock text-danger" ng-show="questionset.locked"></i>
-    <i class="fa fa-ban text-muted" ng-show="questionset.read_only"
-        title="{% trans 'Read only' %}">
-    </i>
-
-    <a href="" class="fa fa-plus"
-        title="{% trans 'Add question' %}"
-        ng-click="service.openFormModal('questions', questionset, true)">
-    </a>
-
-    <a href="" class="fa fa-plus-square"
-        title="{% trans 'Add question set' %}"
-        ng-click="service.openFormModal('questionsets', questionset, true)">
-    </a>
-
-    <a href="" class="fa fa-pencil"
-        title="{% trans 'Update question set' %}"
-        ng-click="service.openFormModal('questionsets', questionset)">
-    </a>
-
-    <a href="" class="fa fa-copy"
-        title="{% trans 'Copy question set' %}"
-        ng-click="service.openFormModal('questionsets', questionset, false, true)">
-    </a>
-
-    <a href="{$ questionset.xml_url $}" class="fa fa-download"
-        title="{% trans 'Export question set as XML' %}"
-        target="blank">
-    </a>
-
-    <a href="" class="fa fa-trash"
-        title="{% trans 'Delete question set' %}"
-        ng-click="service.openDeleteModal('questionsets', questionset)">
-    </a>
diff --git a/rdmo/questions/templates/questions/catalogs_options_sections.html b/rdmo/questions/templates/questions/catalogs_options_sections.html
deleted file mode 100644
index b8b410755e..0000000000
--- a/rdmo/questions/templates/questions/catalogs_options_sections.html
+++ /dev/null
@@ -1,32 +0,0 @@
-{% load i18n %}
-
-    <i class="fa fa-lock text-muted" ng-show="service.catalog.locked"></i>
-    <i class="fa fa-lock text-danger" ng-show="section.locked"></i>
-    <i class="fa fa-ban text-muted" ng-show="section.read_only"
-        title="{% trans 'Read only' %}">
-    </i>
-
-    <a href="" class="fa fa-plus"
-        title="{% trans 'Add question set' %}"
-        ng-click="service.openFormModal('questionsets', section, true)">
-    </a>
-
-    <a href="" class="fa fa-pencil"
-        title="{% trans 'Update section' %}"
-        ng-click="service.openFormModal('sections', section)">
-    </a>
-
-    <a href="" class="fa fa-copy"
-        title="{% trans 'Copy section' %}"
-        ng-click="service.openFormModal('sections', section, false, true)">
-    </a>
-
-    <a href="{$ section.xml_url $}" class="fa fa-download"
-        title="{% trans 'Export section as XML' %}"
-        target="blank">
-    </a>
-
-    <a href="" class="fa fa-trash"
-        title="{% trans 'Delete section' %}"
-        ng-click="service.openDeleteModal('sections', section)">
-    </a>
diff --git a/rdmo/questions/templates/questions/catalogs_sidebar.html b/rdmo/questions/templates/questions/catalogs_sidebar.html
deleted file mode 100644
index eb45baad70..0000000000
--- a/rdmo/questions/templates/questions/catalogs_sidebar.html
+++ /dev/null
@@ -1,175 +0,0 @@
-{% load i18n %}
-
-    <div ng-cloak>
-
-        <h2>{% trans 'Catalog' %}</h2>
-
-        <select class="form-control" ng-model="service.current_catalog_id" ng-change="service.initView()" by-number ng-cloak>
-            <option ng-repeat="catalog in service.catalogs | orderBy : 'title'" value="{$ catalog.id $}">{$ catalog.title $}</option>
-        </select>
-
-        {% if settings.MULTISITE %}
-        <div class="form-group">
-            <div class="checkbox">
-                <label>
-                    <input type="checkbox"
-                    ng-model="service.filterCatalogsCurrentSite"
-                    ng-change="service.init()">
-                    {% trans 'Filter catalogs for' %} {{ request.site.domain }}
-                </label>
-            </div>
-        </div>
-        {% endif %}
-
-        <h2>{% trans 'Filter questions' %}</h2>
-
-        <div class="form-group">
-            <div class="input-group">
-                <input type="text" class="form-control" ng-model="service.filter"
-                       placeholder="{% trans 'Search' %}"></input>
-                <span class="input-group-btn" ng-click="service.filter = ''">
-                    <button class="btn btn-default">
-                        <span class="fa fa-times"></span>
-                    </button>
-                </span>
-            </div>
-        </div>
-
-        <div class="form-group">
-            <select class="form-control" ng-model="service.uri_prefix">
-                <option value="">{% trans 'All URI prefixes' %}</option>
-                <option ng-repeat="uri_prefix in service.uri_prefixes" value="{$ uri_prefix $}">
-                    {$ uri_prefix $}
-                </option>
-            </select>
-        </div>
-
-        <div class="form-group">
-            <div class="checkbox">
-                <label>
-                    <input type="checkbox" ng-model="service.showQuestionSets">
-                    {% trans 'Show questionsets' %}
-                </label>
-            </div>
-        </div>
-
-        <div class="form-group" ng-show="service.showQuestionSets">
-            <div class="checkbox">
-                <label>
-                    <input type="checkbox" ng-model="service.showQuestions">
-                    {% trans 'Show questions' %}
-                </label>
-            </div>
-        </div>
-
-        <div class="form-group">
-            <span class="checkbox">
-                <label>
-                    <input type="checkbox" ng-model="service.showURIs">
-                    {% trans 'Show URIs' %} 
-                </label>
-            </span>
-        </div>
-
-        <div ng-show="service.showURIs">
-            {% include 'questions/catalogs_sidebar_showuri_checkboxes.html' %}
-        </div>
-
-        <h2>{% trans 'Options' %}</h2>
-
-        <ul class="list-unstyled">
-            <li>
-                <a href="" ng-click="service.openFormModal('catalogs', service.catalog)">
-                    {% trans 'Update catalog details' %}
-                    <i class="fa fa-lock text-danger" ng-show="service.catalog.locked"></i>
-                </a>
-            </li>
-            <li>
-                <a href="" ng-click="service.openFormModal('catalogs', service.catalog, false, true)">
-                    {% trans 'Copy catalog' %}
-                </a>
-            </li>
-            <li>
-                <a href="" ng-click="service.openDeleteModal('catalogs', service.catalog)">
-                    {% trans 'Delete catalog' %}
-                </a>
-            </li>
-        </ul>
-
-        <ul class="list-unstyled">
-            <li>
-                <a href="" ng-click="service.openFormModal('catalogs', null, true)">
-                    {% trans 'Create new catalog' %}
-                </a>
-            </li>
-            <li>
-                <a href="" ng-click="service.openFormModal('sections', service.catalog, true)">
-                    {% trans 'Create new section' %}
-                </a>
-            </li>
-            <li>
-                <a href="" ng-click="service.openFormModal('questionsets', null, true)">
-                    {% trans 'Create new questionset' %}
-                </a>
-            </li>
-            <li>
-                <a href="" ng-click="service.openFormModal('questions', null, true)">
-                    {% trans 'Create new question' %}
-                </a>
-            </li>
-        </ul>
-
-        <h2>{% trans 'Export' %}</h2>
-
-        <ul class="list-unstyled">
-            <li>
-                <form id="export-form" class="sidebar-form" method="GET" action="" target="_blank">
-                    <select class="export-form-field form-control" tabindex="-1" aria-hidden="true">
-                        <option value="{$ service.catalog.xml_url $}">
-                            {% trans 'XML' %}
-                        </option>
-
-                        {% for format, text in export_formats %}
-                        <option value="{$ service.catalog.export_urls.{{ format }} $}">
-                            {{ text }}
-                        </option>
-                        {% endfor %}
-                    </select>
-
-                    <div class="sidebar-form-button">
-                        <button type="submit" class="btn btn-primary" title="{% trans 'Export' %}">
-                            <i class="fa fa-arrow-right"></i>
-                        </button>
-                    </div>
-                </form>
-
-                <script>
-                    $(document).ready(function() {
-                        $('#export-form').submit(function( event ) {
-                            event.preventDefault();
-                            window.open($('#export-form > select').val(), '_blank');
-                        });
-                    });
-                </script>
-            </li>
-        </ul>
-
-        <h2>{% trans 'Import' %}</h2>
-
-        <ul class="list-unstyled">
-            <li>
-                {% url 'upload' as upload_url %}
-                {% include 'core/upload_form.html' with upload_url=upload_url %}
-            </li>
-        </ul>
-
-        <ul class="list-unstyled">
-            <li>
-                <p ng-show="service.catalog.projects_count">
-                    {% blocktrans trimmed with projects_count='{$ service.catalog.projects_count $}' %}
-                    This catalog is used in <b>{{ projects_count }} projects</b>.
-                    {% endblocktrans %}
-                </p>
-            </li>
-        </ul>
-    </div>
diff --git a/rdmo/questions/templates/questions/catalogs_sidebar_showuri_checkboxes.html b/rdmo/questions/templates/questions/catalogs_sidebar_showuri_checkboxes.html
deleted file mode 100644
index 1958469e1a..0000000000
--- a/rdmo/questions/templates/questions/catalogs_sidebar_showuri_checkboxes.html
+++ /dev/null
@@ -1,31 +0,0 @@
-{% load i18n %}
-<div class="form-group" ng-show="service.showURIs && service.showQuestionSets">
-  <div class="input-group">
-    <ul class="list-showURI-checkboxes" ng-show="service.showURIs">
-      <li class="form-group showURI-checkbox checkbox" ng-show="service.showQuestionSets && service.showQuestions">
-        <label>
-          <input type="checkbox" ng-model="service.showURIQuestions">
-          <code class="code-questions" title="{% trans 'URI' %}">{% trans 'Questions' %}</code>
-        </label>
-      </li>
-      <li class="form-group showURI-checkbox checkbox" ng-show="service.showQuestionSets">
-        <label>
-          <input type="checkbox" ng-model="service.showURIConditions">
-          <code class="code-conditions" title="{% trans 'Condition' %}">{% trans 'Conditions' %}</code>
-        </label>
-      </li>
-      <li class="form-group showURI-checkbox checkbox" ng-show="service.showQuestionSets">
-        <label>
-          <input type="checkbox" ng-model="service.showURIAttributes">
-          <code title="{% trans 'Attributes' %}">{% trans 'Attributes' %}</code>
-        </label>
-      </li>
-      <li class="form-group showURI-checkbox checkbox" ng-show="service.showQuestionSets && service.showQuestions">
-        <label>
-          <input type="checkbox" ng-model="service.showURIOptions">
-          <code class="code-options" title="{% trans 'Options' %}">{% trans 'Options' %}</code>
-        </label>
-      </li>
-    </ul>
-  </div>
-</div>
diff --git a/rdmo/questions/urls/__init__.py b/rdmo/questions/urls/__init__.py
index 7023733900..e69de29bb2 100644
--- a/rdmo/questions/urls/__init__.py
+++ b/rdmo/questions/urls/__init__.py
@@ -1,8 +0,0 @@
-from django.urls import re_path
-
-from ..views import CatalogExportView, CatalogsView
-
-urlpatterns = [
-    re_path(r'^catalogs/(?P<pk>[0-9]+)/export/(?P<format>[a-z]+)/$', CatalogExportView.as_view(), name='questions_catalog_export'),
-    re_path(r'^catalogs/', CatalogsView.as_view(), name='catalogs'),
-]
diff --git a/rdmo/questions/validators.py b/rdmo/questions/validators.py
index ed9fab909e..f8fca212d3 100644
--- a/rdmo/questions/validators.py
+++ b/rdmo/questions/validators.py
@@ -40,26 +40,35 @@ class QuestionSetQuestionSetValidator(InstanceValidator):
 
     def __call__(self, data):
         questionsets = data.get('questionsets')
-        if questionsets:
-            if self.serializer:
-                # check copied attributes
-                view = self.serializer.context.get('view')
-                if view and view.action == 'copy':
-                    # get the original from the view when cloning an attribute
-                    obj = view.get_object()
-                    for questionset in questionsets:
-                        if obj in [questionset] + questionset.descendants:
-                            self.raise_validation_error({
-                                'questionset': [_('A question set may not be cloned to be a child of itself or one of its descendants.')]
-                            })
-
-            # only check updated attributes
-            if self.instance:
+        if not questionsets:
+            return
+
+        if not self.serializer and not self.instance:
+            return
+
+        if self.serializer:
+            # check copied attributes
+            view = self.serializer.context.get('view')
+            if view and view.action == 'copy':
+                # get the original from the view when cloning an attribute
+                obj = view.get_object()
                 for questionset in questionsets:
-                    if self.instance in [questionset] + questionset.descendants:
+                    if obj in [questionset] + questionset.descendants:
                         self.raise_validation_error({
-                            'questionsets': [_('A question set may not be a child of itself or one of its descendants.')]
+                            'questionset': [_('A question set may not be cloned to be a child of itself or one of its descendants.')]
                         })
+            if not self.instance:
+                return
+
+        # only check updated attributes
+        if not self.instance:
+            return
+
+        for questionset in questionsets:
+            if self.instance in [questionset] + questionset.descendants:
+                self.raise_validation_error({
+                    'questionsets': [_('A question set may not be a child of itself or one of its descendants.')]
+                })
 
 
 class CatalogLockedValidator(LockedValidator):
diff --git a/rdmo/questions/views.py b/rdmo/questions/views.py
deleted file mode 100644
index f802d267df..0000000000
--- a/rdmo/questions/views.py
+++ /dev/null
@@ -1,64 +0,0 @@
-import logging
-
-from django.conf import settings
-from django.db.models import Prefetch
-from django.views.generic import DetailView, TemplateView
-
-from rdmo.core.exports import XMLResponse
-from rdmo.core.utils import get_model_field_meta, render_to_format
-from rdmo.core.views import CSRFViewMixin, ModelPermissionMixin
-
-from .models import Catalog, Page, Question, QuestionSet, Section
-from .renderers import CatalogRenderer
-from .serializers.export import CatalogExportSerializer
-
-log = logging.getLogger(__name__)
-
-
-class CatalogsView(ModelPermissionMixin, CSRFViewMixin, TemplateView):
-    template_name = 'questions/catalogs.html'
-    permission_required = 'questions.view_catalog'
-
-    def get_context_data(self, **kwargs):
-        context = super(CatalogsView, self).get_context_data(**kwargs)
-        context['export_formats'] = settings.EXPORT_FORMATS
-        context['meta'] = {
-            'Catalog': get_model_field_meta(Catalog),
-            'Section': get_model_field_meta(Section),
-            'QuestionSet': get_model_field_meta(QuestionSet),
-            'Question': get_model_field_meta(Question),
-        }
-        return context
-
-
-class CatalogExportView(ModelPermissionMixin, DetailView):
-    model = Catalog
-    context_object_name = 'catalog'
-    permission_required = 'questions.view_catalog'
-
-    def get_queryset(self):
-        return Catalog.objects.prefetch_related(
-            'sections',
-            Prefetch('sections__pages', queryset=Page.objects.prefetch_related(
-                'conditions',
-                'questions',
-                'questions__attribute',
-                'questions__optionsets',
-                'questionsets',
-                'questionsets__attribute',
-                'questionsets__conditions',
-                'questionsets__questions',
-                'questionsets__questions__attribute',
-                'questionsets__questions__optionsets',
-                'questionsets__questionsets'
-            ).select_related('attribute'))
-        )
-
-    def render_to_response(self, context, **response_kwargs):
-        format = self.kwargs.get('format')
-        if format == 'xml':
-            serializer = CatalogExportSerializer(context['catalog'])
-            xml = CatalogRenderer().render([serializer.data])
-            return XMLResponse(xml, name=context['catalog'].uri_path)
-        else:
-            return render_to_format(self.request, format, context['catalog'].title, 'questions/catalog_export.html', context)
diff --git a/rdmo/questions/viewsets.py b/rdmo/questions/viewsets.py
index c36f0f40b4..4c18aa608d 100644
--- a/rdmo/questions/viewsets.py
+++ b/rdmo/questions/viewsets.py
@@ -11,7 +11,6 @@
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
 from rdmo.core.utils import is_truthy, render_to_format
 from rdmo.core.views import ChoicesViewSet
-from rdmo.core.viewsets import CopyModelMixin
 
 from .models import Catalog, Page, Question, QuestionSet, Section
 from .renderers import (CatalogRenderer, PageRenderer, QuestionRenderer,
@@ -20,22 +19,20 @@
                                  QuestionExportSerializer,
                                  QuestionSetExportSerializer,
                                  SectionExportSerializer)
-from .serializers.v1 import (CatalogIndexSerializer, CatalogListSerializer,
-                             CatalogNestedSerializer, CatalogSerializer,
-                             PageIndexSerializer, PageListSerializer,
+from .serializers.v1 import (CatalogIndexSerializer, CatalogNestedSerializer,
+                             CatalogSerializer, PageIndexSerializer,
                              PageNestedSerializer, PageSerializer,
-                             QuestionIndexSerializer, QuestionListSerializer,
-                             QuestionSerializer, QuestionSetIndexSerializer,
-                             QuestionSetListSerializer,
+                             QuestionIndexSerializer, QuestionSerializer,
+                             QuestionSetIndexSerializer,
                              QuestionSetNestedSerializer,
                              QuestionSetSerializer, SectionIndexSerializer,
-                             SectionListSerializer, SectionNestedSerializer,
-                             SectionSerializer)
+                             SectionNestedSerializer, SectionSerializer)
 from .utils import get_widget_type_choices
 
 
-class CatalogViewSet(CopyModelMixin, ModelViewSet):
+class CatalogViewSet(ModelViewSet):
     permission_classes = (HasModelPermission | HasObjectPermission,)
+    serializer_class = CatalogSerializer
 
     filter_backends = (SearchFilter, DjangoFilterBackend)
     search_fields = ('uri', 'title')
@@ -54,9 +51,6 @@ def get_queryset(self):
         else:
             return queryset.prefetch_related('sites', 'editors', 'groups', 'catalog_sections__section')
 
-    def get_serializer_class(self):
-        return CatalogListSerializer if self.action == 'list' else CatalogSerializer
-
     @action(detail=True)
     def nested(self, request, pk):
         serializer = CatalogNestedSerializer(self.get_object(), context={'request': request})
@@ -105,8 +99,9 @@ def get_export_renderer_context(self, request):
         }
 
 
-class SectionViewSet(CopyModelMixin, ModelViewSet):
+class SectionViewSet(ModelViewSet):
     permission_classes = (HasModelPermission | HasObjectPermission, )
+    serializer_class = SectionSerializer
 
     filter_backends = (SearchFilter, DjangoFilterBackend)
     search_fields = ('uri', 'title')
@@ -124,9 +119,6 @@ def get_queryset(self):
         else:
             return queryset.prefetch_related('catalogs', 'editors', 'section_pages__page')
 
-    def get_serializer_class(self):
-        return SectionListSerializer if self.action == 'list' else SectionSerializer
-
     @action(detail=True)
     def nested(self, request, pk):
         serializer = SectionNestedSerializer(self.get_object(), context={'request': request})
@@ -174,8 +166,9 @@ def get_export_renderer_context(self, request):
         }
 
 
-class PageViewSet(CopyModelMixin, ModelViewSet):
+class PageViewSet(ModelViewSet):
     permission_classes = (HasModelPermission | HasObjectPermission, )
+    serializer_class = PageSerializer
 
     filter_backends = (SearchFilter, DjangoFilterBackend)
     search_fields = ('uri', 'title')
@@ -201,9 +194,6 @@ def get_queryset(self):
                 'page_questions__question'
             ).select_related('attribute')
 
-    def get_serializer_class(self):
-        return PageListSerializer if self.action == 'list' else PageSerializer
-
     @action(detail=True)
     def nested(self, request, pk):
         serializer = PageNestedSerializer(self.get_object(), context={'request': request})
@@ -250,8 +240,9 @@ def get_export_renderer_context(self, request):
         }
 
 
-class QuestionSetViewSet(CopyModelMixin, ModelViewSet):
+class QuestionSetViewSet(ModelViewSet):
     permission_classes = (HasModelPermission | HasObjectPermission, )
+    serializer_class = QuestionSetSerializer
 
     filter_backends = (SearchFilter, DjangoFilterBackend)
     search_fields = ('uri', 'title')
@@ -264,9 +255,6 @@ class QuestionSetViewSet(CopyModelMixin, ModelViewSet):
         'is_collection'
     )
 
-    def get_serializer_class(self):
-        return QuestionSetListSerializer if self.action == 'list' else QuestionSetSerializer
-
     def get_queryset(self):
         queryset = QuestionSet.objects.all()
         if self.action in ('nested', 'export', 'detail_export'):
@@ -327,8 +315,9 @@ def get_export_renderer_context(self, request):
         }
 
 
-class QuestionViewSet(CopyModelMixin, ModelViewSet):
+class QuestionViewSet(ModelViewSet):
     permission_classes = (HasModelPermission | HasObjectPermission, )
+    serializer_class = QuestionSerializer
 
     filter_backends = (SearchFilter, DjangoFilterBackend)
     search_fields = ('uri', 'text')
@@ -344,9 +333,6 @@ class QuestionViewSet(CopyModelMixin, ModelViewSet):
         'comment'
     )
 
-    def get_serializer_class(self):
-        return QuestionListSerializer if self.action == 'list' else QuestionSerializer
-
     def get_queryset(self):
         queryset = Question.objects.all()
         if self.action in ('nested', 'export', 'detail_export'):
diff --git a/rdmo/tasks/admin.py b/rdmo/tasks/admin.py
index e544fc3ffc..b241070485 100644
--- a/rdmo/tasks/admin.py
+++ b/rdmo/tasks/admin.py
@@ -8,7 +8,7 @@
 
 
 class TaskAdminForm(forms.ModelForm):
-    key = forms.SlugField(required=True)
+    uri_path = forms.SlugField(required=True)
 
     class Meta:
         model = Task
diff --git a/rdmo/tasks/imports.py b/rdmo/tasks/imports.py
index ad500a4d61..a6a14fdee4 100644
--- a/rdmo/tasks/imports.py
+++ b/rdmo/tasks/imports.py
@@ -2,9 +2,9 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (set_common_fields, set_foreign_field,
-                               set_lang_field, set_m2m_instances,
-                               validate_instance)
+from rdmo.core.imports import (check_permissions, set_common_fields,
+                               set_foreign_field, set_lang_field,
+                               set_m2m_instances, validate_instance)
 
 from .models import Task
 from .validators import TaskLockedValidator, TaskUniqueURIValidator
@@ -12,7 +12,7 @@
 logger = logging.getLogger(__name__)
 
 
-def import_task(element, save=False):
+def import_task(element, save=False, user=None):
     try:
         task = Task.objects.get(uri=element.get('uri'))
     except Task.DoesNotExist:
@@ -33,6 +33,8 @@ def import_task(element, save=False):
 
     validate_instance(task, element, TaskUniqueURIValidator, TaskLockedValidator)
 
+    check_permissions(task, element, user)
+
     if save and not element.get('errors'):
         if task.id:
             element['updated'] = True
diff --git a/rdmo/tasks/migrations/0035_uri_path.py b/rdmo/tasks/migrations/0035_uri_path.py
new file mode 100644
index 0000000000..b7f7ce5656
--- /dev/null
+++ b/rdmo/tasks/migrations/0035_uri_path.py
@@ -0,0 +1,28 @@
+# Generated by Django 3.2.19 on 2023-06-29 12:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('tasks', '0034_task_editors'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='task',
+            old_name='key',
+            new_name='uri_path',
+        ),
+        migrations.AlterField(
+            model_name='task',
+            name='uri',
+            field=models.URLField(blank=True, help_text='The Uniform Resource Identifier of this task (auto-generated).', max_length=800, verbose_name='URI'),
+        ),
+        migrations.AlterField(
+            model_name='task',
+            name='uri_path',
+            field=models.SlugField(blank=True, help_text='The path for the URI of this task.', max_length=512, verbose_name='URI Path'),
+        ),
+    ]
diff --git a/rdmo/tasks/models.py b/rdmo/tasks/models.py
index d96a70bb9c..179badfb17 100644
--- a/rdmo/tasks/models.py
+++ b/rdmo/tasks/models.py
@@ -17,7 +17,7 @@ class Task(TranslationMixin, models.Model):
     objects = TaskManager()
 
     uri = models.URLField(
-        max_length=640, blank=True,
+        max_length=800, blank=True,
         verbose_name=_('URI'),
         help_text=_('The Uniform Resource Identifier of this task (auto-generated).')
     )
@@ -26,10 +26,10 @@ class Task(TranslationMixin, models.Model):
         verbose_name=_('URI Prefix'),
         help_text=_('The prefix for the URI of this task.')
     )
-    key = models.SlugField(
-        max_length=128, blank=True,
-        verbose_name=_('Key'),
-        help_text=_('The internal identifier of this task.')
+    uri_path = models.SlugField(
+        max_length=512, blank=True,
+        verbose_name=_('URI Path'),
+        help_text=_('The path for the URI of this task.')
     )
     comment = models.TextField(
         blank=True,
@@ -148,23 +148,12 @@ class Meta:
         verbose_name_plural = _('Tasks')
 
     def __str__(self):
-        return self.key
+        return self.uri
 
     def save(self, *args, **kwargs):
-        self.uri = self.build_uri(self.uri_prefix, self.key)
+        self.uri = self.build_uri(self.uri_prefix, self.uri_path)
         super().save(*args, **kwargs)
 
-    def copy(self, uri_prefix, key):
-        task = copy_model(self, uri_prefix=uri_prefix, key=key, start_attribute=self.start_attribute, end_attribute=self.end_attribute)
-
-        # add m2m fields
-        task.catalogs.set(self.catalogs.all())
-        task.sites.set(self.sites.all())
-        task.groups.set(self.groups.all())
-        task.conditions.set(self.conditions.all())
-
-        return task
-
     @property
     def title(self):
         return self.trans('title')
@@ -182,6 +171,7 @@ def is_locked(self):
         return self.locked
 
     @classmethod
-    def build_uri(cls, uri_prefix, key):
-        assert key
-        return join_url(uri_prefix or settings.DEFAULT_URI_PREFIX, '/tasks/', key)
+    def build_uri(cls, uri_prefix, uri_path):
+        if not uri_path:
+            raise RuntimeError('uri_path is missing')
+        return join_url(uri_prefix or settings.DEFAULT_URI_PREFIX, '/tasks/', uri_path)
diff --git a/rdmo/tasks/renderers/mixins.py b/rdmo/tasks/renderers/mixins.py
index 9bdcb32358..48e879c50c 100644
--- a/rdmo/tasks/renderers/mixins.py
+++ b/rdmo/tasks/renderers/mixins.py
@@ -9,7 +9,7 @@ def render_task(self, xml, task):
 
             xml.startElement('task', {'dc:uri': task['uri']})
             self.render_text_element(xml, 'uri_prefix', {}, task['uri_prefix'])
-            self.render_text_element(xml, 'key', {}, task['key'])
+            self.render_text_element(xml, 'uri_path', {}, task['uri_path'])
             self.render_text_element(xml, 'dc:comment', {}, task['comment'])
 
             for lang_code, lang_string, lang_field in get_languages():
diff --git a/rdmo/tasks/serializers/export.py b/rdmo/tasks/serializers/export.py
index 7160b228d8..7de22136d8 100644
--- a/rdmo/tasks/serializers/export.py
+++ b/rdmo/tasks/serializers/export.py
@@ -18,7 +18,7 @@ class Meta:
         fields = (
             'uri',
             'uri_prefix',
-            'key',
+            'uri_path',
             'comment',
             'start_attribute',
             'end_attribute',
diff --git a/rdmo/tasks/serializers/v1.py b/rdmo/tasks/serializers/v1.py
index 89feed0900..8940268c2b 100644
--- a/rdmo/tasks/serializers/v1.py
+++ b/rdmo/tasks/serializers/v1.py
@@ -1,21 +1,26 @@
 from rest_framework import serializers
 
-from rdmo.core.serializers import (ElementExportSerializerMixin,
-                                   ElementModelSerializerMixin,
+from rdmo.core.serializers import (ElementModelSerializerMixin,
                                    ElementWarningSerializerMixin,
-                                   TranslationSerializerMixin,
-                                   ReadOnlyObjectPermissionsSerializerMixin)
+                                   ReadOnlyObjectPermissionSerializerMixin,
+                                   TranslationSerializerMixin)
 
 from ..models import Task
 from ..validators import TaskLockedValidator, TaskUniqueURIValidator
 
 
-class BaseTaskSerializer(TranslationSerializerMixin, ElementModelSerializerMixin,
-                         ReadOnlyObjectPermissionsSerializerMixin, serializers.ModelSerializer):
+class TaskSerializer(TranslationSerializerMixin, ElementModelSerializerMixin,
+                     ElementWarningSerializerMixin, ReadOnlyObjectPermissionSerializerMixin,
+                     serializers.ModelSerializer):
 
     model = serializers.SerializerMethodField()
+    uri_path = serializers.CharField(required=True)
+
+    warning = serializers.SerializerMethodField()
     read_only = serializers.SerializerMethodField()
 
+    projects_count = serializers.IntegerField(read_only=True)
+
     class Meta:
         model = Task
         fields = (
@@ -23,14 +28,13 @@ class Meta:
             'model',
             'uri',
             'uri_prefix',
-            'key',
+            'uri_path',
             'comment',
             'locked',
             'available',
             'catalogs',
             'sites',
             'editors',
-            'read_only',
             'groups',
             'start_attribute',
             'end_attribute',
@@ -38,40 +42,19 @@ class Meta:
             'days_after',
             'conditions',
             'title',
-            'text'
+            'text',
+            'warning',
+            'read_only',
+            'projects_count',
         )
         trans_fields = (
             'title',
             'text'
         )
-
-
-class TaskSerializer(BaseTaskSerializer):
-
-    key = serializers.SlugField(required=True)
-    projects_count = serializers.IntegerField(read_only=True)
-
-    class Meta(BaseTaskSerializer.Meta):
-        fields = BaseTaskSerializer.Meta.fields + (
-            'projects_count',
-        )
         validators = (
             TaskUniqueURIValidator(),
             TaskLockedValidator()
         )
-
-
-class TaskListSerializer(ElementExportSerializerMixin, ElementWarningSerializerMixin,
-                         BaseTaskSerializer):
-
-    warning = serializers.SerializerMethodField()
-    xml_url = serializers.SerializerMethodField()
-
-    class Meta(BaseTaskSerializer.Meta):
-        fields = BaseTaskSerializer.Meta.fields + (
-            'warning',
-            'xml_url'
-        )
         warning_fields = (
             'title',
         )
diff --git a/rdmo/tasks/static/tasks/css/tasks.scss b/rdmo/tasks/static/tasks/css/tasks.scss
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/rdmo/tasks/static/tasks/js/tasks.js b/rdmo/tasks/static/tasks/js/tasks.js
deleted file mode 100644
index 1e7a7ad4c8..0000000000
--- a/rdmo/tasks/static/tasks/js/tasks.js
+++ /dev/null
@@ -1,142 +0,0 @@
-angular.module('tasks', ['core'])
-
-.factory('TasksService', ['$resource', '$timeout', '$window', '$q', 'utils', '$sce', function($resource, $timeout, $window, $q, utils, $sce) {
-
-    /* get the base url */
-
-    var baseurl = angular.element('meta[name="baseurl"]').attr('content');
-
-    /* configure resources */
-
-    var resources = {
-        tasks: $resource(baseurl + 'api/v1/tasks/tasks/:list_action/:id/:detail_action/'),
-        catalogs: $resource(baseurl + 'api/v1/questions/catalogs/:list_action/'),
-        attributes: $resource(baseurl + 'api/v1/domain/attributes/:list_action/'),
-        conditions: $resource(baseurl + 'api/v1/conditions/conditions/:list_action/'),
-        settings: $resource(baseurl + 'api/v1/core/settings/'),
-        sites: $resource(baseurl + 'api/v1/core/sites/'),
-        groups: $resource(baseurl + 'api/v1/core/groups/'),
-    };
-
-    /* configure factories */
-
-    var factories = {
-        tasks: function(parent) {
-            return {
-                uri_prefix: (angular.isDefined(parent) && parent) ? parent.uri_prefix : service.settings.default_uri_prefix,
-                attribute: null,
-                sites: [1]
-            };
-        }
-    };
-
-    /* create the tasks service */
-
-    var service = {};
-
-    service.init = function(options) {
-        service.settings = resources.settings.get();
-        service.sites = resources.sites.query();
-        service.groups = resources.groups.query();
-        service.uri_prefixes = []
-        service.uri_prefix = ''
-        service.filter = sessionStorage.getItem('tasks_filter') || '';
-
-        service.initView().then(function () {
-            var current_scroll_pos = sessionStorage.getItem('tasks_scroll_pos');
-            if (current_scroll_pos) {
-                $timeout(function() {
-                    $window.scrollTo(0, current_scroll_pos);
-                });
-            }
-        });
-
-        $window.addEventListener('beforeunload', function() {
-            sessionStorage.setItem('tasks_scroll_pos', $window.scrollY);
-            sessionStorage.setItem('tasks_filter', service.filter);
-        });
-    };
-
-    service.initView = function(options) {
-        return resources.tasks.query({list_action: 'index'}, function(response) {
-            service.tasks = response;
-            service.uri_prefixes = response.reduce(function(list, item) {
-                if (list.indexOf(item.uri_prefix) < 0) {
-                    list.push(item.uri_prefix)
-                }
-                return list
-            }, [])
-
-            // mark text safe
-            service.tasks.map(function(task) {
-                task.text_html = $sce.trustAsHtml(task.text);
-            });
-        }).$promise;
-    };
-
-    service.openFormModal = function(resource, obj, create, copy) {
-        service.errors = {};
-        service.values = utils.fetchValues(resources['tasks'], factories['tasks'], obj, create, copy);
-        service.catalogs = resources.catalogs.query({ list_action: 'index' });
-        service.attributes = resources.attributes.query({ list_action: 'index' });
-        service.conditions = resources.conditions.query({ list_action: 'index' });
-
-        $q.when([
-            service.values.$promise,
-            service.catalogs.$promise,
-            service.attributes.$promise,
-            service.conditions.$promise
-        ]).then(function() {
-            $('#' + resource + '-form-modal').modal('show');
-            $timeout(function() {
-                $('formgroup[data-quicksearch="true"]').trigger('refresh');
-            });
-        });
-    };
-
-    service.submitFormModal = function(resource) {
-        utils.storeValues(resources['tasks'], service.values).then(function() {
-            $('.modal').modal('hide');
-            service.initView();
-        }, function(result) {
-            service.errors = result.data;
-        });
-    };
-
-    service.openDeleteModal = function(resource, obj) {
-        service.values = obj;
-        $('#' + resource + '-delete-modal').modal('show');
-    };
-
-    service.submitDeleteModal = function(resource) {
-        if (angular.isDefined(service.values.id)) {
-            resources[resource].delete({id: service.values.id}, function() {
-                $('.modal').modal('hide');
-                service.initView();
-            });
-        } else {
-            $('.modal').modal('hide');
-        }
-    };
-
-    service.hideTask = function(item) {
-        if (service.filter && item.uri.indexOf(service.filter) < 0
-                           && item.title.indexOf(service.filter) < 0
-                           && item.text.indexOf(service.filter) < 0) {
-            return true;
-        }
-        if (service.uri_prefix && item.uri_prefix != service.uri_prefix) {
-            return true;
-        }
-    };
-
-    return service;
-
-}])
-
-.controller('TasksController', ['$scope', 'TasksService', function($scope, TasksService) {
-
-    $scope.service = TasksService;
-    $scope.service.init();
-
-}]);
diff --git a/rdmo/tasks/templates/tasks/tasks.html b/rdmo/tasks/templates/tasks/tasks.html
deleted file mode 100644
index 918094f281..0000000000
--- a/rdmo/tasks/templates/tasks/tasks.html
+++ /dev/null
@@ -1,192 +0,0 @@
-{% extends 'core/page.html' %}
-{% load static %}
-{% load compress %}
-{% load i18n %}
-{% load core_tags %}
-
-{% block head %}
-    {% vendor 'angular' %}
-    {% vendor 'codemirror' %}
-    {% vendor 'select2' %}
-    {% vendor 'select2-bootstrap-theme' %}
-
-    {% compress css %}
-    <link rel="stylesheet" type="text/x-scss" href="{% static 'tasks/css/tasks.scss' %}" />
-    {% endcompress %}
-
-    {% compress js %}
-    <script type="text/javascript" src="{% static 'core/js/core.js' %}" ></script>
-    <script type="text/javascript" src="{% static 'tasks/js/tasks.js' %}" ></script>
-    {% endcompress %}
-{% endblock %}
-
-{% block bodyattr %} ng-app="tasks" ng-controller="TasksController" {% endblock %}
-
-{% block sidebar %}
-
-    <h2>{% trans 'Filter tasks' %}</h2>
-
-    <div class="form-group">
-        <div class="input-group">
-            <input type="text" class="form-control" ng-model="service.filter"
-                   placeholder="{% trans 'Search' %}"></input>
-            <span class="input-group-btn" ng-click="service.filter = ''">
-                <button class="btn btn-default">
-                    <span class="fa fa-times"></span>
-                </button>
-            </span>
-        </div>
-    </div>
-
-    <div class="form-group">
-        <select class="form-control" ng-model="service.uri_prefix">
-            <option value="">{% trans 'All URI prefixes' %}</option>
-            <option ng-repeat="uri_prefix in service.uri_prefixes" value="{$ uri_prefix $}">
-                {$ uri_prefix $}
-            </option>
-        </select>
-    </div>
-
-    <h2>{% trans 'Options' %}</h2>
-
-    <ul class="list-unstyled">
-        <li>
-            <a href="" ng-click="service.openFormModal('tasks', null, true)">
-                {% trans 'Create new task' %}
-            </a>
-        </li>
-    </ul>
-
-    <h2>{% trans 'Export' %}</h2>
-
-    <ul class="list-unstyled">
-        {% for format, text in export_formats %}
-        <li >
-            <a href="{% url 'tasks_export' format %}" target="_blank">
-                {{ text }}
-            </a>
-        </li>
-        {% endfor %}
-    </ul>
-
-    <ul class="list-unstyled">
-        <li >
-            <a href="{% url 'tasks_export' 'xml' %}" target="_blank">
-                {% trans 'XML' %}
-            </a>
-        </li>
-    </ul>
-
-    <h2>{% trans 'Import' %}</h2>
-    <ul class="list-unstyled">
-        <li>
-            {% url 'upload' as upload_url %}
-            {% include 'core/upload_form.html' with upload_url=upload_url %}
-        </li>
-    </ul>
-
-{% endblock %}
-
-{% block page %}
-
-    <h1>{% trans 'Tasks' %}</h1>
-
-    <div ng-repeat="task in service.tasks | orderBy: 'title'" ng-cloak>
-
-        <div class="panel panel-default" ng-hide="service.hideTask(task)">
-
-            <div class="panel-heading">
-                <div class="pull-right">
-                    <span class="fa fa-warning text-warning"
-                        title="{% trans 'Some languages are not configured' %}"
-                        ng-show="task.warning"></span>
-
-                    <i class="fa fa-lock text-danger" ng-show="task.locked"></i>
-                    <i class="fa fa-ban text-muted" ng-show="task.read_only"
-                        title="{% trans 'Read only' %}">
-                    </i>
-
-                    <a href="" class="fa fa-pencil"
-                        title="{% trans 'Update task' %}"
-                        ng-click="service.openFormModal('tasks', task)">
-                    </a>
-                    <a href="" class="fa fa-copy"
-                        title="{% trans 'Copy task' %}"
-                        ng-click="service.openFormModal('tasks', task, false, true)">
-                    </a>
-                    <a href="" class="fa fa-question-circle"
-                        title="{% trans 'Update conditions' %}"
-                        ng-click="service.openFormModal('conditions', task)">
-                    </a>
-                    <a href="" class="fa fa-clock-o"
-                        title="{% trans 'Update time frame' %}"
-                        ng-click="service.openFormModal('timeframes', task)">
-                    </a>
-                    <a href="{$ task.xml_url $}" class="fa fa-download"
-                        title="{% trans 'Export task as XML' %}"
-                        target="blank">
-                    </a>
-                    <a href="" class="fa fa-trash"
-                        title="{% trans 'Delete task' %}"
-                        ng-click="service.openDeleteModal('tasks', task)">
-                    </a>
-                </div>
-                <strong ng-class="{'text-muted': task.read_only}">
-                    {% trans 'Task' %}
-                </strong>
-                <code class="code-tasks">{$ task.uri $}</code>
-            </div>
-
-            <ul class="list-group">
-                <li class="list-group-item">
-                    <p><strong>{$ task.title $}</strong></p>
-                    <p ng-bind-html="task.text_html"></p>
-                </li>
-            </ul>
-
-            {% if settings.MULTISITE %}
-            <div class="panel-footer">
-                <div class="row">
-                    <div class="col-sm-3">
-                        <strong>{% trans 'Sites' %}</strong>
-                    </div>
-                    <div class="col-sm-9">
-                        <ul>
-                            <li ng-show="task.sites.length > 0 && task.sites.length < service.sites.length"
-                                ng-repeat="site in task.sites">
-                                {$ site.name $}
-                            </li>
-                            <li ng-show="task.sites.length == 0 || task.sites.length == service.sites.length">
-                                {% trans 'All sites' %}
-                            </li>
-                        </ul>
-                    </div>
-                </div>
-                <div class="row">
-                    <div class="col-sm-3">
-                        <strong>{% trans 'Editors' %}</strong>
-                    </div>
-                    <div class="col-sm-9">
-                        <ul>
-                            <li ng-show="task.editors.length > 0 && task.editors.length < service.sites.length"
-                                ng-repeat="editor in task.editors">
-                                {$ editor.name $}
-                            </li>
-                            <li ng-show="task.editors.length == 0 || task.editors.length == service.sites.length">
-                                {% trans 'All sites' %}
-                            </li>
-                        </ul>
-                    </div>
-                </div>
-            </div>
-            {% endif %}
-        </div>
-    </div>
-
-    {% include 'tasks/tasks_modal_form_tasks.html' %}
-    {% include 'tasks/tasks_modal_form_timeframes.html' %}
-    {% include 'tasks/tasks_modal_form_conditions.html' %}
-
-    {% include 'tasks/tasks_modal_delete_tasks.html' %}
-
-{% endblock %}
diff --git a/rdmo/tasks/templates/tasks/tasks_modal_delete_tasks.html b/rdmo/tasks/templates/tasks/tasks_modal_delete_tasks.html
deleted file mode 100644
index 8c42053dc1..0000000000
--- a/rdmo/tasks/templates/tasks/tasks_modal_delete_tasks.html
+++ /dev/null
@@ -1,42 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="tasks-delete-modal" tabindex="-1">
-        <div class="modal-dialog">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title" id="add-modal">
-                        {% trans 'Delete task' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with object='{$ service.values.uri $}' %}
-                        You are about to permanently delete the task <code>{{ object }}</code>.
-                        {% endblocktrans %}
-                    </p>
-
-                    <p class="text-danger">
-                        {% trans 'This action cannot be undone!' %}
-                    </p>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-danger"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitDeleteModal('tasks')">
-                        {% trans 'Delete' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/tasks/templates/tasks/tasks_modal_form_conditions.html b/rdmo/tasks/templates/tasks/tasks_modal_form_conditions.html
deleted file mode 100644
index 7f1053199c..0000000000
--- a/rdmo/tasks/templates/tasks/tasks_modal_form_conditions.html
+++ /dev/null
@@ -1,39 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="conditions-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Conditions' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <formgroup
-                        data-id="conditions"
-                        data-type="selectmultiple"
-                        data-label="{% trans 'Conditions' %}"
-                        data-model="service.values.conditions"
-                        data-errors="service.errors.conditions"
-                        data-options="service.conditions"
-                        data-options-label="uri"
-                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                    </formgroup>
-                </div>
-
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-click="service.submitFormModal('tasks')">
-                        {% trans 'Save' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/tasks/templates/tasks/tasks_modal_form_tasks.html b/rdmo/tasks/templates/tasks/tasks_modal_form_tasks.html
deleted file mode 100644
index 5617e6e22d..0000000000
--- a/rdmo/tasks/templates/tasks/tasks_modal_form_tasks.html
+++ /dev/null
@@ -1,263 +0,0 @@
-{% load i18n %}
-{% get_available_languages as languages %}
-
-    <div class="modal" id="tasks-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        <span ng-hide="service.values.id">{% trans 'Create task' %}</span>
-                        <span ng-show="service.values.copy">{% trans 'Copy task' %}</span>
-                        <span ng-show="service.values.id && !service.values.copy">{% trans 'Update task' %}</span>
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <div class="copy-block" ng-show="service.values.copy">
-                        <p>
-                            {% trans 'Please enter the values for the copy of the task below and change at least the <strong>key</strong>.' %}
-                        </p>
-                        <p>
-                            {% trans 'Other properties can be changed once the new task is created.' %}
-                        </p>
-                    </div>
-                    <div class="row">
-                        <div class="col-sm-6">
-                            <div class="form-group" ng-class="{'has-error': service.errors.uri_prefix }">
-                                <label class="control-label" for="uri_prefix">
-                                    {{ meta.Task.uri_prefix.verbose_name }}
-                                </label>
-
-                                <div class="input-group">
-                                    <input class="form-control"
-                                           id="task_uri_prefix"
-                                           type="text"
-                                           ng-model="service.values.uri_prefix" />
-
-                                    <span class="input-group-btn">
-                                        <button type="button" class="btn btn-default"
-                                            title="{% trans 'Insert default URI Prefix' %}"
-                                            ng-click="service.values.uri_prefix = service.settings.default_uri_prefix">
-
-                                            <span class="fa fa-magic"></span>
-                                        </button>
-                                    </span>
-                                </div>
-
-                                <p class="help-block">
-                                    {{ meta.Task.uri_prefix.help_text }}
-                                </p>
-
-                                <ul class="help-block list-unstyled" ng-show="service.errors.uri_prefix">
-                                    <li ng-repeat="error in service.errors.uri_prefix">
-                                        {$ error $}
-                                    </li>
-                                </ul>
-                            </div>
-                        </div>
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="task_key"
-                                data-type="text"
-                                data-label="{{ meta.Task.key.verbose_name }}"
-                                data-help="{{ meta.Task.key.help_text }}"
-                                data-model="service.values.key"
-                                data-errors="service.errors.key">
-                            </formgroup>
-                        </div>
-                    </div>
-
-                    <div ng-hide="service.values.copy">
-                        <formgroup
-                            data-id="task_comment"
-                            data-type="textarea"
-                            data-label="{{ meta.Task.comment.verbose_name }}"
-                            data-help="{{ meta.Task.comment.help_text }}"
-                            data-model="service.values.comment"
-                            data-errors="service.errors.comment">
-                        </formgroup>
-                        <formgroup
-                            data-id="task_locked"
-                            data-type="checkbox"
-                            data-label="{{ meta.Task.locked.verbose_name }}"
-                            data-help="{{ meta.Task.locked.help_text }}"
-                            data-model="service.values.locked"
-                            data-errors="service.errors.locked">
-                        </formgroup>
-                        <div>
-                            <ul class="nav nav-tabs" role="tablist">
-                                <li role="presentation" class="active">
-                                    <a class="control-label" href="#tasks-general"
-                                        role="tab" data-toggle="tab" aria-controls="tasks-general">
-
-                                        {% trans 'General' %}
-                                    </a>
-                                </li>
-                                {% for lang_code, lang in languages %}
-                                <li role="presentation" ng-class="{
-                                    'has-error':
-                                        service.errors.title_{{ lang_code }} ||
-                                        service.errors.text_{{ lang_code }},
-                                    'has-warning':
-                                        service.values.id &&
-                                        (!service.values.title_{{ lang_code }} ||
-                                         !service.values.text_{{ lang_code }})
-                                }">
-                                    <a role="tab" class="control-label" data-toggle="tab"
-                                        href="#tasks-{{ lang_code }}" aria-controls="tasks-{{ lang_code }}">
-
-                                        {{ lang }}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endfor %}
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.catalogs,
-                                }">
-                                    <a class="control-label" href="#tasks-catalogs"
-                                        role="tab" data-toggle="tab" aria-controls="tasks-catalogs">
-
-                                        {% trans 'Catalogs' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.groups,
-                                }">
-                                    <a class="control-label" href="#tasks-groups"
-                                        role="tab" data-toggle="tab" aria-controls="tasks-groups">
-
-                                        {% trans 'Groups' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% if settings.MULTISITE %}
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.sites,
-                                }">
-                                    <a class="control-label" href="#tasks-sites"
-                                        role="tab" data-toggle="tab" aria-controls="tasks-sites">
-
-                                        {% trans 'Sites' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.editors,
-                                }">
-                                    <a class="control-label" href="#tasks-editors"
-                                        role="tab" data-toggle="tab" aria-controls="tasks-editors">
-
-                                        {% trans 'Editors' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endif %}
-                            </ul>
-                            <div class="tab-content">
-                                <div role="tabpanel" class="tab-pane active" id="tasks-general">
-                                    <formgroup
-                                        data-id="task_available"
-                                        data-type="checkbox"
-                                        data-label="{{ meta.Task.available.verbose_name }}"
-                                        data-help="{{ meta.Task.available.help_text }}"
-                                        data-model="service.values.available"
-                                        data-errors="service.errors.available">
-                                    </formgroup>
-                                </div>
-                                {% for lang_code, lang in languages %}
-                                <div role="tabpanel" class="tab-pane" id="tasks-{{ lang_code }}">
-                                    <formgroup
-                                        data-id="task_title_{{ lang_code }}"
-                                        data-type="text"
-                                        data-label="{% trans 'Title' %} ({{ lang }})"
-                                        data-help="{% trans 'The title for this task.' %}"
-                                        data-model="service.values.title_{{ lang_code }}"
-                                        data-errors="service.errors.title_{{ lang_code }}"
-                                        data-warning="service.values.id && !service.values.title_{{ lang_code }}">
-                                    </formgroup>
-                                    <formgroup
-                                        data-id="task_text_{{ lang_code }}"
-                                        data-type="textarea"
-                                        data-label="{% trans 'Text' %} ({{ lang }})"
-                                        data-help="{% trans 'The text for this task.' %}"
-                                        data-model="service.values.text_{{ lang_code }}"
-                                        data-errors="service.errors.text_{{ lang_code }}"
-                                        data-warning="service.values.id && !service.values.text_{{ lang_code }}">
-                                    </formgroup>
-                                </div>
-                                {% endfor %}
-                                <div role="tabpanel" class="tab-pane" id="tasks-catalogs">
-                                    <formgroup
-                                        data-id="task_catalogs"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Catalogs' %}"
-                                        data-model="service.values.catalogs"
-                                        data-errors="service.errors.catalogs"
-                                        data-options="service.catalogs"
-                                        data-options-label="title"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %} {% trans 'An empty list implies that the task can be used with every catalog.' %}">
-                                    </formgroup>
-                                </div>
-                                <div role="tabpanel" class="tab-pane" id="tasks-groups">
-                                    <formgroup
-                                        data-id="task_groups"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Groups' %}"
-                                        data-model="service.values.groups"
-                                        data-errors="service.errors.groups"
-                                        data-options="service.groups"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                {% if settings.MULTISITE %}
-                                <div role="tabpanel" class="tab-pane" id="tasks-sites">
-                                    <formgroup
-                                        data-id="task_sites"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Sites' %}"
-                                        data-model="service.values.sites"
-                                        data-errors="service.errors.sites"
-                                        data-options="service.sites"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                <div role="tabpanel" class="tab-pane" id="tasks-editors">
-                                    <formgroup
-                                        data-id="task_editors"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Editors' %}"
-                                        data-model="service.values.editors"
-                                        data-errors="service.errors.editors"
-                                        data-options="service.sites"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                {% endif %}
-                            </div>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitFormModal('tasks')">
-                        {% trans 'Save' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/tasks/templates/tasks/tasks_modal_form_timeframes.html b/rdmo/tasks/templates/tasks/tasks_modal_form_timeframes.html
deleted file mode 100644
index 5602fec22c..0000000000
--- a/rdmo/tasks/templates/tasks/tasks_modal_form_timeframes.html
+++ /dev/null
@@ -1,75 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="timeframes-form-modal" tabindex="-1">
-        <div class="modal-dialog">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Task' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <formgroup
-                        data-id="start_attribute"
-                        data-type="selectnumber"
-                        data-label="{{ meta.Task.start_attribute.verbose_name }}"
-                        data-help="{{ meta.Task.start_attribute.help_text }}"
-                        data-model="service.values.start_attribute"
-                        data-errors="service.errors.start_attribute"
-                        data-options="service.attributes"
-                        data-options-label="uri"
-                        data-options-null="1"
-                        data-quicksearch="true">
-                    </formgroup>
-                    <formgroup
-                        data-id="end_attribute"
-                        data-type="selectnumber"
-                        data-label="{{ meta.Task.end_attribute.verbose_name }}"
-                        data-help="{{ meta.Task.end_attribute.help_text }}"
-                        data-model="service.values.end_attribute"
-                        data-errors="service.errors.end_attribute"
-                        data-options="service.attributes"
-                        data-options-label="uri"
-                        data-options-null="1"
-                        data-quicksearch="true">
-                    </formgroup>
-                    <div class="row">
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="days_before"
-                                data-type="number"
-                                data-label="{{ meta.Task.days_before.verbose_name }}"
-                                data-help="{{ meta.Task.days_before.help_text }}"
-                                data-model="service.values.days_before"
-                                data-errors="service.errors.days_before">
-                            </formgroup>
-                        </div>
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="days_after"
-                                data-type="number"
-                                data-label="{{ meta.Task.days_after.verbose_name }}"
-                                data-help="{{ meta.Task.days_after.help_text }}"
-                                data-model="service.values.days_after"
-                                data-errors="service.errors.days_after">
-                            </formgroup>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-click="service.submitFormModal('tasks')">
-                        {% trans 'Save' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/tasks/urls/__init__.py b/rdmo/tasks/urls/__init__.py
index a5329a0291..e69de29bb2 100644
--- a/rdmo/tasks/urls/__init__.py
+++ b/rdmo/tasks/urls/__init__.py
@@ -1,8 +0,0 @@
-from django.urls import re_path
-
-from ..views import TasksExportView, TasksView
-
-urlpatterns = [
-    re_path(r'^$', TasksView.as_view(), name='tasks'),
-    re_path(r'^export/(?P<format>[a-z]+)/$', TasksExportView.as_view(), name='tasks_export'),
-]
diff --git a/rdmo/tasks/validators.py b/rdmo/tasks/validators.py
index 3db35b9d99..526c9dfae6 100644
--- a/rdmo/tasks/validators.py
+++ b/rdmo/tasks/validators.py
@@ -1,5 +1,3 @@
-from django.utils.translation import gettext_lazy as _
-
 from rdmo.core.validators import LockedValidator, UniqueURIValidator
 
 from .models import Task
@@ -9,13 +7,6 @@ class TaskUniqueURIValidator(UniqueURIValidator):
 
     model = Task
 
-    def get_uri(self, data):
-        if not data.get('key'):
-            self.raise_validation_error({'key': _('This field is required.')})
-        else:
-            uri = self.model.build_uri(data.get('uri_prefix'), data.get('key'))
-            return uri
-
 
 class TaskLockedValidator(LockedValidator):
 
diff --git a/rdmo/tasks/views.py b/rdmo/tasks/views.py
deleted file mode 100644
index a14fea027f..0000000000
--- a/rdmo/tasks/views.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import logging
-
-from django.conf import settings
-from django.utils.translation import gettext_lazy as _
-from django.views.generic import ListView, TemplateView
-
-from rdmo.core.exports import XMLResponse
-from rdmo.core.utils import get_model_field_meta, render_to_format
-from rdmo.core.views import CSRFViewMixin, ModelPermissionMixin
-
-from .models import Task
-from .renderers import TaskRenderer
-from .serializers.export import TaskExportSerializer
-
-log = logging.getLogger(__name__)
-
-
-class TasksView(ModelPermissionMixin, CSRFViewMixin, TemplateView):
-    template_name = 'tasks/tasks.html'
-    permission_required = 'tasks.view_task'
-
-    def get_context_data(self, **kwargs):
-        context = super(TasksView, self).get_context_data(**kwargs)
-        context['export_formats'] = settings.EXPORT_FORMATS
-        context['meta'] = {
-            'Task': get_model_field_meta(Task)
-        }
-        return context
-
-
-class TasksExportView(ModelPermissionMixin, ListView):
-    model = Task
-    context_object_name = 'tasks'
-    permission_required = 'tasks.view_task'
-
-    def render_to_response(self, context, **response_kwargs):
-        format = self.kwargs.get('format')
-        if format == 'xml':
-            serializer = TaskExportSerializer(context['tasks'], many=True)
-            xml = TaskRenderer().render(serializer.data)
-            return XMLResponse(xml, name='tasks')
-        else:
-            return render_to_format(self.request, format, _('Tasks'), 'tasks/tasks_export.html', context)
diff --git a/rdmo/tasks/viewsets.py b/rdmo/tasks/viewsets.py
index 32ee88e7e9..2d99061913 100644
--- a/rdmo/tasks/viewsets.py
+++ b/rdmo/tasks/viewsets.py
@@ -8,36 +8,32 @@
 from rdmo.core.exports import XMLResponse
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
 from rdmo.core.utils import is_truthy, render_to_format
-from rdmo.core.viewsets import CopyModelMixin
 
 from .models import Task
 from .renderers import TaskRenderer
 from .serializers.export import TaskExportSerializer
-from .serializers.v1 import (TaskIndexSerializer, TaskListSerializer,
-                             TaskSerializer)
+from .serializers.v1 import TaskIndexSerializer, TaskSerializer
 
 
-class TaskViewSet(CopyModelMixin, ModelViewSet):
+class TaskViewSet(ModelViewSet):
     permission_classes = (HasModelPermission | HasObjectPermission, )
+    serializer_class = TaskSerializer
     queryset = Task.objects.select_related('start_attribute', 'end_attribute') \
                            .prefetch_related('catalogs', 'sites', 'editors', 'groups', 'conditions') \
-                           .annotate(projects_count=models.Count('projects'))
-    serializer_class = TaskSerializer
+                           .annotate(projects_count=models.Count('projects')) \
+                           .order_by('uri')
 
     filter_backends = (SearchFilter, DjangoFilterBackend)
     search_fields = ('uri', )
     filterset_fields = (
         'uri',
         'uri_prefix',
-        'key',
+        'uri_path',
         'comment',
         'sites',
         'editors'
     )
 
-    def get_serializer_class(self):
-        return TaskListSerializer if self.action == 'list' else TaskSerializer
-
     @action(detail=False)
     def index(self, request):
         queryset = self.filter_queryset(self.get_queryset())
@@ -61,9 +57,9 @@ def detail_export(self, request, pk=None, export_format='xml'):
         if export_format == 'xml':
             serializer = TaskExportSerializer(self.get_object())
             xml = TaskRenderer().render([serializer.data], context=self.get_export_renderer_context(request))
-            return XMLResponse(xml, name=self.get_object().key)
+            return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().key, 'tasks/export/tasks.html', {
+            return render_to_format(self.request, export_format, self.get_object().uri_path, 'tasks/export/tasks.html', {
                 'tasks': [self.get_object()]
             })
 
diff --git a/rdmo/views/admin.py b/rdmo/views/admin.py
index c8fc3bd0e9..ba4c17711c 100644
--- a/rdmo/views/admin.py
+++ b/rdmo/views/admin.py
@@ -8,7 +8,7 @@
 
 
 class ViewAdminForm(forms.ModelForm):
-    key = forms.SlugField(required=True)
+    uri_path = forms.SlugField(required=True)
 
     class Meta:
         model = View
diff --git a/rdmo/views/imports.py b/rdmo/views/imports.py
index 3048e6ba93..e811625086 100644
--- a/rdmo/views/imports.py
+++ b/rdmo/views/imports.py
@@ -2,8 +2,9 @@
 
 from django.contrib.sites.models import Site
 
-from rdmo.core.imports import (set_common_fields, set_lang_field,
-                               set_m2m_instances, validate_instance)
+from rdmo.core.imports import (check_permissions, set_common_fields,
+                               set_lang_field, set_m2m_instances,
+                               validate_instance)
 
 from .models import View
 from .validators import ViewLockedValidator, ViewUniqueURIValidator
@@ -11,7 +12,7 @@
 logger = logging.getLogger(__name__)
 
 
-def import_view(element, save=False):
+def import_view(element, save=False, user=None):
     try:
         view = View.objects.get(uri=element.get('uri'))
     except View.DoesNotExist:
@@ -28,6 +29,8 @@ def import_view(element, save=False):
 
     validate_instance(view, element, ViewLockedValidator, ViewUniqueURIValidator)
 
+    check_permissions(view, element, user)
+
     if save and not element.get('errors'):
         if view.id:
             element['updated'] = True
@@ -39,6 +42,6 @@ def import_view(element, save=False):
         view.save()
         set_m2m_instances(view, 'catalogs', element)
         view.sites.add(Site.objects.get_current())
-        view.editors.add(Site.objects.get_current()) 
+        view.editors.add(Site.objects.get_current())
 
     return view
diff --git a/rdmo/views/migrations/0028_uri_path.py b/rdmo/views/migrations/0028_uri_path.py
new file mode 100644
index 0000000000..417ff82678
--- /dev/null
+++ b/rdmo/views/migrations/0028_uri_path.py
@@ -0,0 +1,32 @@
+# Generated by Django 3.2.19 on 2023-06-29 12:38
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('views', '0027_view_editors'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='view',
+            options={'ordering': ('uri',), 'verbose_name': 'View', 'verbose_name_plural': 'Views'},
+        ),
+        migrations.RenameField(
+            model_name='view',
+            old_name='key',
+            new_name='uri_path',
+        ),
+        migrations.AlterField(
+            model_name='view',
+            name='uri',
+            field=models.URLField(blank=True, help_text='The Uniform Resource Identifier of this view (auto-generated).', max_length=800, verbose_name='URI'),
+        ),
+        migrations.AlterField(
+            model_name='view',
+            name='uri_path',
+            field=models.SlugField(blank=True, help_text='The path for the URI of this view.', max_length=512, verbose_name='URI Path'),
+        ),
+    ]
diff --git a/rdmo/views/models.py b/rdmo/views/models.py
index d59bf9c979..9570a5c1ec 100644
--- a/rdmo/views/models.py
+++ b/rdmo/views/models.py
@@ -17,7 +17,7 @@ class View(models.Model, TranslationMixin):
     objects = ViewManager()
 
     uri = models.URLField(
-        max_length=640, blank=True,
+        max_length=800, blank=True,
         verbose_name=_('URI'),
         help_text=_('The Uniform Resource Identifier of this view (auto-generated).')
     )
@@ -26,10 +26,10 @@ class View(models.Model, TranslationMixin):
         verbose_name=_('URI Prefix'),
         help_text=_('The prefix for the URI of this view.')
     )
-    key = models.SlugField(
-        max_length=128, blank=True,
-        verbose_name=_('Key'),
-        help_text=_('The internal identifier of this view.')
+    uri_path = models.SlugField(
+        max_length=512, blank=True,
+        verbose_name=_('URI Path'),
+        help_text=_('The path for the URI of this view.')
     )
     comment = models.TextField(
         blank=True,
@@ -123,27 +123,17 @@ class View(models.Model, TranslationMixin):
     )
 
     class Meta:
-        ordering = ('key', )
+        ordering = ('uri', )
         verbose_name = _('View')
         verbose_name_plural = _('Views')
 
     def __str__(self):
-        return self.key
+        return self.uri
 
     def save(self, *args, **kwargs):
-        self.uri = self.build_uri(self.uri_prefix, self.key)
+        self.uri = self.build_uri(self.uri_prefix, self.uri_path)
         super().save(*args, **kwargs)
 
-    def copy(self, uri_prefix, key):
-        view = copy_model(self, uri_prefix=uri_prefix, key=key)
-
-        # copy m2m fields
-        view.catalogs.set(self.catalogs.all())
-        view.sites.set(self.sites.all())
-        view.groups.set(self.groups.all())
-
-        return view
-
     @property
     def title(self):
         return self.trans('title')
@@ -168,6 +158,7 @@ def render(self, project, snapshot=None, export_format=None):
         }))
 
     @classmethod
-    def build_uri(cls, uri_prefix, key):
-        assert key
-        return join_url(uri_prefix or settings.DEFAULT_URI_PREFIX, '/views/', key)
+    def build_uri(cls, uri_prefix, uri_path):
+        if not uri_path:
+            raise RuntimeError('uri_path is missing')
+        return join_url(uri_prefix or settings.DEFAULT_URI_PREFIX, '/views/', uri_path)
diff --git a/rdmo/views/renderers/mixins.py b/rdmo/views/renderers/mixins.py
index 1fe75991ae..31971975d1 100644
--- a/rdmo/views/renderers/mixins.py
+++ b/rdmo/views/renderers/mixins.py
@@ -9,7 +9,7 @@ def render_view(self, xml, view):
 
             xml.startElement('view', {'dc:uri': view['uri']})
             self.render_text_element(xml, 'uri_prefix', {}, view['uri_prefix'])
-            self.render_text_element(xml, 'key', {}, view['key'])
+            self.render_text_element(xml, 'uri_path', {}, view['uri_path'])
             self.render_text_element(xml, 'dc:comment', {}, view['comment'])
 
             for lang_code, lang_string, lang_field in get_languages():
diff --git a/rdmo/views/serializers/export.py b/rdmo/views/serializers/export.py
index 438a198458..3dc6b6ebf3 100644
--- a/rdmo/views/serializers/export.py
+++ b/rdmo/views/serializers/export.py
@@ -14,7 +14,7 @@ class Meta:
         fields = (
             'uri',
             'uri_prefix',
-            'key',
+            'uri_path',
             'comment',
             'catalogs',
             'template'
diff --git a/rdmo/views/serializers/v1.py b/rdmo/views/serializers/v1.py
index fd8844b91c..a7e27a3d1b 100644
--- a/rdmo/views/serializers/v1.py
+++ b/rdmo/views/serializers/v1.py
@@ -1,22 +1,38 @@
 from django.template import Context, Template, TemplateSyntaxError
 from rest_framework import exceptions, serializers
 
-from rdmo.core.serializers import (ElementExportSerializerMixin,
-                                   ElementModelSerializerMixin,
+from rdmo.core.serializers import (ElementModelSerializerMixin,
                                    ElementWarningSerializerMixin,
-                                   TranslationSerializerMixin,
-                                   ReadOnlyObjectPermissionsSerializerMixin)
+                                   ReadOnlyObjectPermissionSerializerMixin,
+                                   TranslationSerializerMixin)
 
 from ..models import View
 from ..validators import ViewLockedValidator, ViewUniqueURIValidator
 
 
-class BaseViewSerializer(TranslationSerializerMixin, ElementModelSerializerMixin,
-                         ReadOnlyObjectPermissionsSerializerMixin, serializers.ModelSerializer):
+class ViewSerializer(TranslationSerializerMixin, ElementModelSerializerMixin,
+                     ElementWarningSerializerMixin, ReadOnlyObjectPermissionSerializerMixin,
+                     serializers.ModelSerializer):
 
     model = serializers.SerializerMethodField()
+    uri_path = serializers.CharField(required=True)
+
+    warning = serializers.SerializerMethodField()
     read_only = serializers.SerializerMethodField()
 
+    projects_count = serializers.IntegerField(read_only=True)
+
+    def validate(self, data):
+        # try to render the template to see that the syntax is ok
+        try:
+            Template(data['template']).render(Context({}))
+        except (KeyError, IndexError):
+            pass
+        except (TemplateSyntaxError, TypeError) as e:
+            raise exceptions.ValidationError({'template': '\n'.join(e.args)})
+
+        return super().validate(data)
+
     class Meta:
         model = View
         fields = (
@@ -24,63 +40,29 @@ class Meta:
             'model',
             'uri',
             'uri_prefix',
-            'key',
+            'uri_path',
             'comment',
             'locked',
             'available',
             'catalogs',
             'sites',
             'editors',
-            'read_only',
             'groups',
             'template',
             'title',
-            'help'
+            'help',
+            'warning',
+            'read_only',
+            'projects_count'
         )
         trans_fields = (
             'title',
             'help'
         )
-
-
-class ViewSerializer(BaseViewSerializer):
-
-    key = serializers.SlugField(required=True)
-    projects_count = serializers.IntegerField(read_only=True)
-
-    def validate(self, data):
-        # try to render the template to see that the syntax is ok (if the editor was used)
-        if self.context['request'].data.get('editor'):
-            try:
-                Template(data['template']).render(Context({}))
-            except (KeyError, IndexError):
-                pass
-            except (TemplateSyntaxError, TypeError) as e:
-                raise exceptions.ValidationError({'template': '\n'.join(e.args)})
-
-        return super(ViewSerializer, self).validate(data)
-
-    class Meta(BaseViewSerializer.Meta):
-        fields = BaseViewSerializer.Meta.fields + (
-            'projects_count',
-        )
         validators = (
             ViewUniqueURIValidator(),
             ViewLockedValidator()
         )
-
-
-class ViewListSerializer(ElementExportSerializerMixin, ElementWarningSerializerMixin,
-                         BaseViewSerializer):
-
-    warning = serializers.SerializerMethodField()
-    xml_url = serializers.SerializerMethodField()
-
-    class Meta(BaseViewSerializer.Meta):
-        fields = BaseViewSerializer.Meta.fields + (
-            'warning',
-            'xml_url'
-        )
         warning_fields = (
             'title',
         )
diff --git a/rdmo/views/static/views/css/views.scss b/rdmo/views/static/views/css/views.scss
deleted file mode 100644
index 6d86ef2ee2..0000000000
--- a/rdmo/views/static/views/css/views.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.CodeMirror {
-    height: 600px;
-}
diff --git a/rdmo/views/static/views/js/views.js b/rdmo/views/static/views/js/views.js
deleted file mode 100644
index bb5997cb01..0000000000
--- a/rdmo/views/static/views/js/views.js
+++ /dev/null
@@ -1,142 +0,0 @@
-angular.module('views', ['core'])
-
-.factory('ViewsService', ['$resource', '$timeout', '$window', '$q', 'utils', '$sce', function($resource, $timeout, $window, $q, utils, $sce) {
-
-    /* get the base url */
-
-    var baseurl = angular.element('meta[name="baseurl"]').attr('content');
-
-    /* configure resources */
-
-    var resources = {
-        views: $resource(baseurl + 'api/v1/views/views/:list_action/:id/:detail_action/'),
-        catalogs: $resource(baseurl + 'api/v1/questions/catalogs/index/'),
-        settings: $resource(baseurl + 'api/v1/core/settings/'),
-        sites: $resource(baseurl + 'api/v1/core/sites/'),
-        groups: $resource(baseurl + 'api/v1/core/groups/'),
-    };
-
-    /* configure factories */
-
-    var factories = {
-        views: function(parent) {
-            return {
-                template: '',
-                uri_prefix: (angular.isDefined(parent) && parent) ? parent.uri_prefix : service.settings.default_uri_prefix,
-                sites: [1]
-            };
-        }
-    };
-
-    /* create the tasks service */
-
-    var service = {};
-
-    service.init = function(options) {
-        service.settings = resources.settings.get();
-        service.sites = resources.sites.query();
-        service.groups = resources.groups.query();
-        service.uri_prefixes = []
-        service.uri_prefix = ''
-        service.filter = sessionStorage.getItem('views_filter') || '';
-
-        service.initView().then(function () {
-            var current_scroll_pos = sessionStorage.getItem('views_scroll_pos');
-            if (current_scroll_pos) {
-                $timeout(function() {
-                    $window.scrollTo(0, current_scroll_pos);
-                });
-            }
-        });
-
-        $window.addEventListener('beforeunload', function() {
-            sessionStorage.setItem('views_scroll_pos', $window.scrollY);
-            sessionStorage.setItem('views_filter', service.filter);
-        });
-    };
-
-    service.initView = function(options) {
-        return resources.views.query({list_action: 'index'}, function(response) {
-            service.views = response;
-            service.uri_prefixes = response.reduce(function(list, item) {
-                if (list.indexOf(item.uri_prefix) < 0) {
-                    list.push(item.uri_prefix)
-                }
-                return list
-            }, [])
-
-            // mark help safe
-            service.views.map(function(view) {
-                view.help_html = $sce.trustAsHtml(view.help);
-            });
-        }).$promise;
-    };
-
-    service.openFormModal = function(resource, obj, create, copy) {
-        service.errors = {};
-        service.values = utils.fetchValues(resources['views'], factories['views'], obj, create, copy);
-        service.catalogs = resources.catalogs.query();
-
-        $q.all([
-            service.values.$promise,
-            service.catalogs.$promise
-        ]).then(function() {
-            if (service.values.template === ''){
-                service.values.template = '{% load view_tags %}'
-            };
-
-            $('#' + resource + '-form-modal').modal('show');
-
-            if (resource == 'templates') {
-                angular.element('.CodeMirror')[0].CodeMirror.refresh();
-                service.values.editor = true;
-            };
-        });
-    };
-
-    service.submitFormModal = function(resource, close) {
-        utils.storeValues(resources['views'], service.values).then(function() {
-            service.errors = {};
-
-            if (angular.isUndefined(close) || close) {
-                $('#' + resource + '-form-modal').modal('hide');
-                service.initView();
-            }
-        }, function(result) {
-            service.errors = result.data;
-        });
-    };
-
-    service.openDeleteModal = function(resource, obj) {
-        service.values = obj;
-        $('#' + resource + '-delete-modal').modal('show');
-    };
-
-    service.submitDeleteModal = function(resource) {
-        resources[resource].delete({id: service.values.id}, function() {
-            $('#' + resource + '-delete-modal').modal('hide');
-            service.initView();
-        });
-    };
-
-    service.hideView = function(item) {
-        if (service.filter && item.uri.indexOf(service.filter) < 0
-                           && item.title.indexOf(service.filter) < 0
-                           && item.help.indexOf(service.filter) < 0) {
-            return true;
-        }
-        if (service.uri_prefix && item.uri_prefix != service.uri_prefix) {
-            return true;
-        }
-    };
-
-    return service;
-
-}])
-
-.controller('ViewsController', ['$scope', 'ViewsService', function($scope, ViewsService) {
-
-    $scope.service = ViewsService;
-    $scope.service.init();
-
-}]);
diff --git a/rdmo/views/templates/views/views.html b/rdmo/views/templates/views/views.html
deleted file mode 100644
index 2657e4ca9e..0000000000
--- a/rdmo/views/templates/views/views.html
+++ /dev/null
@@ -1,188 +0,0 @@
-{% extends 'core/page.html' %}
-{% load static %}
-{% load compress %}
-{% load i18n %}
-{% load core_tags %}
-
-{% block head %}
-    {% vendor 'angular' %}
-    {% vendor 'codemirror' %}
-
-    {% compress css %}
-    <link rel="stylesheet" type="text/x-scss" href="{% static 'core/css/codemirror.scss' %}" />
-    <link rel="stylesheet" type="text/x-scss" href="{% static 'views/css/views.scss' %}" />
-    {% endcompress %}
-
-    {% compress js %}
-    <script type="text/javascript" src="{% static 'core/js/core.js' %}" ></script>
-    <script type="text/javascript" src="{% static 'views/js/views.js' %}" ></script>
-    {% endcompress %}
-{% endblock %}
-
-{% block bodyattr %} ng-app="views" ng-controller="ViewsController" {% endblock %}
-
-{% block sidebar %}
-
-    <h2>{% trans 'Filter views' %}</h2>
-
-    <div class="form-group">
-        <div class="input-group">
-            <input type="text" class="form-control" ng-model="service.filter"
-                   placeholder="{% trans 'Search' %}"></input>
-            <span class="input-group-btn" ng-click="service.filter = ''">
-                <button class="btn btn-default">
-                    <span class="fa fa-times"></span>
-                </button>
-            </span>
-        </div>
-    </div>
-
-    <div class="form-group">
-        <select class="form-control" ng-model="service.uri_prefix">
-            <option value="">{% trans 'All URI prefixes' %}</option>
-            <option ng-repeat="uri_prefix in service.uri_prefixes" value="{$ uri_prefix $}">
-                {$ uri_prefix $}
-            </option>
-        </select>
-    </div>
-
-    <h2>{% trans 'Options' %}</h2>
-
-    <ul class="list-unstyled">
-        <li>
-            <a href="" ng-click="service.openFormModal('views', null, true)">
-                {% trans 'Create new view' %}
-            </a>
-        </li>
-    </ul>
-
-    <h2>{% trans 'Export' %}</h2>
-
-    <ul class="list-unstyled">
-        {% for format, text in export_formats %}
-        <li >
-            <a href="{% url 'views_export' format %}" target="_blank">
-                {{ text }}
-            </a>
-        </li>
-        {% endfor %}
-    </ul>
-
-    <ul class="list-unstyled">
-        <li >
-            <a href="{% url 'views_export' 'xml' %}" target="_blank">
-                {% trans 'XML' %}
-            </a>
-        </li>
-    </ul>
-
-    <h2>{% trans 'Import' %}</h2>
-    <ul class="list-unstyled">
-        <li>
-            {% url 'upload' as upload_url %}
-            {% include 'core/upload_form.html' with upload_url=upload_url %}
-        </li>
-    </ul>
-
-{% endblock %}
-
-{% block page %}
-
-    <h1>{% trans 'Views' %}</h1>
-
-    <div ng-repeat="view in service.views | orderBy: 'title'" ng-cloak>
-
-        <div class="panel panel-default" ng-hide="service.hideView(view)">
-
-            <div class="panel-heading">
-                <div class="pull-right">
-                    <span class="fa fa-warning text-warning"
-                        title="{% trans 'Some languages are not configured' %}"
-                        ng-show="view.warning"></span>
-
-                    <i class="fa fa-lock text-danger" ng-show="view.locked"></i>
-                    <i class="fa fa-ban text-muted" ng-show="view.read_only"
-                        title="{% trans 'Read only' %}">
-                    </i>
-
-                    <a href="" class="fa fa-pencil"
-                        title="{% trans 'Update view' %}"
-                        ng-click="service.openFormModal('views', view)">
-                    </a>
-                    <a href="" class="fa fa-copy"
-                        title="{% trans 'Copy view' %}"
-                        ng-click="service.openFormModal('views', view, false, true)">
-                    </a>
-                    <a href="" class="fa fa-file-code-o"
-                        title="{% trans 'Edit template' %}"
-                        ng-click="service.openFormModal('templates', view)">
-                    </a>
-                    <a href="{$ view.xml_url $}" class="fa fa-download"
-                        title="{% trans 'Export view as XML' %}"
-                        target="blank">
-                    </a>
-                    <a href="" class="fa fa-trash"
-                        title="{% trans 'Delete view' %}"
-                        ng-click="service.openDeleteModal('views', view)">
-                    </a>
-                </div>
-                <div>
-                    <strong ng-class="{'text-muted': view.read_only}">
-                        {% trans 'View' %}
-                    </strong>
-                    <code class="code-views">{$ view.uri $}</code>
-                </div>
-            </div>
-
-            <ul class="list-group">
-                <li class="list-group-item">
-                    <p><strong>{$ view.title $}</strong></p>
-                    <div ng-bind-html="view.help_html"></div>
-                </li>
-            </ul>
-
-            {% if settings.MULTISITE %}
-            <div class="panel-footer">
-                <div class="row">
-                    <div class="col-sm-3">
-                        <strong>{% trans 'Sites' %}</strong>
-                    </div>
-                    <div class="col-sm-9">
-                        <ul>
-                            <li ng-show="view.sites.length > 0 && view.sites.length < service.sites.length"
-                                ng-repeat="site in view.sites">
-                                {$ site.name $}
-                            </li>
-                            <li ng-show="view.sites.length == 0 || view.sites.length == service.sites.length">
-                                {% trans 'All sites' %}
-                            </li>
-                        </ul>
-                    </div>
-                </div>
-                <div class="row">
-                    <div class="col-sm-3">
-                        <strong>{% trans 'Editors' %}</strong>
-                    </div>
-                    <div class="col-sm-9">
-                        <ul>
-                            <li ng-show="view.editors.length > 0 && view.editors.length < service.sites.length"
-                                ng-repeat="editor in view.editors">
-                                {$ editor.name $}
-                            </li>
-                            <li ng-show="view.editors.length == 0 || view.editors.length == service.sites.length">
-                                {% trans 'All sites' %}
-                            </li>
-                        </ul>
-                    </div>
-                </div>
-            </div>
-            {% endif %}
-        </div>
-    </div>
-
-    {% include 'views/views_modal_form_views.html' %}
-    {% include 'views/views_modal_form_templates.html' %}
-
-    {% include 'views/views_modal_delete_views.html' %}
-
-{% endblock %}
diff --git a/rdmo/views/templates/views/views_modal_delete_views.html b/rdmo/views/templates/views/views_modal_delete_views.html
deleted file mode 100644
index 23dc911781..0000000000
--- a/rdmo/views/templates/views/views_modal_delete_views.html
+++ /dev/null
@@ -1,38 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="views-delete-modal" tabindex="-1">
-        <div class="modal-dialog">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title" id="add-modal">
-                        {% trans 'Delete view' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <p>
-                        {% blocktrans trimmed with object='{$ service.values.uri $}' %}
-                        You are about to permanently delete the view <code>{{ object }}</code>.
-                        {% endblocktrans %}
-                    </p>
-
-                    <p class="text-danger">
-                        {% trans 'This action cannot be undone!' %}
-                    </p>
-                </div>
-
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-danger"
-                            ng-click="service.submitDeleteModal('views')">
-                        {% trans 'Delete' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/views/templates/views/views_modal_form_templates.html b/rdmo/views/templates/views/views_modal_form_templates.html
deleted file mode 100644
index 5d284c9e16..0000000000
--- a/rdmo/views/templates/views/views_modal_form_templates.html
+++ /dev/null
@@ -1,42 +0,0 @@
-{% load i18n %}
-
-    <div class="modal" id="templates-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        {% trans 'Template' %}
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <formgroup
-                        data-id="template"
-                        data-type="codemirror"
-                        data-label="{{ meta.View.template.verbose_name }}"
-                        data-help="{{ meta.View.template.help_text }}"
-                        data-model="service.values.template"
-                        data-errors="service.errors.template"
-                        data-mode="django">
-                    </formgroup>
-                </div>
-
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-default"
-                            ng-click="service.submitFormModal('templates', false)">
-                        {% trans 'Save' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-click="service.submitFormModal('templates')">
-                        {% trans 'Save and close' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/views/templates/views/views_modal_form_views.html b/rdmo/views/templates/views/views_modal_form_views.html
deleted file mode 100644
index a8224b8423..0000000000
--- a/rdmo/views/templates/views/views_modal_form_views.html
+++ /dev/null
@@ -1,261 +0,0 @@
-{% load i18n %}
-{% get_available_languages as languages %}
-
-    <div class="modal" id="views-form-modal" tabindex="-1">
-        <div class="modal-dialog modal-lg">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" tabindex="-1">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h2 class="modal-title">
-                        <span ng-hide="service.values.id">{% trans 'Create view' %}</span>
-                        <span ng-show="service.values.copy">{% trans 'Copy view' %}</span>
-                        <span ng-show="service.values.id && !service.values.copy">{% trans 'Update view' %}</span>
-                    </h2>
-                </div>
-
-                <div class="modal-body">
-                    <div class="copy-block" ng-show="service.values.copy">
-                        <p>
-                            {% trans 'Please enter the values for the copy of the view below and change at least the <strong>key</strong>.' %}
-                        </p>
-                        <p>
-                            {% trans 'Other properties can be changed once the new view is created.' %}
-                        </p>
-                    </div>
-                    <div class="row">
-                        <div class="col-sm-6">
-                            <div class="form-group" ng-class="{'has-error': service.errors.uri_prefix }">
-                                <label class="control-label" for="uri_prefix">
-                                    {{ meta.View.uri_prefix.verbose_name }}
-                                </label>
-
-                                <div class="input-group">
-                                    <input class="form-control"
-                                           id="view_uri_prefix"
-                                           type="text"
-                                           ng-model="service.values.uri_prefix" />
-
-                                    <span class="input-group-btn">
-                                        <button type="button" class="btn btn-default"
-                                            title="{% trans 'Insert default URI Prefix' %}"
-                                            ng-click="service.values.uri_prefix = service.settings.default_uri_prefix">
-
-                                            <span class="fa fa-magic"></span>
-                                        </button>
-                                    </span>
-                                </div>
-
-                                <p class="help-block">
-                                    {{ meta.View.uri_prefix.help_text }}
-                                </p>
-
-                                <ul class="help-block list-unstyled" ng-show="service.errors.uri_prefix">
-                                    <li ng-repeat="error in service.errors.uri_prefix">
-                                        {$ error $}
-                                    </li>
-                                </ul>
-                            </div>
-                        </div>
-                        <div class="col-sm-6">
-                            <formgroup
-                                data-id="view_key"
-                                data-type="text"
-                                data-label="{{ meta.View.key.verbose_name }}"
-                                data-help="{{ meta.View.key.help_text }}"
-                                data-model="service.values.key"
-                                data-errors="service.errors.key">
-                            </formgroup>
-                        </div>
-                    </div>
-
-                    <div ng-hide="service.values.copy">
-                        <formgroup
-                            data-id="view_comment"
-                            data-type="textarea"
-                            data-label="{{ meta.View.comment.verbose_name }}"
-                            data-help="{{ meta.View.comment.help_text }}"
-                            data-model="service.values.comment"
-                            data-errors="service.errors.comment">
-                        </formgroup>
-                        <formgroup
-                            data-id="view_locked"
-                            data-type="checkbox"
-                            data-label="{{ meta.View.locked.verbose_name }}"
-                            data-help="{{ meta.View.locked.help_text }}"
-                            data-model="service.values.locked"
-                            data-errors="service.errors.locked">
-                        </formgroup>
-                        <div>
-                            <ul class="nav nav-tabs" role="tablist">
-                                <li role="presentation" class="active">
-                                    <a class="control-label" href="#views-general"
-                                        role="tab" data-toggle="tab" aria-controls="views-general">
-
-                                        {% trans 'General' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% for lang_code, lang in languages %}
-                                <li role="presentation" ng-class="{
-                                    'has-error':
-                                        service.errors.title_{{ lang_code }} ||
-                                        service.errors.help_{{ lang_code }},
-                                    'has-warning':
-                                        service.values.id && !service.values.title_{{ lang_code }}
-                                }">
-                                    <a role="tab" class="control-label" data-toggle="tab"
-                                        href="#views-{{ lang_code }}" aria-controls="views-{{ lang_code }}">
-
-                                        {{ lang }}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endfor %}
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.catalogs,
-                                }">
-                                    <a class="control-label" href="#views-catalogs"
-                                        role="tab" data-toggle="tab" aria-controls="views-catalogs">
-
-                                        {% trans 'Catalogs' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.groups,
-                                }">
-                                    <a class="control-label" href="#views-groups"
-                                        role="tab" data-toggle="tab" aria-controls="views-groups">
-
-                                        {% trans 'Groups' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% if settings.MULTISITE %}
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.sites,
-                                }">
-                                    <a class="control-label" href="#views-sites"
-                                        role="tab" data-toggle="tab" aria-controls="views-sites">
-
-                                        {% trans 'Sites' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                <li role="presentation" ng-class="{
-                                    'has-error': service.errors.editors,
-                                }">
-                                    <a class="control-label" href="#views-editors"
-                                        role="tab" data-toggle="tab" aria-controls="views-editors">
-
-                                        {% trans 'Editors' %}
-                                        <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
-                                    </a>
-                                </li>
-                                {% endif %}
-                            </ul>
-                            <div class="tab-content">
-                                <div role="tabpanel" class="tab-pane active" id="views-general">
-                                    <formgroup
-                                        data-id="view_available"
-                                        data-type="checkbox"
-                                        data-label="{{ meta.View.available.verbose_name }}"
-                                        data-help="{{ meta.View.available.help_text }}"
-                                        data-model="service.values.available"
-                                        data-errors="service.errors.available">
-                                    </formgroup>
-                                </div>
-                                {% for lang_code, lang in languages %}
-                                <div role="tabpanel" class="tab-pane" id="views-{{ lang_code }}">
-                                    <formgroup
-                                        data-id="view_title_{{ lang_code }}"
-                                        data-type="text"
-                                        data-label="{% trans 'Title' %} ({{ lang }})"
-                                        data-help="{% trans 'The title for this view.' %}"
-                                        data-model="service.values.title_{{ lang_code }}"
-                                        data-errors="service.errors.title_{{ lang_code }}"
-                                        data-warning="service.values.id && !service.values.title_{{ lang_code }}">
-                                    </formgroup>
-                                    <formgroup
-                                        data-id="view_help_{{ lang_code }}"
-                                        data-type="textarea"
-                                        data-label="{% trans 'Help' %} ({{ lang }})"
-                                        data-help="{% trans 'The help text for this view.' %}"
-                                        data-model="service.values.help_{{ lang_code }}"
-                                        data-errors="service.errors.help_{{ lang_code }}">
-                                    </formgroup>
-                                </div>
-                                {% endfor %}
-                                <div role="tabpanel" class="tab-pane" id="views-catalogs">
-                                    <formgroup
-                                        data-id="view_catalogs"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Catalogs' %}"
-                                        data-model="service.values.catalogs"
-                                        data-errors="service.errors.catalogs"
-                                        data-options="service.catalogs"
-                                        data-options-label="title"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %} {% trans 'An empty list implies that the view can be used with every catalog.' %}">
-                                    </formgroup>
-                                </div>
-                                <div role="tabpanel" class="tab-pane" id="views-groups">
-                                    <formgroup
-                                        data-id="view_groups"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Groups' %}"
-                                        data-model="service.values.groups"
-                                        data-errors="service.errors.groups"
-                                        data-options="service.groups"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                {% if settings.MULTISITE %}
-                                <div role="tabpanel" class="tab-pane" id="views-sites">
-                                    <formgroup
-                                        data-id="view_sites"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Sites' %}"
-                                        data-model="service.values.sites"
-                                        data-errors="service.errors.sites"
-                                        data-options="service.sites"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                <div role="tabpanel" class="tab-pane" id="views-editors">
-                                    <formgroup
-                                        data-id="view_editors"
-                                        data-type="selectmultiple"
-                                        data-label="{% trans 'Editors' %}"
-                                        data-model="service.values.editors"s
-                                        data-errors="service.errors.editors"
-                                        data-options="service.sites"
-                                        data-options-label="name"
-                                        data-help="{% trans 'Hold down Control, or Command on a Mac, to select more than one.' %}">
-                                    </formgroup>
-                                </div>
-                                {% endif %}
-                            </div>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="modal-footer">
-                    <div class="alert alert-warning pull-left" ng-show="service.errors.detail" role="alert">
-                        {$ service.errors.detail $}
-                    </div>
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        {% trans 'Close' %}
-                    </button>
-                    <button type="button" class="btn btn-primary"
-                            ng-class="{'disabled': service.values.read_only}"
-                            ng-click="service.submitFormModal('views')">
-                        {% trans 'Save' %}
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
diff --git a/rdmo/views/urls/__init__.py b/rdmo/views/urls/__init__.py
index 81b72106e4..e69de29bb2 100644
--- a/rdmo/views/urls/__init__.py
+++ b/rdmo/views/urls/__init__.py
@@ -1,8 +0,0 @@
-from django.urls import re_path
-
-from ..views import ViewsExportView, ViewsView
-
-urlpatterns = [
-    re_path(r'^$', ViewsView.as_view(), name='views'),
-    re_path(r'^export/(?P<format>[a-z]+)/$', ViewsExportView.as_view(), name='views_export'),
-]
diff --git a/rdmo/views/utils.py b/rdmo/views/utils.py
index 6121c9fd42..cda712d1fb 100644
--- a/rdmo/views/utils.py
+++ b/rdmo/views/utils.py
@@ -67,7 +67,7 @@ def tree(self):
     def conditions(self):
         conditions = {}
         for condition in self._conditions:
-            conditions[condition.uri] = conditions[condition.key] = self._resolve_condition(condition)
+            conditions[condition.uri] = conditions[condition.uri_path] = self._resolve_condition(condition)
         return conditions
 
     @cached_property
@@ -128,7 +128,7 @@ def _check_condition(self, condition, set_prefix=None, set_index=None):
         if urlparse(condition).scheme:
             conditions = filter(lambda c: c.uri == condition, conditions)
         else:
-            conditions = filter(lambda c: c.key == condition, conditions)
+            conditions = filter(lambda c: c.uri_path == condition, conditions)
 
         return self._resolve_conditions(conditions, set_prefix=set_prefix, set_index=set_index)
 
diff --git a/rdmo/views/validators.py b/rdmo/views/validators.py
index 96ae4b4da3..2d0b46facc 100644
--- a/rdmo/views/validators.py
+++ b/rdmo/views/validators.py
@@ -1,5 +1,3 @@
-from django.utils.translation import gettext_lazy as _
-
 from rdmo.core.validators import LockedValidator, UniqueURIValidator
 
 from .models import View
@@ -9,13 +7,6 @@ class ViewUniqueURIValidator(UniqueURIValidator):
 
     model = View
 
-    def get_uri(self, data):
-        if not data.get('key'):
-            self.raise_validation_error({'key': _('This field is required.')})
-        else:
-            uri = self.model.build_uri(data.get('uri_prefix'), data.get('key'))
-            return uri
-
 
 class ViewLockedValidator(LockedValidator):
 
diff --git a/rdmo/views/views.py b/rdmo/views/views.py
deleted file mode 100644
index 1c3bc5a857..0000000000
--- a/rdmo/views/views.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import logging
-
-from django.conf import settings
-from django.utils.translation import gettext_lazy as _
-from django.views.generic import ListView, TemplateView
-
-from rdmo.core.exports import XMLResponse
-from rdmo.core.utils import get_model_field_meta, render_to_format
-from rdmo.core.views import CSRFViewMixin, ModelPermissionMixin
-
-from .models import View
-from .renderers import ViewRenderer
-from .serializers.export import ViewExportSerializer
-
-log = logging.getLogger(__name__)
-
-
-class ViewsView(ModelPermissionMixin, CSRFViewMixin, TemplateView):
-    template_name = 'views/views.html'
-    permission_required = 'views.view_view'
-
-    def get_context_data(self, **kwargs):
-        context = super(ViewsView, self).get_context_data(**kwargs)
-        context['export_formats'] = settings.EXPORT_FORMATS
-        context['meta'] = {
-            'View': get_model_field_meta(View)
-        }
-        return context
-
-
-class ViewsExportView(ModelPermissionMixin, ListView):
-    model = View
-    context_object_name = 'views'
-    permission_required = 'views.view_view'
-
-    def render_to_response(self, context, **response_kwargs):
-        format = self.kwargs.get('format')
-        if format == 'xml':
-            serializer = ViewExportSerializer(context['views'], many=True)
-            xml = ViewRenderer().render(serializer.data)
-            return XMLResponse(xml, name='views')
-        else:
-            return render_to_format(self.request, format, _('Views'), 'views/views_export.html', context)
diff --git a/rdmo/views/viewsets.py b/rdmo/views/viewsets.py
index a2ce41c8e0..0e6ba73288 100644
--- a/rdmo/views/viewsets.py
+++ b/rdmo/views/viewsets.py
@@ -1,41 +1,38 @@
 from django.db import models
 from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework.decorators import action
+from rest_framework.filters import SearchFilter
 from rest_framework.response import Response
 from rest_framework.viewsets import ModelViewSet
-from rest_framework.filters import SearchFilter
 
 from rdmo.core.exports import XMLResponse
 from rdmo.core.permissions import HasModelPermission, HasObjectPermission
 from rdmo.core.utils import render_to_format
-from rdmo.core.viewsets import CopyModelMixin
 
 from .models import View
 from .renderers import ViewRenderer
 from .serializers.export import ViewExportSerializer
-from .serializers.v1 import (ViewIndexSerializer, ViewListSerializer,
-                             ViewSerializer)
+from .serializers.v1 import ViewIndexSerializer, ViewSerializer
 
 
-class ViewViewSet(CopyModelMixin, ModelViewSet):
+class ViewViewSet(ModelViewSet):
     permission_classes = (HasModelPermission | HasObjectPermission, )
+    serializer_class = ViewSerializer
     queryset = View.objects.prefetch_related('catalogs', 'sites', 'editors', 'groups') \
-                           .annotate(projects_count=models.Count('projects'))
+                           .annotate(projects_count=models.Count('projects')) \
+                           .order_by('uri')
 
     filter_backends = (SearchFilter, DjangoFilterBackend)
     search_fields = ('uri', )
     filterset_fields = (
         'uri',
         'uri_prefix',
-        'key',
+        'uri_path',
         'comment',
         'sites',
         'editors'
     )
 
-    def get_serializer_class(self):
-        return ViewListSerializer if self.action == 'list' else ViewSerializer
-
     @action(detail=False)
     def index(self, request):
         queryset = self.filter_queryset(self.get_queryset())
@@ -59,8 +56,8 @@ def detail_export(self, request, pk=None, export_format='xml'):
         if export_format == 'xml':
             serializer = ViewExportSerializer(self.get_object())
             xml = ViewRenderer().render([serializer.data])
-            return XMLResponse(xml, name=self.get_object().key)
+            return XMLResponse(xml, name=self.get_object().uri_path)
         else:
-            return render_to_format(self.request, export_format, self.get_object().key, 'views/export/views.html', {
+            return render_to_format(self.request, export_format, self.get_object().uri_path, 'views/export/views.html', {
                 'views': [self.get_object()]
             })


Test patch:
diff --git a/conftest.py b/conftest.py
index f9be0d74b9..8b79407ee3 100644
--- a/conftest.py
+++ b/conftest.py
@@ -1,3 +1,4 @@
+import json
 import subprocess
 from pathlib import Path
 
@@ -42,3 +43,12 @@ def setup():
 
     setup()
     return setup
+
+
+@pytest.fixture
+def json_data():
+    json_file = Path(settings.BASE_DIR) / 'import' / 'catalogs.json'
+    json_data = {
+        'elements': json.loads(json_file.read_text())
+    }
+    return json_data
diff --git a/rdmo/conditions/tests/test_models.py b/rdmo/conditions/tests/test_models.py
index a102618f83..089319f4f0 100644
--- a/rdmo/conditions/tests/test_models.py
+++ b/rdmo/conditions/tests/test_models.py
@@ -11,15 +11,3 @@ def test_condition_clean(db):
     instances = Condition.objects.all()
     for instance in instances:
         instance.clean()
-
-
-def test_condition_copy(db):
-    instances = Condition.objects.all()
-    for instance in instances:
-        new_uri_prefix = instance.uri_prefix + '-'
-        new_key = instance.key + '-'
-        new_instance = instance.copy(new_uri_prefix, new_key)
-        assert new_instance.uri_prefix == new_uri_prefix
-        assert new_instance.key == new_key
-        assert new_instance.source == instance.source
-        assert new_instance.target_option == instance.target_option
diff --git a/rdmo/conditions/tests/test_validator_unique_uri.py b/rdmo/conditions/tests/test_validator_unique_uri.py
index d3ee53bfaf..3c77a74e50 100644
--- a/rdmo/conditions/tests/test_validator_unique_uri.py
+++ b/rdmo/conditions/tests/test_validator_unique_uri.py
@@ -12,7 +12,7 @@
 def test_unique_uri_validator_create(db):
     ConditionUniqueURIValidator()({
         'uri_prefix': settings.DEFAULT_URI_PREFIX,
-        'key': 'test'
+        'uri_path': 'test'
     })
 
 
@@ -20,7 +20,7 @@ def test_unique_uri_validator_create_error(db):
     with pytest.raises(ValidationError):
         ConditionUniqueURIValidator()({
             'uri_prefix': settings.DEFAULT_URI_PREFIX,
-            'uri_path': Condition.objects.last().key
+            'uri_path': Condition.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
 
 
@@ -29,17 +29,17 @@ def test_unique_uri_validator_update(db):
 
     ConditionUniqueURIValidator(condition)({
         'uri_prefix': condition.uri_prefix,
-        'key': condition.key
+        'uri_path': condition.uri_path
     })
 
 
 def test_unique_uri_validator_update_error(db):
-    condition = Condition.objects.first()
+    condition = Condition.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).first()
 
     with pytest.raises(ValidationError):
         ConditionUniqueURIValidator(condition)({
             'uri_prefix': condition.uri_prefix,
-            'uri_path': Condition.objects.last().key
+            'uri_path': Condition.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
 
 
@@ -49,7 +49,7 @@ def test_unique_uri_validator_serializer_create(db):
 
     validator({
         'uri_prefix': settings.DEFAULT_URI_PREFIX,
-        'key': 'test'
+        'uri_path': 'test'
     })
 
 
@@ -60,7 +60,7 @@ def test_unique_uri_validator_serializer_create_error(db):
     with pytest.raises(RestFameworkValidationError):
         validator({
             'uri_prefix': settings.DEFAULT_URI_PREFIX,
-            'uri_path': Condition.objects.last().key
+            'uri_path': Condition.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
 
 
@@ -72,12 +72,12 @@ def test_unique_uri_validator_serializer_update(db):
 
     validator({
         'uri_prefix': condition.uri_prefix,
-        'key': condition.key
+        'uri_path': condition.uri_path
     })
 
 
 def test_unique_uri_validator_serializer_update_error(db):
-    condition = Condition.objects.first()
+    condition = Condition.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).first()
 
     validator = ConditionUniqueURIValidator()
     validator.set_context(ConditionSerializer(instance=condition))
@@ -85,5 +85,5 @@ def test_unique_uri_validator_serializer_update_error(db):
     with pytest.raises(RestFameworkValidationError):
         validator({
             'uri_prefix': condition.uri_prefix,
-            'uri_path': Condition.objects.last().key
+            'uri_path': Condition.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
diff --git a/rdmo/conditions/tests/test_viewset_condition.py b/rdmo/conditions/tests/test_viewset_condition.py
index 478e699439..94740e8a03 100644
--- a/rdmo/conditions/tests/test_viewset_condition.py
+++ b/rdmo/conditions/tests/test_viewset_condition.py
@@ -28,9 +28,6 @@
     },
     'delete': {
         'editor': 204, 'reviewer': 403, 'api': 204, 'user': 404, 'anonymous': 401
-    },
-    'copy': {
-        'editor': 201, 'reviewer': 403, 'api': 201, 'user': 404, 'anonymous': 401
     }
 }
 
@@ -100,7 +97,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': '%s_new_%s' % (instance.key, username),
+            'uri_path': '%s_new_%s' % (instance.uri_path, username),
             'comment': instance.comment,
             'source': instance.source.pk,
             'relation': instance.relation,
@@ -122,7 +119,7 @@ def test_create_optionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'uri_path': '%s_new_%s' % (instance.uri_path, username),
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -149,7 +146,7 @@ def test_create_page(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'uri_path': '%s_new_%s' % (instance.uri_path, username),
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -176,7 +173,7 @@ def test_create_questionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'uri_path': '%s_new_%s' % (instance.uri_path, username),
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -203,7 +200,7 @@ def test_create_question(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'uri_path': '%s_new_%s' % (instance.uri_path, username),
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -230,7 +227,7 @@ def test_create_task(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'uri_path': '%s_new_%s' % (instance.uri_path, username),
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -255,7 +252,7 @@ def test_update(db, client, username, password):
         url = reverse(urlnames['detail'], args=[instance.pk])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': instance.key,
+            'uri_path': instance.uri_path,
             'comment': instance.comment,
             'source': instance.source.pk,
             'relation': instance.relation,
@@ -292,36 +289,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['condition']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Condition.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'key': instance.key + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == status_map['copy'][username], response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Condition.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'key': instance.key
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/conditions/tests/test_viewset_condition_multisite.py b/rdmo/conditions/tests/test_viewset_condition_multisite.py
index 08d064c049..cbb8f59806 100644
--- a/rdmo/conditions/tests/test_viewset_condition_multisite.py
+++ b/rdmo/conditions/tests/test_viewset_condition_multisite.py
@@ -68,7 +68,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': '%s_new_%s' % (instance.key, username),
+            'uri_path': '%s_new_%s' % (instance.uri_path, username),
             'comment': instance.comment,
             'source': instance.source.pk,
             'relation': instance.relation,
@@ -90,7 +90,7 @@ def test_create_optionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'uri_path': '%s_new_%s' % (instance.uri_path, username),
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -117,7 +117,7 @@ def test_create_page(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'uri_path': '%s_new_%s' % (instance.uri_path, username),
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -144,7 +144,7 @@ def test_create_questionset(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'uri_path': '%s_new_%s' % (instance.uri_path, username),
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -171,7 +171,7 @@ def test_create_question(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'uri_path': '%s_new_%s' % (instance.uri_path, username),
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -198,7 +198,7 @@ def test_create_task(db, client, username, password):
             url = reverse(urlnames['list'])
             data = {
                 'uri_prefix': instance.uri_prefix,
-                'key': '%s_new_%s' % (instance.key, username),
+                'uri_path': '%s_new_%s' % (instance.uri_path, username),
                 'comment': instance.comment,
                 'source': instance.source.pk,
                 'relation': instance.relation,
@@ -223,7 +223,7 @@ def test_update(db, client, username, password):
         url = reverse(urlnames['detail'], args=[instance.pk])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': instance.key,
+            'uri_path': instance.uri_path,
             'comment': instance.comment,
             'source': instance.source.pk,
             'relation': instance.relation,
@@ -260,36 +260,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['condition']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Condition.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'key': instance.key + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Condition.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'key': instance.key
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
diff --git a/rdmo/core/tests/__init__.py b/rdmo/core/tests/__init__.py
index 44ffb7538a..a71a08afcb 100644
--- a/rdmo/core/tests/__init__.py
+++ b/rdmo/core/tests/__init__.py
@@ -1,4 +1,5 @@
 
+from rdmo.core.models import Model
 
 multisite_users = (
     ('user', 'user'),
@@ -64,6 +65,11 @@
 
 status_map_object_permissions = {
     'copy': {
+        'all-element': {
+            'foo-reviewer': 403, 'foo-editor': 201,
+            'bar-reviewer': 403, 'bar-editor': 201,
+            'example-reviewer': 403, 'example-editor': 201,
+        },
         'foo-element': {
             'foo-reviewer': 403, 'foo-editor': 201,
             'bar-reviewer': 404, 'bar-editor': 404,
@@ -76,6 +82,11 @@
         }
     },
     'update': {
+        'all-element': {
+            'foo-reviewer': 403, 'foo-editor': 200,
+            'bar-reviewer': 403, 'bar-editor': 200,
+            'example-reviewer': 403, 'example-editor': 200,
+        },
         'foo-element': {
             'foo-reviewer': 403, 'foo-editor': 200,
             'bar-reviewer': 404, 'bar-editor': 404,
@@ -88,6 +99,11 @@
         }
     },
     'delete': {
+        'all-element': {
+            'foo-reviewer': 403, 'foo-editor': 204,
+            'bar-reviewer': 403, 'bar-editor': 204,
+            'example-reviewer': 403, 'example-editor': 204,
+        },
         'foo-element': {
             'foo-reviewer': 403, 'foo-editor': 204,
             'bar-reviewer': 404, 'bar-editor': 404,
@@ -104,15 +120,22 @@
 
 def get_obj_perms_status_code(instance, username, method):
     ''' looks for the object permissions of the instance and returns the status code '''
-    if not instance.editors.exists():
-        return multisite_status_map[method][username]
-    
+
+    if isinstance(instance, Model):
+        try:
+            if not instance.editors.exists():
+                return multisite_status_map[method][username]
+        except AttributeError as exc:
+            raise AttributeError(f'instance {instance} should have an editors attribute') from exc
+    elif isinstance(instance, str):
+        pass
+
     if 'foo-' in str(instance):
         instance_obj_perms_key = 'foo-element'
     elif 'bar-' in str(instance):
         instance_obj_perms_key = 'bar-element'
     else:
-        raise ValueError('instance should have either foo or bar in name for obj perms')
+        instance_obj_perms_key = 'all-element'
 
     try:
         method_instance_obj_perms_map = status_map_object_permissions[method][instance_obj_perms_key]
diff --git a/rdmo/core/tests/test_views.py b/rdmo/core/tests/test_views.py
index 6accb79029..f15157ef87 100644
--- a/rdmo/core/tests/test_views.py
+++ b/rdmo/core/tests/test_views.py
@@ -50,3 +50,13 @@ def test_i18n_switcher(db, client):
     response = client.get(url)
     assert response.status_code == 302
     assert 'en' in response['Content-Language']
+
+
+@pytest.mark.parametrize("username,password", users)
+def test_can_view_management(db, client, username, password):
+    client.login(username=username, password=password)
+    response = client.get(reverse('management'))
+    if username in ('editor', 'reviewer', 'api'):
+        assert response.status_code == 200
+    else:
+        assert response.status_code == 403
diff --git a/rdmo/domain/tests/test_models.py b/rdmo/domain/tests/test_models.py
index 3b18005f8d..bae044d4cf 100644
--- a/rdmo/domain/tests/test_models.py
+++ b/rdmo/domain/tests/test_models.py
@@ -11,15 +11,3 @@ def test_attribute_clean(db):
     instances = Attribute.objects.all()
     for instance in instances:
         instance.clean()
-
-
-def test_attribute_copy(db):
-    instances = Attribute.objects.all()
-    for instance in instances:
-        new_uri_prefix = instance.uri_prefix + '-'
-        new_key = instance.key + '-'
-        new_instance = instance.copy(new_uri_prefix, new_key)
-        assert new_instance.uri_prefix == new_uri_prefix
-        assert new_instance.key == new_key
-        assert new_instance.parent == instance.parent
-        assert new_instance.get_descendants().count() == new_instance.get_descendants().count()
diff --git a/rdmo/domain/tests/test_validator_parent.py b/rdmo/domain/tests/test_validator_parent.py
index fa7dd712fb..3592cba5a4 100644
--- a/rdmo/domain/tests/test_validator_parent.py
+++ b/rdmo/domain/tests/test_validator_parent.py
@@ -76,42 +76,28 @@ def test_serializer_update_error(db):
         })
 
 
-def test_serializer_copy(db):
-    attribute = Attribute.objects.get(uri='http://example.com/terms/domain/individual/single/text')
-
-    class MockedView(object):
-        action = 'copy'
-
-        def get_object(self):
-            return attribute
+def test_import_create(db):
+    AttributeParentValidator()({
+        'uri_prefix': settings.DEFAULT_URI_PREFIX,
+        'key': 'test',
+        'parent_id': Attribute.objects.get(uri='http://example.com/terms/domain/individual/single').id
+    })
 
-    validator = AttributeParentValidator()
-    validator.set_context(AttributeSerializer())
-    validator.serializer.context['view'] = MockedView()
 
-    validator({
+def test_import_update(db):
+    attribute = Attribute.objects.get(uri='http://example.com/terms/domain/individual/single/text')
+    AttributeParentValidator(attribute)({
         'uri_prefix': attribute.uri_prefix,
         'key': attribute.key,
-        'parent':  attribute.parent
+        'parent_id': attribute.parent.id
     })
 
 
-def test_serializer_copy_error(db):
+def test_import_update_error(db):
     attribute = Attribute.objects.get(uri='http://example.com/terms/domain/individual/single/text')
-
-    class MockedView(object):
-        action = 'copy'
-
-        def get_object(self):
-            return attribute
-
-    validator = AttributeParentValidator()
-    validator.set_context(AttributeSerializer())
-    validator.serializer.context['view'] = MockedView()
-
-    with pytest.raises(RestFameworkValidationError):
-        validator({
+    with pytest.raises(ValidationError):
+        AttributeParentValidator(attribute)({
             'uri_prefix': attribute.uri_prefix,
             'key': attribute.key,
-            'parent':  attribute  # set self as parent
+            'parent_id': attribute.id  # set self as parent
         })
diff --git a/rdmo/domain/tests/test_viewset_attribute.py b/rdmo/domain/tests/test_viewset_attribute.py
index 33337eced8..e1607df934 100644
--- a/rdmo/domain/tests/test_viewset_attribute.py
+++ b/rdmo/domain/tests/test_viewset_attribute.py
@@ -23,9 +23,6 @@
     'create': {
         'editor': 201, 'reviewer': 403, 'api': 201, 'user': 403, 'anonymous': 401
     },
-    'copy': {
-        'editor': 201, 'reviewer': 403, 'api': 201, 'user': 404, 'anonymous': 401
-    },
     'update': {
         'editor': 200, 'reviewer': 403, 'api': 200, 'user': 404, 'anonymous': 401
     },
@@ -248,37 +245,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['attribute']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Attribute.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'key': instance.key + '-',
-            'parent': instance.parent.pk if instance.parent else None
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == status_map['copy'][username], response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Attribute.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'key': instance.key
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/domain/tests/test_viewset_attribute_multisite.py b/rdmo/domain/tests/test_viewset_attribute_multisite.py
index 3de2855e8b..87b3b61629 100644
--- a/rdmo/domain/tests/test_viewset_attribute_multisite.py
+++ b/rdmo/domain/tests/test_viewset_attribute_multisite.py
@@ -127,38 +127,3 @@ def test_detail_export(db, client, username, password):
             assert root.tag == 'rdmo'
             for child in root:
                 assert child.tag in ['attribute']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_multisite(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Attribute.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'key': instance.key + '-',
-            'parent': instance.parent.pk if instance.parent else None
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    ''' only users and status_map are unique for this test '''
-    client.login(username=username, password=password)
-    instance = Attribute.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'key': instance.key
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/management/tests/test_import_conditions.py b/rdmo/management/tests/test_import_conditions.py
index b99dce73f0..b19ad8e70c 100644
--- a/rdmo/management/tests/test_import_conditions.py
+++ b/rdmo/management/tests/test_import_conditions.py
@@ -20,6 +20,8 @@ def test_create_conditions(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Condition.objects.count() == 15
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_conditions(db, settings):
@@ -33,7 +35,9 @@ def test_update_conditions(db, settings):
     elements = elements.values()
     import_elements(elements)
 
-    assert len(root) == len(elements) == Condition.objects.count() == 15
+    assert len(root) == len(elements)
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
 
 def test_create_legacy_conditions(db, settings):
@@ -50,6 +54,8 @@ def test_create_legacy_conditions(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Condition.objects.count() == 15
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_legacy_conditions(db, settings):
@@ -63,4 +69,6 @@ def test_update_legacy_conditions(db, settings):
     elements = elements.values()
     import_elements(elements)
 
-    assert len(root) == len(elements) == Condition.objects.count() == 15
+    assert len(root) == len(elements) == 15
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
diff --git a/rdmo/management/tests/test_import_domain.py b/rdmo/management/tests/test_import_domain.py
index 2560ce0190..33ae0d53c7 100644
--- a/rdmo/management/tests/test_import_domain.py
+++ b/rdmo/management/tests/test_import_domain.py
@@ -20,6 +20,8 @@ def test_create_domain(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Attribute.objects.count() == 86
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_domain(db, settings):
@@ -33,7 +35,9 @@ def test_update_domain(db, settings):
     elements = elements.values()
     import_elements(elements)
 
-    assert len(root) == len(elements) == Attribute.objects.count() == 86
+    assert len(root) == len(elements)
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
 
 def test_create_legacy_domain(db, settings):
@@ -50,7 +54,9 @@ def test_create_legacy_domain(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 86
-    assert Attribute.objects.count() == 81
+    assert Attribute.objects.count() == 86
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_legacy_domain(db, settings):
@@ -65,4 +71,5 @@ def test_update_legacy_domain(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 86
-    assert Attribute.objects.count() == 86
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
diff --git a/rdmo/management/tests/test_import_options.py b/rdmo/management/tests/test_import_options.py
index 798fb91d42..45eb499f96 100644
--- a/rdmo/management/tests/test_import_options.py
+++ b/rdmo/management/tests/test_import_options.py
@@ -22,6 +22,8 @@ def test_create_optionsets(db, settings):
     assert len(root) == len(elements) == 12
     assert OptionSet.objects.count() == 4
     assert Option.objects.count() == 8
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_optionsets(db, settings):
@@ -35,8 +37,8 @@ def test_update_optionsets(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 12
-    assert OptionSet.objects.count() == 4
-    assert Option.objects.count() == 8
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
 
 def test_create_options(db, settings):
@@ -52,6 +54,8 @@ def test_create_options(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Option.objects.count() == 8
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_options(db, settings):
@@ -64,7 +68,9 @@ def test_update_options(db, settings):
     elements = elements.values()
     import_elements(elements)
 
-    assert len(root) == len(elements) == Option.objects.count() == 8
+    assert len(root) == len(elements) == 8
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
 
 def test_create_legacy_options(db, settings):
@@ -84,6 +90,8 @@ def test_create_legacy_options(db, settings):
     assert len(root) == len(elements) == 12
     assert OptionSet.objects.count() == 4
     assert Option.objects.count() == 8
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_legacy_options(db, settings):
@@ -98,5 +106,5 @@ def test_update_legacy_options(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 12
-    assert OptionSet.objects.count() == 4
-    assert Option.objects.count() == 8
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
diff --git a/rdmo/management/tests/test_import_questions.py b/rdmo/management/tests/test_import_questions.py
index 9906f9fa0f..c52db2924c 100644
--- a/rdmo/management/tests/test_import_questions.py
+++ b/rdmo/management/tests/test_import_questions.py
@@ -29,6 +29,8 @@ def test_create_catalogs(db, settings):
     assert Page.objects.count() == 48
     assert QuestionSet.objects.count() == 3
     assert Question.objects.count() == 89
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_catalogs(db, settings):
@@ -43,11 +45,8 @@ def test_update_catalogs(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 148
-    assert Catalog.objects.count() == 2
-    assert Section.objects.count() == 6
-    assert Page.objects.count() == 48
-    assert QuestionSet.objects.count() == 3
-    assert Question.objects.count() == 89
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
 
 def test_create_sections(db, settings):
@@ -71,6 +70,8 @@ def test_create_sections(db, settings):
     assert Page.objects.count() == 48
     assert QuestionSet.objects.count() == 3
     assert Question.objects.count() == 89
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_sections(db, settings):
@@ -85,10 +86,8 @@ def test_update_sections(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 146
-    assert Section.objects.count() == 6
-    assert Page.objects.count() == 48
-    assert QuestionSet.objects.count() == 3
-    assert Question.objects.count() == 89
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
 
 def test_create_pages(db, settings):
@@ -110,6 +109,8 @@ def test_create_pages(db, settings):
     assert Page.objects.count() == 48
     assert QuestionSet.objects.count() == 3
     assert Question.objects.count() == 89
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_pages(db, settings):
@@ -124,9 +125,8 @@ def test_update_pages(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 140
-    assert Page.objects.count() == 48
-    assert QuestionSet.objects.count() == 3
-    assert Question.objects.count() == 89
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
 
 def test_create_questionsets(db, settings):
@@ -148,6 +148,8 @@ def test_create_questionsets(db, settings):
     assert len(elements) == 8
     assert QuestionSet.objects.count() == 3
     assert Question.objects.count() == 5
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_questionsets(db, settings):
@@ -162,9 +164,8 @@ def test_update_questionsets(db, settings):
     import_elements(elements)
 
     assert len(root) == 10  # two questionsets apear twice in the export file
-    assert len(elements) == 8
-    assert QuestionSet.objects.count() == 3
-    assert Question.objects.count() == 89
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
 
 def test_create_questions(db, settings):
@@ -184,6 +185,8 @@ def test_create_questions(db, settings):
 
     assert len(root) == len(elements) == 89
     assert Question.objects.count() == 89
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_questions(db, settings):
@@ -198,7 +201,8 @@ def test_update_questions(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 89
-    assert Question.objects.count() == 89
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
 
 def test_create_legacy_questions(db, settings):
@@ -224,6 +228,8 @@ def test_create_legacy_questions(db, settings):
     assert Page.objects.count() == 48
     assert QuestionSet.objects.count() == 3
     assert Question.objects.count() == 89
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
     # check that all elements ended up in the catalog
     catalog = Catalog.objects.prefetch_elements().first()
@@ -244,11 +250,8 @@ def test_update_legacy_questions(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == 147
-    assert Catalog.objects.count() == 2
-    assert Section.objects.count() == 6
-    assert Page.objects.count() == 48
-    assert QuestionSet.objects.count() == 3
-    assert Question.objects.count() == 89
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
     # check that all elements ended up in the catalog
     catalog = Catalog.objects.prefetch_elements().first()
diff --git a/rdmo/management/tests/test_import_tasks.py b/rdmo/management/tests/test_import_tasks.py
index ae98bddcc8..6f80c35d78 100644
--- a/rdmo/management/tests/test_import_tasks.py
+++ b/rdmo/management/tests/test_import_tasks.py
@@ -20,6 +20,8 @@ def test_create_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Task.objects.count() == 2
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_tasks(db, settings):
@@ -33,7 +35,9 @@ def test_update_tasks(db, settings):
     elements = elements.values()
     import_elements(elements)
 
-    assert len(root) == len(elements) == Task.objects.count() == 2
+    assert len(root) == len(elements) == 2
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
 
 def test_create_legacy_tasks(db, settings):
@@ -50,6 +54,8 @@ def test_create_legacy_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == Task.objects.count() == 2
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_legacy_tasks(db, settings):
@@ -63,4 +69,6 @@ def test_update_legacy_tasks(db, settings):
     elements = elements.values()
     import_elements(elements)
 
-    assert len(root) == len(elements) == Task.objects.count() == 2
+    assert len(root) == len(elements) == 2
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
diff --git a/rdmo/management/tests/test_import_views.py b/rdmo/management/tests/test_import_views.py
index ec2d2619be..cec79e5dab 100644
--- a/rdmo/management/tests/test_import_views.py
+++ b/rdmo/management/tests/test_import_views.py
@@ -20,6 +20,8 @@ def test_create_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == View.objects.count() == 3
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_tasks(db, settings):
@@ -33,7 +35,9 @@ def test_update_tasks(db, settings):
     elements = elements.values()
     import_elements(elements)
 
-    assert len(root) == len(elements) == View.objects.count() == 3
+    assert len(root) == len(elements) == 3
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
 
 
 def test_create_legacy_tasks(db, settings):
@@ -50,6 +54,8 @@ def test_create_legacy_tasks(db, settings):
     import_elements(elements)
 
     assert len(root) == len(elements) == View.objects.count() == 3
+    assert all([element['created'] is True for element in elements])
+    assert all([element['updated'] is False for element in elements])
 
 
 def test_update_legacy_tasks(db, settings):
@@ -63,4 +69,6 @@ def test_update_legacy_tasks(db, settings):
     elements = elements.values()
     import_elements(elements)
 
-    assert len(root) == len(elements) == View.objects.count() == 3
+    assert len(root) == len(elements) == 3
+    assert all([element['created'] is False for element in elements])
+    assert all([element['updated'] is True for element in elements])
diff --git a/rdmo/management/tests/test_view.py b/rdmo/management/tests/test_view.py
new file mode 100644
index 0000000000..46df0ff0fb
--- /dev/null
+++ b/rdmo/management/tests/test_view.py
@@ -0,0 +1,26 @@
+import pytest
+from django.urls import reverse
+
+users = (
+    ('admin', 'admin'),
+    ('editor', 'editor'),
+    ('reviewer', 'reviewer'),
+    ('user', 'user'),
+    ('api', 'api'),
+    ('anonymous', None)
+)
+
+status_map = {
+    'management': {
+        'admin': 200, 'editor': 200, 'reviewer': 200, 'api': 200, 'user': 403, 'anonymous': 302
+    }
+}
+
+
+@pytest.mark.parametrize('username,password', users)
+def test_list(db, client, username, password):
+    client.login(username=username, password=password)
+
+    url = reverse('management')
+    response = client.get(url)
+    assert response.status_code == status_map['management'][username]
diff --git a/rdmo/management/tests/test_viewset_import.py b/rdmo/management/tests/test_viewset_import.py
index 1350327b03..23cb6742d5 100644
--- a/rdmo/management/tests/test_viewset_import.py
+++ b/rdmo/management/tests/test_viewset_import.py
@@ -1,9 +1,5 @@
-import json
-
-from pathlib import Path
-
 import pytest
-from django.conf import settings
+
 from django.urls import reverse
 
 from rdmo.questions.models import Catalog, Page, Question, QuestionSet, Section
@@ -21,7 +17,7 @@
         'editor': 405, 'reviewer': 405, 'api': 405, 'user': 405, 'anonymous': 401
     },
     'create': {
-        'editor': 200, 'reviewer': 403, 'api': 200, 'user': 403, 'anonymous': 401
+        'editor': 200, 'reviewer': 200, 'api': 200, 'user': 200, 'anonymous': 401
     },
     'create_error': {
         'editor': 400, 'reviewer': 400, 'api': 400, 'user': 400, 'anonymous': 401
@@ -43,7 +39,7 @@ def test_list(db, client, username, password):
 
 
 @pytest.mark.parametrize('username,password', users)
-def test_create_create(db, client, username, password):
+def test_create_create(db, client, username, password, json_data):
     Catalog.objects.all().delete()
     Section.objects.all().delete()
     Page.objects.all().delete()
@@ -52,30 +48,23 @@ def test_create_create(db, client, username, password):
 
     client.login(username=username, password=password)
 
-    json_file = Path(settings.BASE_DIR) / 'import' / 'catalogs.json'
-    json_data = {
-        'elements': json.loads(json_file.read_text())
-    }
-
     url = reverse(urlnames['list'])
     response = client.post(url, json_data, content_type='application/json')
 
     assert response.status_code == status_map['create'][username], response.json()
     if response.status_code == 200:
         for element in response.json():
-            assert element.get('created') is True
+            if username in ['reviewer', 'user']:
+                assert element.get('created') is False
+            else:
+                assert element.get('created') is True
             assert element.get('updated') is False
 
 
 @pytest.mark.parametrize('username,password', users)
-def test_create_update(db, client, username, password):
+def test_create_update(db, client, username, password, json_data):
     client.login(username=username, password=password)
 
-    json_file = Path(settings.BASE_DIR) / 'import' / 'catalogs.json'
-    json_data = {
-        'elements': json.loads(json_file.read_text())
-    }
-
     url = reverse(urlnames['list'])
     response = client.post(url, json_data, content_type='application/json')
 
@@ -83,7 +72,7 @@ def test_create_update(db, client, username, password):
     if response.status_code == 200:
         for element in response.json():
             assert element.get('created') is False
-            assert element.get('updated') is True
+            assert element.get('updated') is False if username in ['reviewer', 'user'] else True
 
 
 @pytest.mark.parametrize('username,password', users)
diff --git a/rdmo/management/tests/test_viewset_import_multisite.py b/rdmo/management/tests/test_viewset_import_multisite.py
new file mode 100644
index 0000000000..0be8805755
--- /dev/null
+++ b/rdmo/management/tests/test_viewset_import_multisite.py
@@ -0,0 +1,128 @@
+import pytest
+
+from django.urls import reverse
+
+from rdmo.core.tests import multisite_users as users
+from rdmo.core.tests import get_obj_perms_status_code
+
+from rdmo.questions.models import Catalog, Page, Question, QuestionSet, Section
+
+
+status_map = {
+    'list': {
+        'default': 405, 'anonymous': 401
+    },
+    'create': {
+        'default': 200, 'anonymous': 401
+    },
+    'create_error': {
+        'default': 400, 'anonymous': 401
+    }
+}
+
+catalog_uri_paths = [
+    'catalog',
+    'catalog2',
+    'foo-catalog',
+    'bar-catalog',
+]
+
+urlnames = {
+    'list': 'v1-management:import-list'
+}
+
+
+@pytest.mark.parametrize('username,password', users)
+def test_list(db, client, username, password):
+    client.login(username=username, password=password)
+
+    url = reverse(urlnames['list'])
+    response = client.get(url)
+    assert response.status_code == status_map['list'].get(username, status_map['list']['default']), response.json()
+
+
+@pytest.mark.parametrize('username,password', users)
+def test_create_create(db, client, username, password, json_data):
+    Catalog.objects.all().delete()
+    Section.objects.all().delete()
+    Page.objects.all().delete()
+    QuestionSet.objects.all().delete()
+    Question.objects.all().delete()
+
+    client.login(username=username, password=password)
+
+    url = reverse(urlnames['list'])
+    response = client.post(url, json_data, content_type='application/json')
+
+    assert response.status_code == status_map['create'].get(username, status_map['create']['default']), response.json()
+
+    if response.status_code == 200:
+        for element in response.json():
+            if any(i in username for i in ['reviewer', 'user']):
+                assert element.get('created') is False
+            else:
+                assert element.get('created') is True
+            assert element.get('updated') is False
+
+
+@pytest.mark.parametrize('username,password', users)
+def test_create_update(db, client, username, password, json_data):
+    client.login(username=username, password=password)
+
+    url = reverse(urlnames['list'])
+    response = client.post(url, json_data, content_type='application/json')
+
+    assert response.status_code == status_map['create'].get(username, status_map['create']['default']), response.json()
+
+    if response.status_code == 200:
+        for element in response.json():
+            assert element.get('created') is False
+            obj_perm_status_code = get_obj_perms_status_code(element.get('uri_path'), username, 'update')
+            if obj_perm_status_code == 200:
+                assert element.get('updated') is True
+            else:
+                assert element.get('updated') is False
+
+
+@pytest.mark.parametrize('username,password', users)
+@pytest.mark.parametrize('catalog_uri_path', catalog_uri_paths)
+def test_create_update_certain_catalog(db, client, username, password, catalog_uri_path, json_data):
+    client.login(username=username, password=password)
+
+    instance_json = [i for i in json_data['elements'] if i['uri_path'] == catalog_uri_path][0]
+    instance_json['title_en'] += ' (updated)'
+    instance_json['title_de'] += ' (updated)'
+    instance_data = {'elements': [instance_json]}
+
+    url = reverse(urlnames['list'])
+    response = client.post(url, instance_data, content_type='application/json')
+
+    assert response.status_code == status_map['create'].get(username, status_map['create']['default']), response.json()
+    if response.status_code == 200:
+        obj_perm_status_code = get_obj_perms_status_code(catalog_uri_path, username, 'update')
+        for element in response.json():
+            assert element.get('created') is False
+            if obj_perm_status_code == 200:
+                assert element.get('updated') is True
+            else:
+                assert element.get('updated') is False
+
+
+@pytest.mark.parametrize('username,password', users)
+def test_create_empty(db, client, username, password):
+    client.login(username=username, password=password)
+
+    url = reverse(urlnames['list'])
+    response = client.post(url, {}, content_type='application/json')
+    assert response.status_code == status_map['create_error'].get(username, status_map['create_error']['default']), response.json()
+
+
+@pytest.mark.parametrize('username,password', users)
+def test_create_error(db, client, username, password):
+    client.login(username=username, password=password)
+
+    json_data = {'foo': 'bar'}
+
+    url = reverse(urlnames['list'])
+    response = client.post(url, json_data, content_type='application/json')
+    assert response.status_code == status_map['create_error'].get(username, status_map['create_error']['default']), response.json()
diff --git a/rdmo/management/tests/test_viewset_upload.py b/rdmo/management/tests/test_viewset_upload.py
index 8e52e52624..3f8ccb44f3 100644
--- a/rdmo/management/tests/test_viewset_upload.py
+++ b/rdmo/management/tests/test_viewset_upload.py
@@ -19,7 +19,7 @@
         'editor': 405, 'reviewer': 405, 'api': 405, 'user': 405, 'anonymous': 401
     },
     'create': {
-        'editor': 200, 'reviewer': 403, 'api': 200, 'user': 403, 'anonymous': 401
+        'editor': 200, 'reviewer': 200, 'api': 200, 'user': 200, 'anonymous': 401
     },
     'create_error': {
         'editor': 400, 'reviewer': 400, 'api': 400, 'user': 400, 'anonymous': 401
@@ -75,7 +75,7 @@ def test_create_import_create(db, client, username, password):
     assert response.status_code == status_map['create'][username], response.json()
     if response.status_code == 200:
         for element in response.json():
-            assert element.get('created') is True
+            assert element.get('created') is False if username in ['reviewer', 'user'] else True
             assert element.get('updated') is False
 
 
@@ -93,7 +93,7 @@ def test_create_import_update(db, client, username, password):
     if response.status_code == 200:
         for element in response.json():
             assert element.get('created') is False
-            assert element.get('updated') is True
+            assert element.get('updated') is False if username in ['reviewer', 'user'] else True
 
 
 @pytest.mark.parametrize('username,password', users)
diff --git a/rdmo/options/tests/test_models.py b/rdmo/options/tests/test_models.py
index 4cbf4913c0..f5f1cb083b 100644
--- a/rdmo/options/tests/test_models.py
+++ b/rdmo/options/tests/test_models.py
@@ -19,30 +19,7 @@ def test_options_str(db):
         assert str(instance)
 
 
-def test_optionset_copy(db):
-    instances = OptionSet.objects.all()
-    for instance in instances:
-        new_uri_prefix = instance.uri_prefix + '-'
-        new_uri_path = instance.uri_path + '-'
-        new_instance = instance.copy(new_uri_prefix, new_uri_path)
-        assert new_instance.uri_prefix == new_uri_prefix
-        assert new_instance.uri_path == new_uri_path
-        assert list(new_instance.conditions.values('id')) == list(new_instance.conditions.values('id'))
-        assert list(new_instance.options.values('id')) == list(instance.options.values('id'))
-
-
 def test_options_clean(db):
     instances = Option.objects.all()
     for instance in instances:
         instance.clean()
-
-
-def test_options_copy(db):
-    instances = Option.objects.all()
-    for instance in instances:
-        new_uri_prefix = instance.uri_prefix + '-'
-        new_uri_path = instance.uri_path + '-'
-        new_instance = instance.copy(new_uri_prefix, new_uri_path)
-        assert new_instance.uri_prefix == new_uri_prefix
-        assert new_instance.uri_path == new_uri_path
-        assert list(new_instance.optionsets.values('id')) == list(instance.optionsets.values('id'))
diff --git a/rdmo/options/tests/test_viewset_options.py b/rdmo/options/tests/test_viewset_options.py
index b4223e5edb..833b68b084 100644
--- a/rdmo/options/tests/test_viewset_options.py
+++ b/rdmo/options/tests/test_viewset_options.py
@@ -24,9 +24,6 @@
     'create': {
         'editor': 201, 'reviewer': 403, 'api': 201, 'user': 403, 'anonymous': 401
     },
-    'copy': {
-        'editor': 201, 'reviewer': 403, 'api': 201, 'user': 404, 'anonymous': 401
-    },
     'update': {
         'editor': 200, 'reviewer': 403, 'api': 200, 'user': 404, 'anonymous': 401
     },
@@ -189,36 +186,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['option']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Option.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == status_map['copy'][username], response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Option.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/options/tests/test_viewset_options_multisite.py b/rdmo/options/tests/test_viewset_options_multisite.py
index be5558b794..bae8a3aba9 100644
--- a/rdmo/options/tests/test_viewset_options_multisite.py
+++ b/rdmo/options/tests/test_viewset_options_multisite.py
@@ -106,36 +106,3 @@ def test_delete_multisite(db, client, username, password):
         url = reverse(urlnames['detail'], args=[instance.pk])
         response = client.delete(url)
         assert response.status_code == get_obj_perms_status_code(instance, username, 'delete')
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_multisite(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Option.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Option.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/options/tests/test_viewset_optionsets.py b/rdmo/options/tests/test_viewset_optionsets.py
index 4c80a9b1ff..881d35d9d8 100644
--- a/rdmo/options/tests/test_viewset_optionsets.py
+++ b/rdmo/options/tests/test_viewset_optionsets.py
@@ -23,9 +23,6 @@
     'create': {
         'editor': 201, 'reviewer': 403, 'api': 201, 'user': 403, 'anonymous': 401
     },
-    'copy': {
-        'editor': 201, 'reviewer': 403, 'api': 201, 'user': 404, 'anonymous': 401
-    },
     'update': {
         'editor': 200, 'reviewer': 403, 'api': 200, 'user': 404, 'anonymous': 401
     },
@@ -296,36 +293,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['optionset', 'option']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = OptionSet.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == status_map['copy'][username], response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = OptionSet.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/options/tests/test_viewset_optionsets_multisite.py b/rdmo/options/tests/test_viewset_optionsets_multisite.py
index f07103f0ed..483b8e3005 100644
--- a/rdmo/options/tests/test_viewset_optionsets_multisite.py
+++ b/rdmo/options/tests/test_viewset_optionsets_multisite.py
@@ -146,36 +146,3 @@ def test_detail_export(db, client, username, password):
             assert root.tag == 'rdmo'
             for child in root:
                 assert child.tag in ['optionset', 'option']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_multisite(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = OptionSet.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = OptionSet.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/questions/tests/test_models.py b/rdmo/questions/tests/test_models.py
index c71838f011..a2fcb5f657 100644
--- a/rdmo/questions/tests/test_models.py
+++ b/rdmo/questions/tests/test_models.py
@@ -20,19 +20,6 @@ def test_catalog_clean(db):
         instance.clean()
 
 
-def test_catalog_copy(db):
-    instances = Catalog.objects.all()
-    for instance in instances:
-        new_uri_prefix = instance.uri_prefix + '-'
-        new_uri_path = instance.uri_path + '-'
-        new_instance = instance.copy(new_uri_prefix, new_uri_path)
-        assert new_instance.uri_prefix == new_uri_prefix
-        assert new_instance.uri_path == new_uri_path
-        assert list(new_instance.sites.values('id')) == list(instance.sites.values('id'))
-        assert list(new_instance.groups.values('id')) == list(instance.groups.values('id'))
-        assert list(new_instance.sections.values('id')) == list(instance.sections.values('id'))
-
-
 def test_catalog_descendants(db):
     instances = Catalog.objects.all()
     for instance in instances:
@@ -80,6 +67,7 @@ def test_section_str(db):
         assert str(instance)
         assert str(instance) == instance.uri
 
+
 def test_section_property_is_locked(db):
     instances = Section.objects.all()
     for instance in instances:
@@ -92,17 +80,6 @@ def test_section_clean(db):
         instance.clean()
 
 
-def test_section_copy(db):
-    instances = Section.objects.all()
-    for instance in instances:
-        new_uri_prefix = instance.uri_prefix + '-'
-        new_uri_path = instance.uri_path + '-'
-        new_instance = instance.copy(new_uri_prefix, new_uri_path)
-        assert new_instance.uri_prefix == new_uri_prefix
-        assert new_instance.uri_path == new_uri_path
-        assert list(new_instance.pages.values('id')) == list(instance.pages.values('id'))
-
-
 def test_section_descendants(db):
     instances = Section.objects.all()
     for instance in instances:
@@ -152,20 +129,6 @@ def test_page_clean(db):
         instance.clean()
 
 
-def test_page_copy(db):
-    instances = Page.objects.all()
-    for instance in instances:
-        new_uri_prefix = instance.uri_prefix + '-'
-        new_uri_path = instance.uri_path + '-'
-        new_instance = instance.copy(new_uri_prefix, new_uri_path)
-        assert new_instance.uri_prefix == new_uri_prefix
-        assert new_instance.uri_path == new_uri_path
-        assert new_instance.attribute == instance.attribute
-        assert list(new_instance.conditions.values('id')) == list(instance.conditions.values('id'))
-        assert list(new_instance.questionsets.values('id')) == list(instance.questionsets.values('id'))
-        assert list(new_instance.questions.values('id')) == list(instance.questions.values('id'))
-
-
 def test_page_descendants(db):
     instances = Page.objects.all()
     for instance in instances:
@@ -218,20 +181,6 @@ def test_questionset_clean(db):
         instance.clean()
 
 
-def test_questionset_copy(db):
-    instances = QuestionSet.objects.all()
-    for instance in instances:
-        new_uri_prefix = instance.uri_prefix + '-'
-        new_uri_path = instance.uri_path + '-'
-        new_instance = instance.copy(new_uri_prefix, new_uri_path)
-        assert new_instance.uri_prefix == new_uri_prefix
-        assert new_instance.uri_path == new_uri_path
-        assert new_instance.attribute == instance.attribute
-        assert list(new_instance.conditions.values('id')) == list(instance.conditions.values('id'))
-        assert list(new_instance.questionsets.values('id')) == list(instance.questionsets.values('id'))
-        assert list(new_instance.questions.values('id')) == list(instance.questions.values('id'))
-
-
 def test_questionset_descendants(db):
     instances = QuestionSet.objects.all()
     for instance in instances:
@@ -272,20 +221,7 @@ def test_question_clean(db):
         instance.clean()
 
 
-def test_questionset_property_is_locked(db):
+def test_question_property_is_locked(db):
     instances = Question.objects.all()
     for instance in instances:
         assert instance.is_locked == instance.locked or instance.questionset.is_locked
-
-
-def test_question_copy(db):
-    instances = Question.objects.all()
-    for instance in instances:
-        new_uri_prefix = instance.uri_prefix + '-'
-        new_uri_path = instance.uri_path + '-'
-        new_instance = instance.copy(new_uri_prefix, new_uri_path)
-        assert new_instance.uri_prefix == new_uri_prefix
-        assert new_instance.uri_path == new_uri_path
-        assert new_instance.attribute == instance.attribute
-        assert list(new_instance.conditions.values('id')) == list(instance.conditions.values('id'))
-        assert list(new_instance.optionsets.values('id')) == list(instance.optionsets.values('id'))
diff --git a/rdmo/questions/tests/test_validator_locked_questionsets.py b/rdmo/questions/tests/test_validator_locked_questionsets.py
index b1a0c27263..94f9d42f73 100644
--- a/rdmo/questions/tests/test_validator_locked_questionsets.py
+++ b/rdmo/questions/tests/test_validator_locked_questionsets.py
@@ -3,7 +3,7 @@
 from rest_framework.exceptions import \
     ValidationError as RestFameworkValidationError
 
-from ..models import Page, QuestionSet, QuestionSet
+from ..models import Page, QuestionSet
 from ..serializers.v1 import QuestionSetSerializer
 from ..validators import QuestionSetLockedValidator
 
diff --git a/rdmo/questions/tests/test_viewset_catalog.py b/rdmo/questions/tests/test_viewset_catalog.py
index edc12efbc8..c84c6204b8 100644
--- a/rdmo/questions/tests/test_viewset_catalog.py
+++ b/rdmo/questions/tests/test_viewset_catalog.py
@@ -26,9 +26,6 @@
     'create': {
         'editor': 201, 'reviewer': 403, 'api': 201, 'user': 403, 'anonymous': 401
     },
-    'copy': {
-        'editor': 201, 'reviewer': 403, 'api': 201, 'user': 404, 'anonymous': 401
-    },
     'update': {
         'editor': 200, 'reviewer': 403, 'api': 200, 'user': 404, 'anonymous': 401
     },
@@ -245,36 +242,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['catalog', 'section', 'page', 'questionset', 'question']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Catalog.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-',
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == status_map['copy'][username], response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Catalog.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/questions/tests/test_viewset_catalog_multisite.py b/rdmo/questions/tests/test_viewset_catalog_multisite.py
index 65bd0cb09a..d8b14db237 100644
--- a/rdmo/questions/tests/test_viewset_catalog_multisite.py
+++ b/rdmo/questions/tests/test_viewset_catalog_multisite.py
@@ -207,36 +207,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['catalog', 'section', 'page', 'questionset', 'question']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Catalog.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-',
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Catalog.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
diff --git a/rdmo/questions/tests/test_viewset_page.py b/rdmo/questions/tests/test_viewset_page.py
index 628efdbd16..7b3ec2d748 100644
--- a/rdmo/questions/tests/test_viewset_page.py
+++ b/rdmo/questions/tests/test_viewset_page.py
@@ -27,9 +27,6 @@
     'create': {
         'editor': 201, 'reviewer': 403, 'api': 201, 'user': 403, 'anonymous': 401
     },
-    'copy': {
-        'editor': 201, 'reviewer': 403, 'api': 201, 'user': 404, 'anonymous': 401
-    },
     'update': {
         'editor': 200, 'reviewer': 403, 'api': 200, 'user': 404, 'anonymous': 401
     },
@@ -334,36 +331,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['page', 'questionset', 'question']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Page.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == status_map['copy'][username], response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Page.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/questions/tests/test_viewset_page_multisite.py b/rdmo/questions/tests/test_viewset_page_multisite.py
index 4499ea4403..16f001af38 100644
--- a/rdmo/questions/tests/test_viewset_page_multisite.py
+++ b/rdmo/questions/tests/test_viewset_page_multisite.py
@@ -296,36 +296,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['page', 'questionset', 'question']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Page.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Page.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if get_obj_perms_status_code(instance, username, 'copy') == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
diff --git a/rdmo/questions/tests/test_viewset_question.py b/rdmo/questions/tests/test_viewset_question.py
index 6a3fc3665f..8cc192b2bd 100644
--- a/rdmo/questions/tests/test_viewset_question.py
+++ b/rdmo/questions/tests/test_viewset_question.py
@@ -27,9 +27,6 @@
     'create': {
         'editor': 201, 'reviewer': 403, 'api': 201, 'user': 403, 'anonymous': 401
     },
-    'copy': {
-        'editor': 201, 'reviewer': 403, 'api': 201, 'user': 404, 'anonymous': 401
-    },
     'update': {
         'editor': 200, 'reviewer': 403, 'api': 200, 'user': 404, 'anonymous': 401
     },
@@ -371,36 +368,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['question']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Question.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == status_map['copy'][username], response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Question.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/questions/tests/test_viewset_question_multisite.py b/rdmo/questions/tests/test_viewset_question_multisite.py
index 1eed0425ff..495bade005 100644
--- a/rdmo/questions/tests/test_viewset_question_multisite.py
+++ b/rdmo/questions/tests/test_viewset_question_multisite.py
@@ -333,36 +333,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['question']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Question.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Question.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if get_obj_perms_status_code(instance, username, 'copy') == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
diff --git a/rdmo/questions/tests/test_viewset_questionset.py b/rdmo/questions/tests/test_viewset_questionset.py
index fc575a812b..1462562b5f 100644
--- a/rdmo/questions/tests/test_viewset_questionset.py
+++ b/rdmo/questions/tests/test_viewset_questionset.py
@@ -27,9 +27,6 @@
     'create': {
         'editor': 201, 'reviewer': 403, 'api': 201, 'user': 403, 'anonymous': 401
     },
-    'copy': {
-        'editor': 201, 'reviewer': 403, 'api': 201, 'user': 404, 'anonymous': 401
-    },
     'update': {
         'editor': 200, 'reviewer': 403, 'api': 200, 'user': 404, 'anonymous': 401
     },
@@ -376,36 +373,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['questionset', 'question']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = QuestionSet.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == status_map['copy'][username], response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = QuestionSet.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/questions/tests/test_viewset_questionset_multisite.py b/rdmo/questions/tests/test_viewset_questionset_multisite.py
index c17b024790..f7789c869b 100644
--- a/rdmo/questions/tests/test_viewset_questionset_multisite.py
+++ b/rdmo/questions/tests/test_viewset_questionset_multisite.py
@@ -337,36 +337,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['questionset', 'question']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = QuestionSet.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = QuestionSet.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if get_obj_perms_status_code(instance, username, 'copy') == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
diff --git a/rdmo/questions/tests/test_viewset_section.py b/rdmo/questions/tests/test_viewset_section.py
index 0e56aa9eee..a60d068720 100644
--- a/rdmo/questions/tests/test_viewset_section.py
+++ b/rdmo/questions/tests/test_viewset_section.py
@@ -27,9 +27,6 @@
     'create': {
         'editor': 201, 'reviewer': 403, 'api': 201, 'user': 403, 'anonymous': 401
     },
-    'copy': {
-        'editor': 201, 'reviewer': 403, 'api': 201, 'user': 404, 'anonymous': 401
-    },
     'update': {
         'editor': 200, 'reviewer': 403, 'api': 200, 'user': 404, 'anonymous': 401
     },
@@ -269,36 +266,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['section', 'page', 'questionset', 'question']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Section.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == status_map['copy'][username], response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Section.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/questions/tests/test_viewset_section_multisite.py b/rdmo/questions/tests/test_viewset_section_multisite.py
index 4abe25517a..85cc44dfac 100644
--- a/rdmo/questions/tests/test_viewset_section_multisite.py
+++ b/rdmo/questions/tests/test_viewset_section_multisite.py
@@ -231,36 +231,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['section', 'page', 'questionset', 'question']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Section.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'uri_path': instance.uri_path + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Section.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'uri_path': instance.uri_path
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if get_obj_perms_status_code(instance, username, 'copy') == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
diff --git a/rdmo/tasks/tests/test_models.py b/rdmo/tasks/tests/test_models.py
index 8c22e80c20..2f7654b868 100644
--- a/rdmo/tasks/tests/test_models.py
+++ b/rdmo/tasks/tests/test_models.py
@@ -11,19 +11,3 @@ def test_task_clean(db):
     instances = Task.objects.all()
     for instance in instances:
         instance.clean()
-
-
-def test_task_copy(db):
-    instances = Task.objects.all()
-    for instance in instances:
-        new_uri_prefix = instance.uri_prefix + '-'
-        new_key = instance.key + '-'
-        new_instance = instance.copy(new_uri_prefix, new_key)
-        assert new_instance.uri_prefix == new_uri_prefix
-        assert new_instance.key == new_key
-        assert new_instance.start_attribute == instance.start_attribute
-        assert new_instance.end_attribute == instance.end_attribute
-        assert list(new_instance.catalogs.values('id')) == list(new_instance.catalogs.values('id'))
-        assert list(new_instance.sites.values('id')) == list(new_instance.sites.values('id'))
-        assert list(new_instance.groups.values('id')) == list(new_instance.groups.values('id'))
-        assert list(new_instance.conditions.values('id')) == list(new_instance.conditions.values('id'))
diff --git a/rdmo/tasks/tests/test_validator_unique_uri.py b/rdmo/tasks/tests/test_validator_unique_uri.py
index c8ad46adca..2bf50c4160 100644
--- a/rdmo/tasks/tests/test_validator_unique_uri.py
+++ b/rdmo/tasks/tests/test_validator_unique_uri.py
@@ -12,7 +12,7 @@
 def test_unique_uri_validator_create(db):
     TaskUniqueURIValidator()({
         'uri_prefix': settings.DEFAULT_URI_PREFIX,
-        'key': 'test'
+        'uri_path': 'test'
     })
 
 
@@ -20,7 +20,7 @@ def test_unique_uri_validator_create_error(db):
     with pytest.raises(ValidationError):
         TaskUniqueURIValidator()({
             'uri_prefix': settings.DEFAULT_URI_PREFIX,
-            'key': Task.objects.last().key
+            'uri_path': Task.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
 
 
@@ -29,17 +29,17 @@ def test_unique_uri_validator_update(db):
 
     TaskUniqueURIValidator(task)({
         'uri_prefix': task.uri_prefix,
-        'key': task.key
+        'uri_path': task.uri_path
     })
 
 
 def test_unique_uri_validator_update_error(db):
-    task = Task.objects.first()
+    task = Task.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).first()
 
     with pytest.raises(ValidationError):
         TaskUniqueURIValidator(task)({
             'uri_prefix': task.uri_prefix,
-            'key': Task.objects.last().key
+            'uri_path': Task.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
 
 
@@ -49,7 +49,7 @@ def test_unique_uri_validator_serializer_create(db):
 
     validator({
         'uri_prefix': settings.DEFAULT_URI_PREFIX,
-        'key': 'test'
+        'uri_path': 'test'
     })
 
 
@@ -60,7 +60,7 @@ def test_unique_uri_validator_serializer_create_error(db):
     with pytest.raises(RestFameworkValidationError):
         validator({
             'uri_prefix': settings.DEFAULT_URI_PREFIX,
-            'key': Task.objects.last().key
+            'uri_path': Task.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
 
 
@@ -72,12 +72,12 @@ def test_unique_uri_validator_serializer_update(db):
 
     validator({
         'uri_prefix': task.uri_prefix,
-        'key': task.key
+        'uri_path': task.uri_path
     })
 
 
 def test_unique_uri_validator_serializer_update_error(db):
-    task = Task.objects.first()
+    task = Task.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).first()
 
     validator = TaskUniqueURIValidator()
     validator.set_context(TaskSerializer(instance=task))
@@ -85,5 +85,5 @@ def test_unique_uri_validator_serializer_update_error(db):
     with pytest.raises(RestFameworkValidationError):
         validator({
             'uri_prefix': task.uri_prefix,
-            'key': Task.objects.last().key
+            'uri_path': Task.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
diff --git a/rdmo/tasks/tests/test_viewset_task.py b/rdmo/tasks/tests/test_viewset_task.py
index 8163ab0fe1..2e3a35db97 100644
--- a/rdmo/tasks/tests/test_viewset_task.py
+++ b/rdmo/tasks/tests/test_viewset_task.py
@@ -23,9 +23,6 @@
     'create': {
         'editor': 201, 'reviewer': 403, 'api': 201, 'user': 403, 'anonymous': 401
     },
-    'copy': {
-        'editor': 201, 'reviewer': 403, 'api': 201, 'user': 404, 'anonymous': 401
-    },
     'update': {
         'editor': 200, 'reviewer': 403, 'api': 200, 'user': 404, 'anonymous': 401
     },
@@ -100,7 +97,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': '%s_new_%s' % (instance.key, username),
+            'uri_path': '%s_new_%s' % (instance.uri_path, username),
             'comment': instance.comment,
             'title_en': instance.title_lang1,
             'title_de': instance.title_lang2,
@@ -125,7 +122,7 @@ def test_update(db, client, username, password):
         url = reverse(urlnames['detail'], args=[instance.pk])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': instance.key,
+            'uri_path': instance.uri_path,
             'comment': instance.comment,
             'title_en': instance.title_lang1,
             'title_de': instance.title_lang2,
@@ -167,36 +164,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['task']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Task.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'key': instance.key + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == status_map['copy'][username], response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Task.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'key': instance.key
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/tasks/tests/test_viewset_task_multisite.py b/rdmo/tasks/tests/test_viewset_task_multisite.py
index 80fe89256b..98ecb8a823 100644
--- a/rdmo/tasks/tests/test_viewset_task_multisite.py
+++ b/rdmo/tasks/tests/test_viewset_task_multisite.py
@@ -67,7 +67,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': '%s_new_%s' % (instance.key, username),
+            'uri_path': '%s_new_%s' % (instance.uri_path, username),
             'comment': instance.comment,
             'title_en': instance.title_lang1,
             'title_de': instance.title_lang2,
@@ -92,7 +92,7 @@ def test_update(db, client, username, password):
         url = reverse(urlnames['detail'], args=[instance.pk])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': instance.key,
+            'uri_path': instance.uri_path,
             'comment': instance.comment,
             'title_en': instance.title_lang1,
             'title_de': instance.title_lang2,
@@ -134,36 +134,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['task']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = Task.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'key': instance.key + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = Task.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'key': instance.key
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201 and response.status_code != 404:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
diff --git a/rdmo/views/tests/test_models.py b/rdmo/views/tests/test_models.py
index fa3aaaac8d..e686d1f1d7 100644
--- a/rdmo/views/tests/test_models.py
+++ b/rdmo/views/tests/test_models.py
@@ -11,16 +11,3 @@ def test_view_clean(db):
     instances = View.objects.all()
     for instance in instances:
         instance.clean()
-
-
-def test_view_copy(db):
-    instances = View.objects.all()
-    for instance in instances:
-        new_uri_prefix = instance.uri_prefix + '-'
-        new_key = instance.key + '-'
-        new_instance = instance.copy(new_uri_prefix, new_key)
-        assert new_instance.uri_prefix == new_uri_prefix
-        assert new_instance.key == new_key
-        assert list(new_instance.catalogs.values('id')) == list(new_instance.catalogs.values('id'))
-        assert list(new_instance.sites.values('id')) == list(new_instance.sites.values('id'))
-        assert list(new_instance.groups.values('id')) == list(new_instance.groups.values('id'))
diff --git a/rdmo/views/tests/test_validator_unique_uri.py b/rdmo/views/tests/test_validator_unique_uri.py
index 3fa206ce66..480d5ef6a0 100644
--- a/rdmo/views/tests/test_validator_unique_uri.py
+++ b/rdmo/views/tests/test_validator_unique_uri.py
@@ -12,7 +12,7 @@
 def test_unique_uri_validator_create(db):
     ViewUniqueURIValidator()({
         'uri_prefix': settings.DEFAULT_URI_PREFIX,
-        'key': 'test'
+        'uri_path': 'test'
     })
 
 
@@ -20,7 +20,7 @@ def test_unique_uri_validator_create_error(db):
     with pytest.raises(ValidationError):
         ViewUniqueURIValidator()({
             'uri_prefix': settings.DEFAULT_URI_PREFIX,
-            'key': View.objects.last().key
+            'uri_path': View.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
 
 
@@ -29,17 +29,17 @@ def test_unique_uri_validator_update(db):
 
     ViewUniqueURIValidator(view)({
         'uri_prefix': view.uri_prefix,
-        'key': view.key
+        'uri_path': view.uri_path
     })
 
 
 def test_unique_uri_validator_update_error(db):
-    view = View.objects.first()
+    view = View.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).first()
 
     with pytest.raises(ValidationError):
         ViewUniqueURIValidator(view)({
             'uri_prefix': view.uri_prefix,
-            'key': View.objects.last().key
+            'uri_path': View.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
 
 
@@ -49,7 +49,7 @@ def test_unique_uri_validator_serializer_create(db):
 
     validator({
         'uri_prefix': settings.DEFAULT_URI_PREFIX,
-        'key': 'test'
+        'uri_path': 'test'
     })
 
 
@@ -60,7 +60,7 @@ def test_unique_uri_validator_serializer_create_error(db):
     with pytest.raises(RestFameworkValidationError):
         validator({
             'uri_prefix': settings.DEFAULT_URI_PREFIX,
-            'key': View.objects.last().key
+            'uri_path': View.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
 
 
@@ -72,12 +72,12 @@ def test_unique_uri_validator_serializer_update(db):
 
     validator({
         'uri_prefix': view.uri_prefix,
-        'key': view.key
+        'uri_path': view.uri_path
     })
 
 
 def test_unique_uri_validator_serializer_update_error(db):
-    view = View.objects.first()
+    view = View.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).first()
 
     validator = ViewUniqueURIValidator()
     validator.set_context(ViewSerializer(instance=view))
@@ -85,5 +85,5 @@ def test_unique_uri_validator_serializer_update_error(db):
     with pytest.raises(RestFameworkValidationError):
         validator({
             'uri_prefix': view.uri_prefix,
-            'key': View.objects.last().key
+            'uri_path': View.objects.filter(uri_prefix=settings.DEFAULT_URI_PREFIX).last().uri_path
         })
diff --git a/rdmo/views/tests/test_viewset_view.py b/rdmo/views/tests/test_viewset_view.py
index 45cd11d60e..310afb4662 100644
--- a/rdmo/views/tests/test_viewset_view.py
+++ b/rdmo/views/tests/test_viewset_view.py
@@ -23,9 +23,6 @@
     'create': {
         'editor': 201, 'reviewer': 403, 'api': 201, 'user': 403, 'anonymous': 401
     },
-    'copy': {
-        'editor': 201, 'reviewer': 403, 'api': 201, 'user': 404, 'anonymous': 401
-    },
     'update': {
         'editor': 200, 'reviewer': 403, 'api': 200, 'user': 404, 'anonymous': 401
     },
@@ -100,7 +97,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': '%s_new_%s' % (instance.key, username),
+            'uri_path': '%s_new_%s' % (instance.uri_path, username),
             'comment': instance.comment,
             'template': instance.template,
             'title_en': instance.title_lang1,
@@ -121,7 +118,7 @@ def test_update(db, client, username, password):
         url = reverse(urlnames['detail'], args=[instance.pk])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': instance.key,
+            'uri_path': instance.uri_path,
             'comment': instance.comment,
             'template': instance.template,
             'title_en': instance.title_lang1,
@@ -159,36 +156,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['view']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = View.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'key': instance.key + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == status_map['copy'][username], response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = View.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'key': instance.key
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == status_map['copy'][username], response.json()
diff --git a/rdmo/views/tests/test_viewset_view_multisite.py b/rdmo/views/tests/test_viewset_view_multisite.py
index 00553b91c9..fa84517ec9 100644
--- a/rdmo/views/tests/test_viewset_view_multisite.py
+++ b/rdmo/views/tests/test_viewset_view_multisite.py
@@ -67,7 +67,7 @@ def test_create(db, client, username, password):
         url = reverse(urlnames['list'])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': '%s_new_%s' % (instance.key, username),
+            'uri_path': '%s_new_%s' % (instance.uri_path, username),
             'comment': instance.comment,
             'template': instance.template,
             'title_en': instance.title_lang1,
@@ -88,7 +88,7 @@ def test_update(db, client, username, password):
         url = reverse(urlnames['detail'], args=[instance.pk])
         data = {
             'uri_prefix': instance.uri_prefix,
-            'key': instance.key,
+            'uri_path': instance.uri_path,
             'comment': instance.comment,
             'template': instance.template,
             'title_en': instance.title_lang1,
@@ -126,36 +126,3 @@ def test_detail_export(db, client, username, password, export_format):
         assert root.tag == 'rdmo'
         for child in root:
             assert child.tag in ['view']
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy(db, client, username, password):
-    client.login(username=username, password=password)
-    instances = View.objects.all()
-
-    for instance in instances:
-        url = reverse(urlnames['copy'], args=[instance.pk])
-        data = {
-            'uri_prefix': instance.uri_prefix + '-',
-            'key': instance.key + '-'
-        }
-        response = client.put(url, data, content_type='application/json')
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
-
-
-@pytest.mark.parametrize('username,password', users)
-def test_copy_wrong(db, client, username, password):
-    client.login(username=username, password=password)
-    instance = View.objects.first()
-
-    url = reverse(urlnames['copy'], args=[instance.pk])
-    data = {
-        'uri_prefix': instance.uri_prefix,
-        'key': instance.key
-    }
-    response = client.put(url, data, content_type='application/json')
-
-    if status_map['copy'][username] == 201 and response.status_code != 404:
-        assert response.status_code == 400, response.json()
-    else:
-        assert response.status_code == get_obj_perms_status_code(instance, username, 'copy'), response.json()
diff --git a/testing/fixtures/conditions.json b/testing/fixtures/conditions.json
index 1124c6931b..8d4172436a 100644
--- a/testing/fixtures/conditions.json
+++ b/testing/fixtures/conditions.json
@@ -5,7 +5,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/text_equal_test",
       "uri_prefix": "http://example.com/terms",
-      "key": "text_equal_test",
+      "uri_path": "text_equal_test",
       "comment": "",
       "locked": false,
       "source": 81,
@@ -21,7 +21,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/text_not_equal_test",
       "uri_prefix": "http://example.com/terms",
-      "key": "text_not_equal_test",
+      "uri_path": "text_not_equal_test",
       "comment": "",
       "locked": false,
       "source": 81,
@@ -37,7 +37,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/text_contains_test",
       "uri_prefix": "http://example.com/terms",
-      "key": "text_contains_test",
+      "uri_path": "text_contains_test",
       "comment": "",
       "locked": false,
       "source": 81,
@@ -53,7 +53,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/text_greater_than_0",
       "uri_prefix": "http://example.com/terms",
-      "key": "text_greater_than_0",
+      "uri_path": "text_greater_than_0",
       "comment": "",
       "locked": false,
       "source": 81,
@@ -69,7 +69,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/text_greater_than_equal_0",
       "uri_prefix": "http://example.com/terms",
-      "key": "text_greater_than_equal_0",
+      "uri_path": "text_greater_than_equal_0",
       "comment": "",
       "locked": false,
       "source": 81,
@@ -85,7 +85,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/text_lesser_than_0",
       "uri_prefix": "http://example.com/terms",
-      "key": "text_lesser_than_0",
+      "uri_path": "text_lesser_than_0",
       "comment": "",
       "locked": false,
       "source": 81,
@@ -101,7 +101,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/text_lesser_than_equal_0",
       "uri_prefix": "http://example.com/terms",
-      "key": "text_lesser_than_equal_0",
+      "uri_path": "text_lesser_than_equal_0",
       "comment": "",
       "locked": false,
       "source": 81,
@@ -117,7 +117,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/text_empty",
       "uri_prefix": "http://example.com/terms",
-      "key": "text_empty",
+      "uri_path": "text_empty",
       "comment": "",
       "locked": false,
       "source": 81,
@@ -133,7 +133,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/text_not_empty",
       "uri_prefix": "http://example.com/terms",
-      "key": "text_not_empty",
+      "uri_path": "text_not_empty",
       "comment": "",
       "locked": false,
       "source": 81,
@@ -149,7 +149,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/options_equal_one",
       "uri_prefix": "http://example.com/terms",
-      "key": "options_equal_one",
+      "uri_path": "options_equal_one",
       "comment": "",
       "locked": false,
       "source": 82,
@@ -165,7 +165,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/options_not_equal_one",
       "uri_prefix": "http://example.com/terms",
-      "key": "options_not_equal_one",
+      "uri_path": "options_not_equal_one",
       "comment": "",
       "locked": false,
       "source": 82,
@@ -181,7 +181,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/options_empty",
       "uri_prefix": "http://example.com/terms",
-      "key": "options_empty",
+      "uri_path": "options_empty",
       "comment": "",
       "locked": false,
       "source": 82,
@@ -197,7 +197,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/options_not_empty",
       "uri_prefix": "http://example.com/terms",
-      "key": "options_not_empty",
+      "uri_path": "options_not_empty",
       "comment": "",
       "locked": false,
       "source": 82,
@@ -213,7 +213,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/set_bool_is_true",
       "uri_prefix": "http://example.com/terms",
-      "key": "set_bool_is_true",
+      "uri_path": "set_bool_is_true",
       "comment": "",
       "locked": false,
       "source": 114,
@@ -229,7 +229,7 @@
     "fields": {
       "uri": "http://example.com/terms/conditions/optionset_bool_is_true",
       "uri_prefix": "http://example.com/terms",
-      "key": "optionset_bool_is_true",
+      "uri_path": "optionset_bool_is_true",
       "comment": "",
       "locked": false,
       "source": 120,
@@ -245,7 +245,7 @@
     "fields": {
       "uri": "https://foo.com/terms/conditions/foo-condition",
       "uri_prefix": "https://foo.com/terms",
-      "key": "foo-condition",
+      "uri_path": "foo-condition",
       "comment": "",
       "locked": false,
       "source": 122,
@@ -263,7 +263,7 @@
     "fields": {
       "uri": "https://bar.com/terms/conditions/bar-condition",
       "uri_prefix": "https://bar.com/terms",
-      "key": "bar-condition",
+      "uri_path": "bar-condition",
       "comment": "",
       "locked": false,
       "source": 123,
diff --git a/testing/fixtures/tasks.json b/testing/fixtures/tasks.json
index 640e3501a8..cc107eef63 100644
--- a/testing/fixtures/tasks.json
+++ b/testing/fixtures/tasks.json
@@ -5,7 +5,7 @@
     "fields": {
       "uri": "http://example.com/terms/tasks/text_equal_test",
       "uri_prefix": "http://example.com/terms",
-      "key": "text_equal_test",
+      "uri_path": "text_equal_test",
       "comment": "1",
       "locked": false,
       "title_lang1": "text_equal_test",
@@ -42,7 +42,7 @@
     "fields": {
       "uri": "http://example.com/terms/tasks/null",
       "uri_prefix": "http://example.com/terms",
-      "key": "null",
+      "uri_path": "null",
       "comment": "1",
       "locked": false,
       "title_lang1": "null",
@@ -79,7 +79,7 @@
     "fields": {
       "uri": "https://foo.com/terms/tasks/foo-task",
       "uri_prefix": "https://foo.com/terms",
-      "key": "foo-task",
+      "uri_path": "foo-task",
       "comment": "",
       "locked": false,
       "title_lang1": "foo-task",
@@ -116,7 +116,7 @@
     "fields": {
       "uri": "https://bar.com/terms/tasks/bar-task",
       "uri_prefix": "https://bar.com/terms",
-      "key": "bar-task",
+      "uri_path": "bar-task",
       "comment": "",
       "locked": false,
       "title_lang1": "bar-task",
diff --git a/testing/fixtures/views.json b/testing/fixtures/views.json
index dde510b750..3f6554b89a 100644
--- a/testing/fixtures/views.json
+++ b/testing/fixtures/views.json
@@ -5,7 +5,7 @@
     "fields": {
       "uri": "http://example.com/terms/views/view_a",
       "uri_prefix": "http://example.com/terms",
-      "key": "view_a",
+      "uri_path": "view_a",
       "comment": "",
       "locked": false,
       "template": "{% load view_tags %}\n\n<h2>Individual</h2>\n\n<h3>Collection</h3>\n\n<ul>\n    {% render_value_list 'individual/collection/bool' %}\n</ul>\n<ul>\n    {% render_value_list 'individual/collection/checkbox' %}\n</ul>\n<ul>\n    {% render_value_list 'individual/collection/datetime' %}\n</ul>\n<ul>\n    {% render_value_list 'individual/collection/float' %}\n</ul>\n<ul>\n    {% render_value_list 'individual/collection/integer' %}\n</ul>\n<ul>\n    {% render_value_list 'individual/collection/radio' %}\n</ul>\n<ul>\n    {% render_value_list 'individual/collection/range' %}\n</ul>\n<ul>\n    {% render_value_list 'individual/collection/select' %}\n</ul>\n<ul>\n    {% render_value_list 'individual/collection/text' %}\n</ul>\n<ul>\n    {% render_value_list 'individual/collection/textarea' %}\n</ul>\n<ul>\n    {% render_value_list 'individual/collection/url' %}\n</ul>\n<ul>\n    {% render_value_list 'individual/collection/file' %}\n</ul>\n\n<h2>Individual</h2>\n\n<h3>Single</h3>\n\n<p>\n    {% render_value 'individual/single/bool' %}\n</p>\n<p>\n    {% render_value 'individual/single/checkbox' %}\n</p>\n<p>\n    {% render_value 'individual/single/datetime' %}\n</p>\n<p>\n    {% render_value 'individual/single/float' %}\n</p>\n<p>\n    {% render_value 'individual/single/integer' %}\n</p>\n<p>\n    {% render_value 'individual/single/radio' %}\n</p>\n<p>\n    {% render_value 'individual/single/range' %}\n</p>\n<p>\n    {% render_value 'individual/single/select' %}\n</p>\n<p>\n    {% render_value 'individual/single/text' %}\n</p>\n<p>\n    {% render_value 'individual/single/textarea' %}\n</p>\n<p>\n    {% render_value 'individual/single/url' %}\n</p>\n<p>\n    {% render_value 'individual/single/file' %}\n</p>\n\n<h2>Set</h2>\n\n{% get_values 'set/id' index=0 as sets %}\n\n<h3>Collection</h3>\n\n{% for set in sets %}\n\n<h4>{{ set.value }}</h4>\n\n<ul>\n    {% render_value_list 'set/collection/bool' set_index=set.set_index %}\n</ul>\n<ul>\n    {% render_value_list 'set/collection/checkbox' set_index=set.set_index %}\n</ul>\n<ul>\n    {% render_value_list 'set/collection/datetime' set_index=set.set_index %}\n</ul>\n<ul>\n    {% render_value_list 'set/collection/float' set_index=set.set_index %}\n</ul>\n<ul>\n    {% render_value_list 'set/collection/integer' set_index=set.set_index %}\n</ul>\n<ul>\n    {% render_value_list 'set/collection/radio' set_index=set.set_index %}\n</ul>\n<ul>\n    {% render_value_list 'set/collection/range' set_index=set.set_index %}\n</ul>\n<ul>\n    {% render_value_list 'set/collection/select' set_index=set.set_index %}\n</ul>\n<ul>\n    {% render_value_list 'set/collection/text' set_index=set.set_index %}\n</ul>\n<ul>\n    {% render_value_list 'set/collection/textarea' set_index=set.set_index %}\n</ul>\n<ul>\n    {% render_value_list 'set/collection/url' set_index=set.set_index %}\n</ul>\n<ul>\n    {% render_value_list 'set/collection/file' set_index=set.set_index %}\n</ul>\n\n{% endfor %}\n\n<h3>Collection</h3>\n\n{% for set in sets %}\n\n<h4>{% render_value 'set/id' set_index=set.set_index %}</h4>\n\n<p>\n    {% render_value 'set/single/bool' set_index=set.set_index %}\n</p>\n<p>\n    {% render_value 'set/single/checkbox' set_index=set.set_index %}\n</p>\n<p>\n    {% render_value 'set/single/datetime' set_index=set.set_index %}\n</p>\n<p>\n    {% render_value 'set/single/float' set_index=set.set_index %}\n</p>\n<p>\n    {% render_value 'set/single/integer' set_index=set.set_index %}\n</p>\n<p>\n    {% render_value 'set/single/radio' set_index=set.set_index %}\n</p>\n<p>\n    {% render_value 'set/single/range' set_index=set.set_index %}\n</p>\n<p>\n    {% render_value 'set/single/select' set_index=set.set_index %}\n</p>\n<p>\n    {% render_value 'set/single/text' set_index=set.set_index %}\n</p>\n<p>\n    {% render_value 'set/single/textarea' set_index=set.set_index %}\n</p>\n<p>\n    {% render_value 'set/single/url' set_index=set.set_index %}\n</p>\n<p>\n    {% render_value 'set/single/file' set_index=set.set_index %}\n</p>\n\n{% endfor %}",
@@ -36,7 +36,7 @@
     "fields": {
       "uri": "http://example.com/terms/views/view_b",
       "uri_prefix": "http://example.com/terms",
-      "key": "view_b",
+      "uri_path": "view_b",
       "comment": "",
       "locked": false,
       "template": "{% load view_tags %}\n\n<h2>Empty</h2>",
@@ -67,7 +67,7 @@
     "fields": {
       "uri": "http://example.com/terms/views/view_c",
       "uri_prefix": "http://example.com/terms",
-      "key": "view_c",
+      "uri_path": "view_c",
       "comment": "",
       "locked": false,
       "template": "{% load view_tags %}\n\n<h2>Empty</h2>",
@@ -96,7 +96,7 @@
     "fields": {
       "uri": "https://foo.com/terms/views/foo-view",
       "uri_prefix": "https://foo.com/terms",
-      "key": "foo-view",
+      "uri_path": "foo-view",
       "comment": "",
       "locked": false,
       "template": "",
@@ -129,7 +129,7 @@
     "fields": {
       "uri": "https://bar.com/terms/views/bar-view",
       "uri_prefix": "https://bar.com/terms",
-      "key": "bar-view",
+      "uri_path": "bar-view",
       "comment": "",
       "locked": false,
       "template": "",
diff --git a/testing/import/catalogs.json b/testing/import/catalogs.json
index e7027993f0..a018cd5adb 100644
--- a/testing/import/catalogs.json
+++ b/testing/import/catalogs.json
@@ -5364,5 +5364,33 @@
     "title_de": "Katalog2",
     "help_de": null,
     "sections": null
+  },
+  {
+    "uri": "https://foo.com/terms/questions/foo-catalog",
+    "type": "catalogs",
+    "model": "questions.catalog",
+    "uri_prefix": "https://foo.com/terms",
+    "uri_path": "foo-catalog",
+    "comment": null,
+    "order": "1",
+    "title_en": "foo-catalog",
+    "help_en": null,
+    "title_de": "foo-catalog",
+    "help_de": null,
+    "sections": null
+  },
+  {
+    "uri": "https://bar.com/terms/questions/bar-catalog",
+    "type": "catalogs",
+    "model": "questions.catalog",
+    "uri_prefix": "https://bar.com/terms",
+    "uri_path": "bar-catalog",
+    "comment": null,
+    "order": "1",
+    "title_en": "bar-catalog",
+    "help_en": null,
+    "title_de": "bar-catalog",
+    "help_de": null,
+    "sections": null
   }
 ]
\ No newline at end of file
diff --git a/testing/xml/elements/conditions.xml b/testing/xml/elements/conditions.xml
index 79505f4cab..0afd835112 100644
--- a/testing/xml/elements/conditions.xml
+++ b/testing/xml/elements/conditions.xml
@@ -2,7 +2,7 @@
 <rdmo xmlns:dc="http://purl.org/dc/elements/1.1/" created="2023-04-20T09:39:37.642472+02:00" version="1.9.2">
 	<condition dc:uri="http://example.com/terms/conditions/options_empty">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>options_empty</key>
+		<uri_path>options_empty</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/option"/>
 		<relation>empty</relation>
@@ -11,7 +11,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/options_equal_one">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>options_equal_one</key>
+		<uri_path>options_equal_one</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/option"/>
 		<relation>eq</relation>
@@ -20,7 +20,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/optionset_bool_is_true">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>optionset_bool_is_true</key>
+		<uri_path>optionset_bool_is_true</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/optionset/bool"/>
 		<relation>eq</relation>
@@ -29,7 +29,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/options_not_empty">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>options_not_empty</key>
+		<uri_path>options_not_empty</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/option"/>
 		<relation>notempty</relation>
@@ -38,7 +38,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/options_not_equal_one">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>options_not_equal_one</key>
+		<uri_path>options_not_equal_one</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/option"/>
 		<relation>neq</relation>
@@ -47,7 +47,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/set_bool_is_true">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>set_bool_is_true</key>
+		<uri_path>set_bool_is_true</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/set/bool"/>
 		<relation>eq</relation>
@@ -56,7 +56,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/text_contains_test">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>text_contains_test</key>
+		<uri_path>text_contains_test</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/text"/>
 		<relation>contains</relation>
@@ -65,7 +65,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/text_empty">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>text_empty</key>
+		<uri_path>text_empty</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/text"/>
 		<relation>empty</relation>
@@ -74,7 +74,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/text_equal_test">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>text_equal_test</key>
+		<uri_path>text_equal_test</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/text"/>
 		<relation>eq</relation>
@@ -83,7 +83,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/text_greater_than_0">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>text_greater_than_0</key>
+		<uri_path>text_greater_than_0</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/text"/>
 		<relation>gt</relation>
@@ -92,7 +92,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/text_greater_than_equal_0">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>text_greater_than_equal_0</key>
+		<uri_path>text_greater_than_equal_0</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/text"/>
 		<relation>gte</relation>
@@ -101,7 +101,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/text_lesser_than_0">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>text_lesser_than_0</key>
+		<uri_path>text_lesser_than_0</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/text"/>
 		<relation>lt</relation>
@@ -110,7 +110,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/text_lesser_than_equal_0">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>text_lesser_than_equal_0</key>
+		<uri_path>text_lesser_than_equal_0</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/text"/>
 		<relation>lte</relation>
@@ -119,7 +119,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/text_not_empty">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>text_not_empty</key>
+		<uri_path>text_not_empty</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/text"/>
 		<relation>notempty</relation>
@@ -128,7 +128,7 @@
 	</condition>
 	<condition dc:uri="http://example.com/terms/conditions/text_not_equal_test">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>text_not_equal_test</key>
+		<uri_path>text_not_equal_test</uri_path>
 		<dc:comment/>
 		<source dc:uri="http://example.com/terms/domain/conditions/text"/>
 		<relation>neq</relation>
diff --git a/testing/xml/elements/tasks.xml b/testing/xml/elements/tasks.xml
index 9edca803cc..30e9ec57d8 100644
--- a/testing/xml/elements/tasks.xml
+++ b/testing/xml/elements/tasks.xml
@@ -2,7 +2,7 @@
 <rdmo xmlns:dc="http://purl.org/dc/elements/1.1/" created="2023-04-20T09:39:55.354321+02:00" version="1.9.2">
 	<task dc:uri="http://example.com/terms/tasks/text_equal_test">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>text_equal_test</key>
+		<uri_path>text_equal_test</uri_path>
 		<dc:comment>1</dc:comment>
 		<title lang="en">text_equal_test</title>
 		<text lang="en">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est. Lorem ipsum dolor sit amet.</text>
@@ -21,7 +21,7 @@
 	</task>
 	<task dc:uri="http://example.com/terms/tasks/null">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>null</key>
+		<uri_path>null</uri_path>
 		<dc:comment>1</dc:comment>
 		<title lang="en">null</title>
 		<text lang="en">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est. Lorem ipsum dolor sit amet.</text>
diff --git a/testing/xml/elements/views.xml b/testing/xml/elements/views.xml
index cdbbdb114e..795476c747 100644
--- a/testing/xml/elements/views.xml
+++ b/testing/xml/elements/views.xml
@@ -2,7 +2,7 @@
 <rdmo xmlns:dc="http://purl.org/dc/elements/1.1/" created="2023-04-20T09:40:06.795538+02:00" version="1.9.2">
 	<view dc:uri="http://example.com/terms/views/view_a">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>view_a</key>
+		<uri_path>view_a</uri_path>
 		<dc:comment/>
 		<title lang="en">View A</title>
 		<help lang="en">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.</help>
@@ -191,7 +191,7 @@
 	</view>
 	<view dc:uri="http://example.com/terms/views/view_b">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>view_b</key>
+		<uri_path>view_b</uri_path>
 		<dc:comment/>
 		<title lang="en">View B</title>
 		<help lang="en">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.</help>
@@ -206,7 +206,7 @@
 	</view>
 	<view dc:uri="http://example.com/terms/views/view_c">
 		<uri_prefix>http://example.com/terms</uri_prefix>
-		<key>view_c</key>
+		<uri_path>view_c</uri_path>
 		<dc:comment/>
 		<title lang="en">Project Test View C</title>
 		<help lang="en">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.</help>