diff --git a/arkindex/dataimport/api.py b/arkindex/dataimport/api.py
index 2dc1834125b2f8b80d32217ae4ca0d996bf50c4f..587a12fbda62faa63391180de934cd94d8ef417e 100644
--- a/arkindex/dataimport/api.py
+++ b/arkindex/dataimport/api.py
@@ -1,4 +1,5 @@
 from django.db.models import Sum
+from django.http.response import Http404
 from django.shortcuts import get_object_or_404
 from django.core.exceptions import PermissionDenied
 from django.conf import settings
@@ -41,13 +42,13 @@ class DataImportsList(CorpusACLMixin, ListCreateAPIView):
         if serializer.validated_data['mode'] not in (DataImportMode.Images, ):
             raise ValidationError('Unsupported mode for now, sorry.')
 
-        if Right.Write not in serializer.validated_data['corpus'].get_acl_rights(self.request.user):
+        if not self.has_write_access(serializer.validated_data['corpus']):
             raise PermissionDenied
 
         return super().perform_create(serializer)
 
 
-class DataImportDetails(RetrieveUpdateDestroyAPIView):
+class DataImportDetails(CorpusACLMixin, RetrieveUpdateDestroyAPIView):
     """
     Retrieve and edit a data import
     """
@@ -60,7 +61,7 @@ class DataImportDetails(RetrieveUpdateDestroyAPIView):
 
     def perform_update(self, serializer):
         dataimport = serializer.instance
-        if Right.Write not in dataimport.corpus.get_acl_rights(self.request.user):
+        if not self.has_write_access(dataimport.corpus):
             raise PermissionDenied
 
         if dataimport.state not in (DataImportState.Created, DataImportState.Configured):
@@ -84,7 +85,7 @@ class DataImportDetails(RetrieveUpdateDestroyAPIView):
             dataimport.save()
 
     def perform_destroy(self, instance):
-        if Right.Write not in instance.corpus.get_acl_rights(self.request.user):
+        if not self.has_write_access(instance.corpus):
             raise PermissionDenied
         if instance.state == DataImportState.Running:
             raise ValidationError("Cannot delete a workflow while it is running")
@@ -106,7 +107,7 @@ class DataImportFailures(ListAPIView):
         ).prefetch_related('dataimport__revision__repo', 'element').order_by('path', 'line')
 
 
-class DataImportDemo(CreateAPIView):
+class DataImportDemo(CorpusACLMixin, CreateAPIView):
     """
     Create, configure and start an Images workflow from a single DataFile
     """
@@ -117,7 +118,7 @@ class DataImportDemo(CreateAPIView):
     def create(self, request, pk=None, **kwargs):
 
         # Get Datafile
-        corpora = Corpus.objects.readable(self.request.user).filter(corpus_right__can_write=True)
+        corpora = Corpus.objects.writable(self.request.user)
         datafile = get_object_or_404(DataFile, corpus__in=corpora, id=pk)
 
         # Get volume, defaults to demo one
@@ -133,7 +134,7 @@ class DataImportDemo(CreateAPIView):
 
         volume = get_object_or_404(Element, **filters)
 
-        assert Right.Write in volume.corpus.get_acl_rights(self.request.user), \
+        assert self.has_write_access(volume.corpus), \
             'Corpus is not writable'
 
         # Start the import
@@ -164,7 +165,7 @@ class DataFileList(CorpusACLMixin, ListAPIView):
         return DataFile.objects.filter(corpus=self.get_corpus(self.kwargs['pk']))
 
 
-class DataFileRetrieve(RetrieveUpdateDestroyAPIView):
+class DataFileRetrieve(CorpusACLMixin, RetrieveUpdateDestroyAPIView):
     """
     Get one file
     """
@@ -175,17 +176,17 @@ class DataFileRetrieve(RetrieveUpdateDestroyAPIView):
         return DataFile.objects.filter(corpus__in=Corpus.objects.readable(self.request.user))
 
     def perform_update(self, serializer):
-        if Right.Write not in serializer.instance.corpus.get_acl_rights(self.request.user):
+        if not self.has_write_access(serializer.instance.corpus):
             raise PermissionDenied
         return super().perform_update(serializer)
 
     def perform_destroy(self, instance):
-        if Right.Write not in instance.corpus.get_acl_rights(self.request.user):
+        if not self.has_write_access(instance.corpus):
             raise PermissionDenied
         return super().perform_destroy(instance)
 
 
-class DataFileUpload(APIView):
+class DataFileUpload(CorpusACLMixin, APIView):
     """
     Upload a new file to a corpus
     """
@@ -195,14 +196,10 @@ class DataFileUpload(APIView):
     def post(self, request, pk=None, format=None):
         if 'file' not in request.FILES:
             raise ValidationError({'file': ['No file was sent in the request']})
-        corpus_qs = Corpus.objects.filter(id=pk)
-        if not corpus_qs.exists():
+        try:
+            corpus = self.get_corpus(pk, right=Right.Write)
+        except Http404:
             raise ValidationError({'corpus': ['Corpus not found']})
-        corpus = corpus_qs.get()
-
-        # Check corpus is writable for current user
-        if Right.Write not in corpus.get_acl_rights(self.request.user):
-            raise PermissionDenied
 
         file_obj = request.FILES['file']
 
@@ -287,7 +284,7 @@ class AvailableRepositoriesList(ListCreateAPIView):
         return Response(data={'import_id': str(dataimport.id)}, status=status.HTTP_201_CREATED)
 
 
-class RepositoryRetrieve(RetrieveUpdateDestroyAPIView):
+class RepositoryRetrieve(CorpusACLMixin, RetrieveUpdateDestroyAPIView):
     permission_classes = (IsAuthenticated, )
     serializer_class = RepositorySerializer
 
@@ -298,12 +295,12 @@ class RepositoryRetrieve(RetrieveUpdateDestroyAPIView):
         )
 
     def perform_update(self, serializer):
-        if Right.Write not in serializer.instance.corpus.get_acl_rights(self.request.user):
+        if not self.has_write_access(serializer.instance.corpus):
             raise PermissionDenied
         return super().perform_update(self, serializer)
 
     def perform_destroy(self, instance):
-        if Right.Write not in instance.corpus.get_acl_rights(self.request.user):
+        if not self.has_write_access(instance.corpus):
             raise PermissionDenied
         return super().perform_destroy(self, instance)
 
@@ -314,7 +311,7 @@ class RepositoryStartImport(RetrieveAPIView):
     def get_queryset(self):
         return Repository.objects.filter(
             credentials__user=self.request.user,
-            corpus__in=Corpus.objects.readable(self.request.user).filter(corpus_right__can_write=True),
+            corpus__in=Corpus.objects.writable(self.request.user),
         )
 
     def get(self, request, *args, **kwargs):
diff --git a/arkindex/dataimport/serializers.py b/arkindex/dataimport/serializers.py
index ae812acd4774f770d4a0f564237b45d7cb14b3b6..17c79039f97496403429c679fac97c947b8938c7 100644
--- a/arkindex/dataimport/serializers.py
+++ b/arkindex/dataimport/serializers.py
@@ -253,7 +253,6 @@ class ExternalRepositorySerializer(serializers.BaseSerializer):
 
         return {
             'id': data['id'],
-            'corpus': Corpus.objects.readable(self.request.user)
-                                    .filter(corpus_right__can_write=True)
+            'corpus': Corpus.objects.writable(self.request.user)
                                     .get(id=data['corpus'])
         }
diff --git a/arkindex/documents/managers.py b/arkindex/documents/managers.py
index f8c4d2a216f34d00db220da5dfb12a8899a54fd1..a3cdf753b8e6d89ef9f7082018d2d8565d0d5165 100644
--- a/arkindex/documents/managers.py
+++ b/arkindex/documents/managers.py
@@ -140,3 +140,17 @@ class CorpusManager(models.Manager):
         qs = qs.prefetch_related('corpus_right')
         qs = qs.filter(models.Q(public=True) | models.Q(corpus_right__user=user))
         return qs
+
+    def writable(self, user):
+        # An anonymous user cannot write anything
+        if user.is_anonymous:
+            return super().none()
+
+        qs = super().get_queryset().order_by('name')
+
+        # Admins have access to every corpus
+        if user.is_admin:
+            return qs.all()
+
+        # Authenticated users can write only on corpora with ACL
+        return qs.filter(corpus_right__user=user, corpus_right__can_write=True)
diff --git a/arkindex/documents/serializers/transcriptions.py b/arkindex/documents/serializers/transcriptions.py
index 3596477605ef181598df30b79639349514f7a588..8f7373f5ab9730018a34d12c69e4f30eaf6dc282 100644
--- a/arkindex/documents/serializers/transcriptions.py
+++ b/arkindex/documents/serializers/transcriptions.py
@@ -43,7 +43,7 @@ class TranscriptionCreateSerializer(serializers.Serializer):
         super().__init__(*args, **kwargs)
         assert 'request' in self.context, 'An API request is required to initialize this serializer'
         self.fields['element'].queryset = Element.objects.filter(
-            corpus__in=Corpus.objects.readable(self.context['request'].user).filter(corpus_right__can_write=True),
+            corpus__in=Corpus.objects.writable(self.context['request'].user),
         )
 
 
@@ -79,5 +79,5 @@ class TranscriptionsSerializer(serializers.Serializer):
         super().__init__(*args, **kwargs)
         assert 'request' in self.context, 'An API request is required to initialize this serializer'
         self.fields['parent'].queryset = Element.objects.filter(
-            corpus__in=Corpus.objects.readable(self.context['request'].user).filter(corpus_right__can_write=True),
+            corpus__in=Corpus.objects.writable(self.context['request'].user),
         )
diff --git a/arkindex/project/mixins.py b/arkindex/project/mixins.py
index 36c82363100dba2abff81252ef7307fe63d8f9ad..fa2fc6238c50f03989bad13f971b2164d17eac2d 100644
--- a/arkindex/project/mixins.py
+++ b/arkindex/project/mixins.py
@@ -10,3 +10,17 @@ class CorpusACLMixin(object):
         if right not in corpus.get_acl_rights(self.request.user):
             raise PermissionDenied()
         return corpus
+
+    def has_read_access(self, corpus):
+        assert isinstance(corpus, Corpus)
+        return corpus.public or self.request.user.is_admin or Right.Read in corpus.get_acl_rights(self.request.user)
+
+    def has_write_access(self, corpus):
+        assert isinstance(corpus, Corpus)
+        return self.request.user.is_admin or not self.request.user.is_anonymous and \
+            Right.Write in corpus.get_acl_rights(self.request.user)
+
+    def has_admin_access(self, corpus):
+        assert isinstance(corpus, Corpus)
+        return self.request.user.is_admin or not self.request.user.is_anonymous and \
+            Right.Admin in corpus.get_acl_rights(self.request.user)