From 6492908c598fe833d40136f37a9b6cf56fe37f77 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Mon, 7 Mar 2016 01:53:13 -0400 Subject: [PATCH] Make folders and tags apps multitenant. --- .../appearance/templates/appearance/base.html | 2 +- mayan/apps/documents/forms.py | 2 +- mayan/apps/documents/managers.py | 4 +- .../0029_documenttype_organization.py | 21 +++ .../migrations/0030_document_organization.py | 21 +++ .../0031_remove_document_organization.py | 18 +++ .../migrations/0032_auto_20160307_0504.py | 21 +++ mayan/apps/documents/models.py | 7 + mayan/apps/documents/views.py | 124 +++++++++------ mayan/apps/folders/forms.py | 2 +- .../migrations/0005_folder_organization.py | 22 +++ mayan/apps/folders/models.py | 7 + mayan/apps/folders/views.py | 16 +- mayan/apps/organizations/__init__.py | 3 + mayan/apps/organizations/admin.py | 11 ++ mayan/apps/organizations/apps.py | 12 ++ mayan/apps/organizations/management.py | 43 +++++ mayan/apps/organizations/managers.py | 64 ++++++++ mayan/apps/organizations/middleware.py | 10 ++ .../organizations/migrations/0001_initial.py | 26 +++ .../apps/organizations/migrations/__init__.py | 0 mayan/apps/organizations/models.py | 71 +++++++++ mayan/apps/organizations/shortcuts.py | 8 + mayan/apps/organizations/tests.py | 149 ++++++++++++++++++ mayan/apps/tags/forms.py | 2 +- .../tags/migrations/0007_tag_organization.py | 22 +++ mayan/apps/tags/models.py | 9 ++ mayan/apps/tags/views.py | 26 +-- mayan/settings/base.py | 4 + 29 files changed, 661 insertions(+), 66 deletions(-) create mode 100644 mayan/apps/documents/migrations/0029_documenttype_organization.py create mode 100644 mayan/apps/documents/migrations/0030_document_organization.py create mode 100644 mayan/apps/documents/migrations/0031_remove_document_organization.py create mode 100644 mayan/apps/documents/migrations/0032_auto_20160307_0504.py create mode 100644 mayan/apps/folders/migrations/0005_folder_organization.py create mode 100644 mayan/apps/organizations/__init__.py create mode 100644 mayan/apps/organizations/admin.py create mode 100644 mayan/apps/organizations/apps.py create mode 100644 mayan/apps/organizations/management.py create mode 100644 mayan/apps/organizations/managers.py create mode 100644 mayan/apps/organizations/middleware.py create mode 100644 mayan/apps/organizations/migrations/0001_initial.py create mode 100644 mayan/apps/organizations/migrations/__init__.py create mode 100644 mayan/apps/organizations/models.py create mode 100644 mayan/apps/organizations/shortcuts.py create mode 100644 mayan/apps/organizations/tests.py create mode 100644 mayan/apps/tags/migrations/0007_tag_organization.py diff --git a/mayan/apps/appearance/templates/appearance/base.html b/mayan/apps/appearance/templates/appearance/base.html index 2e5771dc34..b3b2ffdff0 100644 --- a/mayan/apps/appearance/templates/appearance/base.html +++ b/mayan/apps/appearance/templates/appearance/base.html @@ -70,7 +70,7 @@ {% if not user.is_authenticated %} {% trans 'Anonymous' %} {% else %} -
  • {{ user.get_full_name|default:user }}
  • +
  • {{ request.organization }}: {{ user.get_full_name|default:user }}
  • {% endif %} diff --git a/mayan/apps/documents/forms.py b/mayan/apps/documents/forms.py index ad7808fd86..f2237e4b69 100644 --- a/mayan/apps/documents/forms.py +++ b/mayan/apps/documents/forms.py @@ -104,7 +104,7 @@ class DocumentTypeSelectForm(forms.Form): as form #1 in the document creation wizard """ document_type = forms.ModelChoiceField( - queryset=DocumentType.objects.all(), label=_('Document type') + queryset=DocumentType.on_organization.all(), label=_('Document type') ) diff --git a/mayan/apps/documents/managers.py b/mayan/apps/documents/managers.py index 6dba4b61d1..68438270c2 100644 --- a/mayan/apps/documents/managers.py +++ b/mayan/apps/documents/managers.py @@ -4,6 +4,7 @@ from datetime import timedelta import logging from django.apps import apps +from django.conf import settings from django.db import models from django.utils.timezone import now @@ -132,7 +133,8 @@ class RecentDocumentManager(models.Manager): if user.is_authenticated(): return document_model.objects.filter( - recentdocument__user=user + recentdocument__user=user, + document_type__organization__id=settings.ORGANIZATION_ID ).order_by('-recentdocument__datetime_accessed') else: return document_model.objects.none() diff --git a/mayan/apps/documents/migrations/0029_documenttype_organization.py b/mayan/apps/documents/migrations/0029_documenttype_organization.py new file mode 100644 index 0000000000..bf0504e52f --- /dev/null +++ b/mayan/apps/documents/migrations/0029_documenttype_organization.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('organizations', '0001_initial'), + ('documents', '0028_newversionblock'), + ] + + operations = [ + migrations.AddField( + model_name='documenttype', + name='organization', + field=models.ForeignKey(default=1, to='organizations.Organization'), + preserve_default=False, + ), + ] diff --git a/mayan/apps/documents/migrations/0030_document_organization.py b/mayan/apps/documents/migrations/0030_document_organization.py new file mode 100644 index 0000000000..d24344498f --- /dev/null +++ b/mayan/apps/documents/migrations/0030_document_organization.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('organizations', '0001_initial'), + ('documents', '0029_documenttype_organization'), + ] + + operations = [ + migrations.AddField( + model_name='document', + name='organization', + field=models.ForeignKey(default=1, to='organizations.Organization'), + preserve_default=False, + ), + ] diff --git a/mayan/apps/documents/migrations/0031_remove_document_organization.py b/mayan/apps/documents/migrations/0031_remove_document_organization.py new file mode 100644 index 0000000000..ddafccbcb9 --- /dev/null +++ b/mayan/apps/documents/migrations/0031_remove_document_organization.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('documents', '0030_document_organization'), + ] + + operations = [ + migrations.RemoveField( + model_name='document', + name='organization', + ), + ] diff --git a/mayan/apps/documents/migrations/0032_auto_20160307_0504.py b/mayan/apps/documents/migrations/0032_auto_20160307_0504.py new file mode 100644 index 0000000000..ec74666d13 --- /dev/null +++ b/mayan/apps/documents/migrations/0032_auto_20160307_0504.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import organizations.shortcuts + + +class Migration(migrations.Migration): + + dependencies = [ + ('documents', '0031_remove_document_organization'), + ] + + operations = [ + migrations.AlterField( + model_name='documenttype', + name='organization', + field=models.ForeignKey(default=organizations.shortcuts.get_current_organization, to='organizations.Organization'), + preserve_default=True, + ), + ] diff --git a/mayan/apps/documents/models.py b/mayan/apps/documents/models.py index c671b0d733..0142628e36 100644 --- a/mayan/apps/documents/models.py +++ b/mayan/apps/documents/models.py @@ -24,6 +24,9 @@ from converter.exceptions import InvalidOfficeFormat, PageCountError from converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION from converter.models import Transformation from mimetype.api import get_mimetype +from organizations.models import Organization +from organizations.managers import CurrentOrganizationManager +from organizations.shortcuts import get_current_organization from permissions import Permission from .events import ( @@ -62,6 +65,9 @@ class DocumentType(models.Model): Define document types or classes to which a specific set of properties can be attached """ + organization = models.ForeignKey( + Organization, default=get_current_organization + ) label = models.CharField( max_length=32, unique=True, verbose_name=_('Label') ) @@ -87,6 +93,7 @@ class DocumentType(models.Model): ) objects = DocumentTypeManager() + on_organization = CurrentOrganizationManager() def __str__(self): return self.label diff --git a/mayan/apps/documents/views.py b/mayan/apps/documents/views.py index 4ee92bbddb..27d31d8a73 100644 --- a/mayan/apps/documents/views.py +++ b/mayan/apps/documents/views.py @@ -72,7 +72,7 @@ class DocumentListView(SingleObjectListView): object_permission = permission_document_view def get_document_queryset(self): - return Document.objects.all() + return Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) def get_queryset(self): self.queryset = self.get_document_queryset().filter(is_stub=False) @@ -88,7 +88,7 @@ class DeletedDocumentListView(DocumentListView): } def get_document_queryset(self): - queryset = Document.trash.all() + queryset = Document.trash.filter(document_type__organization__id=settings.ORGANIZATION_ID) try: Permission.check_permissions( @@ -111,7 +111,7 @@ class DeletedDocumentDeleteView(ConfirmView): def object_action(self, instance): source_document = get_object_or_404( - Document.passthrough, pk=instance.pk + Document.passthrough.filter(document_type__organization__id=settings.ORGANIZATION_ID), pk=instance.pk ) try: @@ -139,14 +139,15 @@ class DeletedDocumentDeleteManyView(MultipleInstanceActionMixin, DeletedDocument extra_context = { 'title': _('Delete the selected documents?') } - model = DeletedDocument success_message = '%(count)d document deleted.' success_message_plural = '%(count)d documents deleted.' + def get_queryset(self): + return DeletedDocument.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) + class DocumentEditView(SingleObjectEditView): form_class = DocumentForm - model = Document object_permission = permission_document_properties_edit def dispatch(self, request, *args, **kwargs): @@ -162,16 +163,19 @@ class DocumentEditView(SingleObjectEditView): 'title': _('Edit properties of document: %s') % self.get_object(), } - def get_save_extra_data(self): - return { - '_user': self.request.user - } + def get_queryset(self): + return Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) def get_post_action_redirect(self): return reverse( 'documents:document_properties', args=(self.get_object().pk,) ) + def get_save_extra_data(self): + return { + '_user': self.request.user + } + class DocumentRestoreView(ConfirmView): extra_context = { @@ -180,7 +184,7 @@ class DocumentRestoreView(ConfirmView): def object_action(self, instance): source_document = get_object_or_404( - Document.passthrough, pk=instance.pk + Document.passthrough.filter(document_type__organization__id=settings.ORGANIZATION_ID), pk=instance.pk ) try: @@ -210,10 +214,12 @@ class DocumentRestoreManyView(MultipleInstanceActionMixin, DocumentRestoreView): extra_context = { 'title': _('Restore the selected documents?') } - model = DeletedDocument success_message = '%(count)d document restored.' success_message_plural = '%(count)d documents restored.' + def get_queryset(self): + return DeletedDocument.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) + class DocumentPageListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): @@ -232,7 +238,7 @@ class DocumentPageListView(SingleObjectListView): ).dispatch(request, *args, **kwargs) def get_document(self): - return get_object_or_404(Document, pk=self.kwargs['pk']) + return get_object_or_404(Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID), pk=self.kwargs['pk']) def get_queryset(self): return self.get_document().pages.all() @@ -287,11 +293,10 @@ class DocumentPageView(SimpleView): } def get_object(self): - return get_object_or_404(DocumentPage, pk=self.kwargs['pk']) + return get_object_or_404(DocumentPage.objects.filter(document__document_type__organization__pk=settings.ORGANIZATION_ID), pk=self.kwargs['pk']) class DocumentPreviewView(SingleObjectDetailView): - model = Document object_permission = permission_document_view def dispatch(self, request, *args, **kwargs): @@ -309,6 +314,9 @@ class DocumentPreviewView(SingleObjectDetailView): 'title': _('Preview of document: %s') % self.get_object(), } + def get_queryset(self): + return Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) + class DocumentTrashView(ConfirmView): def get_extra_context(self): @@ -318,7 +326,7 @@ class DocumentTrashView(ConfirmView): } def get_object(self): - return get_object_or_404(Document, pk=self.kwargs['pk']) + return get_object_or_404(Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID), pk=self.kwargs['pk']) def get_post_action_redirect(self): return reverse('documents:document_list_recent') @@ -348,7 +356,6 @@ class DocumentTrashView(ConfirmView): class DocumentTrashManyView(MultipleInstanceActionMixin, DocumentTrashView): - model = Document success_message = '%(count)d document moved to the trash.' success_message_plural = '%(count)d documents moved to the trash.' @@ -357,10 +364,13 @@ class DocumentTrashManyView(MultipleInstanceActionMixin, DocumentTrashView): 'title': _('Move the selected documents to the trash?') } + def get_queryset(self): + return Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) + class DocumentTypeDocumentListView(DocumentListView): def get_document_type(self): - return get_object_or_404(DocumentType, pk=self.kwargs['pk']) + return get_object_or_404(DocumentType.objects.filter(organization__id=settings.ORGANIZATION_ID), pk=self.kwargs['pk']) def get_document_queryset(self): return self.get_document_type().documents.all() @@ -374,7 +384,6 @@ class DocumentTypeDocumentListView(DocumentListView): class DocumentTypeListView(SingleObjectListView): - model = DocumentType view_permission = permission_document_type_view def get_extra_context(self): @@ -383,13 +392,15 @@ class DocumentTypeListView(SingleObjectListView): 'title': _('Document types'), } + def get_queryset(self): + return DocumentType.objects.filter(organization__id=settings.ORGANIZATION_ID) + class DocumentTypeCreateView(SingleObjectCreateView): fields = ( 'label', 'trash_time_period', 'trash_time_unit', 'delete_time_period', 'delete_time_unit' ) - model = DocumentType post_action_redirect = reverse_lazy('documents:document_type_list') view_permission = permission_document_type_create @@ -398,9 +409,11 @@ class DocumentTypeCreateView(SingleObjectCreateView): 'title': _('Create document type'), } + def get_queryset(self): + return DocumentType.objects.filter(organization__id=settings.ORGANIZATION_ID) + class DocumentTypeDeleteView(SingleObjectDeleteView): - model = DocumentType post_action_redirect = reverse_lazy('documents:document_type_list') view_permission = permission_document_type_delete @@ -411,13 +424,15 @@ class DocumentTypeDeleteView(SingleObjectDeleteView): 'title': _('Delete the document type: %s?') % self.get_object(), } + def get_queryset(self): + return DocumentType.objects.filter(organization__id=settings.ORGANIZATION_ID) + class DocumentTypeEditView(SingleObjectEditView): fields = ( 'label', 'trash_time_period', 'trash_time_unit', 'delete_time_period', 'delete_time_unit' ) - model = DocumentType post_action_redirect = reverse_lazy('documents:document_type_list') view_permission = permission_document_type_edit @@ -427,13 +442,15 @@ class DocumentTypeEditView(SingleObjectEditView): 'title': _('Edit document type: %s') % self.get_object(), } + def get_queryset(self): + return DocumentType.objects.filter(organization__id=settings.ORGANIZATION_ID) + class DocumentTypeFilenameListView(SingleObjectListView): - model = DocumentType view_permission = permission_document_type_view def get_document_type(self): - return get_object_or_404(DocumentType, pk=self.kwargs['pk']) + return get_object_or_404(DocumentType.objects.filter(organization__id=settings.ORGANIZATION_ID), pk=self.kwargs['pk']) def get_extra_context(self): return { @@ -451,7 +468,6 @@ class DocumentTypeFilenameListView(SingleObjectListView): class DocumentTypeFilenameEditView(SingleObjectEditView): fields = ('enabled', 'filename',) - model = DocumentTypeFilename view_permission = permission_document_type_edit def get_extra_context(self): @@ -476,9 +492,11 @@ class DocumentTypeFilenameEditView(SingleObjectEditView): args=(self.get_object().document_type.pk,) ) + def get_queryset(self): + return DocumentTypeFilename.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) + class DocumentTypeFilenameDeleteView(SingleObjectDeleteView): - model = DocumentTypeFilename view_permission = permission_document_type_edit def get_extra_context(self): @@ -501,6 +519,9 @@ class DocumentTypeFilenameDeleteView(SingleObjectDeleteView): args=(self.get_object().document_type.pk,) ) + def get_queryset(self): + return DocumentTypeFilename.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) + class DocumentVersionListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): @@ -520,7 +541,7 @@ class DocumentVersionListView(SingleObjectListView): ).dispatch(request, *args, **kwargs) def get_document(self): - return get_object_or_404(Document, pk=self.kwargs['pk']) + return get_object_or_404(Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID), pk=self.kwargs['pk']) def get_extra_context(self): return { @@ -533,7 +554,6 @@ class DocumentVersionListView(SingleObjectListView): class DocumentView(SingleObjectDetailView): - model = Document object_permission = permission_document_view def dispatch(self, request, *args, **kwargs): @@ -592,6 +612,9 @@ class DocumentView(SingleObjectDetailView): instance=document, extra_fields=document_fields ) + def get_queryset(self): + return Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) + class EmptyTrashCanView(ConfirmView): extra_context = { @@ -603,7 +626,7 @@ class EmptyTrashCanView(ConfirmView): ) def view_action(self): - for deleted_document in DeletedDocument.objects.all(): + for deleted_document in DeletedDocument.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID): deleted_document.delete() messages.success(self.request, _('Trash emptied successfully')) @@ -622,11 +645,13 @@ class RecentDocumentListView(DocumentListView): def document_document_type_edit(request, document_id=None, document_id_list=None): post_action_redirect = None + queryset = Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) + if document_id: - queryset = Document.objects.filter(pk=document_id) + queryset = queryset.filter(pk=document_id) post_action_redirect = reverse('documents:document_list_recent') elif document_id_list: - queryset = Document.objects.filter(pk__in=document_id_list) + queryset = queryset.filter(pk__in=document_id_list) try: Permission.check_permissions( @@ -700,7 +725,7 @@ def document_multiple_document_type_edit(request): # TODO: Get rid of this view and convert widget to use API and base64 only images def get_document_image(request, document_id, size=setting_preview_size.value): - document = get_object_or_404(Document.passthrough, pk=document_id) + document = get_object_or_404(Document.passthrough.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID), pk=document_id) try: Permission.check_permissions(request.user, (permission_document_view,)) except PermissionDenied: @@ -732,12 +757,14 @@ def get_document_image(request, document_id, size=setting_preview_size.value): def document_download(request, document_id=None, document_id_list=None, document_version_pk=None): previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)))) + queryset = Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) + if document_id: - documents = Document.objects.filter(pk=document_id) + documents = queryset.filter(pk=document_id) elif document_id_list: - documents = Document.objects.filter(pk__in=document_id_list) + documents = queryset.filter(pk__in=document_id_list) elif document_version_pk: - documents = Document.objects.filter( + documents = queryset.filter( pk=get_object_or_404( DocumentVersion, pk=document_version_pk ).document.pk @@ -762,6 +789,7 @@ def document_download(request, document_id=None, document_id_list=None, document ) ) + # TODO: check organization if document_version_pk: queryset = DocumentVersion.objects.filter(pk=document_version_pk) else: @@ -873,10 +901,12 @@ def document_multiple_download(request): def document_update_page_count(request, document_id=None, document_id_list=None): + queryset = Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) + if document_id: - documents = Document.objects.filter(pk=document_id) + documents = queryset.filter(pk=document_id) elif document_id_list: - documents = Document.objects.filter(pk__in=document_id_list) + documents = queryset.objects.filter(pk__in=document_id_list) if not documents: messages.error(request, _('At least one document must be selected.')) @@ -936,11 +966,13 @@ def document_multiple_update_page_count(request): def document_clear_transformations(request, document_id=None, document_id_list=None): + queryset = Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID) + if document_id: - documents = Document.objects.filter(pk=document_id) + documents = queryset.filter(pk=document_id) post_redirect = documents[0].get_absolute_url() elif document_id_list: - documents = Document.objects.filter(pk__in=document_id_list) + documents = queryset.filter(pk__in=document_id_list) post_redirect = None if not documents: @@ -1020,7 +1052,7 @@ def document_page_view_reset(request, document_page_id): def document_page_navigation_next(request, document_page_id): - document_page = get_object_or_404(DocumentPage, pk=document_page_id) + document_page = get_object_or_404(DocumentPage.objects.filter(document__document_type__organization__id=settings.ORGANIZATION_ID), pk=document_page_id) try: Permission.check_permissions(request.user, (permission_document_view,)) @@ -1038,7 +1070,7 @@ def document_page_navigation_next(request, document_page_id): def document_page_navigation_previous(request, document_page_id): - document_page = get_object_or_404(DocumentPage, pk=document_page_id) + document_page = get_object_or_404(DocumentPage.objects.filter(document__document_type__organization__id=settings.ORGANIZATION_ID), pk=document_page_id) try: Permission.check_permissions(request.user, (permission_document_view,)) @@ -1056,7 +1088,7 @@ def document_page_navigation_previous(request, document_page_id): def document_page_navigation_first(request, document_page_id): - document_page = get_object_or_404(DocumentPage, pk=document_page_id) + document_page = get_object_or_404(DocumentPage.objects.filter(document__document_type__organization__id=settings.ORGANIZATION_ID), pk=document_page_id) document_page = get_object_or_404(document_page.siblings, page_number=1) try: @@ -1070,7 +1102,7 @@ def document_page_navigation_first(request, document_page_id): def document_page_navigation_last(request, document_page_id): - document_page = get_object_or_404(DocumentPage, pk=document_page_id) + document_page = get_object_or_404(DocumentPage.objects.filter(document__document_type__organization__id=settings.ORGANIZATION_ID), pk=document_page_id) document_page = get_object_or_404(document_page.siblings, page_number=document_page.siblings.count()) try: @@ -1084,7 +1116,7 @@ def document_page_navigation_last(request, document_page_id): def transform_page(request, document_page_id, zoom_function=None, rotation_function=None): - document_page = get_object_or_404(DocumentPage, pk=document_page_id) + document_page = get_object_or_404(DocumentPage.objects.filter(document__document_type__organization__id=settings.ORGANIZATION_ID), pk=document_page_id) try: Permission.check_permissions(request.user, (permission_document_view,)) @@ -1147,7 +1179,7 @@ def document_page_rotate_left(request, document_page_id): def document_print(request, document_id): - document = get_object_or_404(Document, pk=document_id) + document = get_object_or_404(Document.objects.filter(document_type__organization__id=settings.ORGANIZATION_ID), pk=document_id) try: Permission.check_permissions(request.user, (permission_document_print,)) @@ -1194,7 +1226,7 @@ def document_print(request, document_id): def document_type_filename_create(request, document_type_id): Permission.check_permissions(request.user, (permission_document_type_edit,)) - document_type = get_object_or_404(DocumentType, pk=document_type_id) + document_type = get_object_or_404(DocumentType.objects.filter(organization__id=settings.ORGANIZATION_ID), pk=document_type_id) if request.method == 'POST': form = DocumentTypeFilenameForm_create(request.POST) @@ -1240,7 +1272,7 @@ def document_clear_image_cache(request): def document_version_revert(request, document_version_pk): - document_version = get_object_or_404(DocumentVersion, pk=document_version_pk) + document_version = get_object_or_404(DocumentVersion.objects.filter(document__document_type__organization__id=settings.ORGANIZATION_ID), pk=document_version_pk) try: Permission.check_permissions(request.user, (permission_document_version_revert,)) diff --git a/mayan/apps/folders/forms.py b/mayan/apps/folders/forms.py index e0a4b991c7..c1cc6fda1f 100644 --- a/mayan/apps/folders/forms.py +++ b/mayan/apps/folders/forms.py @@ -21,7 +21,7 @@ class FolderListForm(forms.Form): logger.debug('user: %s', user) super(FolderListForm, self).__init__(*args, **kwargs) - queryset = Folder.objects.all() + queryset = Folder.on_organization.all() try: Permission.check_permissions(user, (permission_folder_view,)) except PermissionDenied: diff --git a/mayan/apps/folders/migrations/0005_folder_organization.py b/mayan/apps/folders/migrations/0005_folder_organization.py new file mode 100644 index 0000000000..4715175d72 --- /dev/null +++ b/mayan/apps/folders/migrations/0005_folder_organization.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import organizations.shortcuts + + +class Migration(migrations.Migration): + + dependencies = [ + ('organizations', '0001_initial'), + ('folders', '0004_documentfolder'), + ] + + operations = [ + migrations.AddField( + model_name='folder', + name='organization', + field=models.ForeignKey(default=organizations.shortcuts.get_current_organization, to='organizations.Organization'), + preserve_default=True, + ), + ] diff --git a/mayan/apps/folders/models.py b/mayan/apps/folders/models.py index a5e2a83d58..4db0235042 100644 --- a/mayan/apps/folders/models.py +++ b/mayan/apps/folders/models.py @@ -10,6 +10,9 @@ from django.utils.translation import ugettext_lazy as _ from acls.models import AccessControlList from documents.models import Document from documents.permissions import permission_document_view +from organizations.models import Organization +from organizations.managers import CurrentOrganizationManager +from organizations.shortcuts import get_current_organization from permissions import Permission from .managers import FolderManager @@ -17,6 +20,9 @@ from .managers import FolderManager @python_2_unicode_compatible class Folder(models.Model): + organization = models.ForeignKey( + Organization, default=get_current_organization + ) label = models.CharField( db_index=True, max_length=128, verbose_name=_('Label') ) @@ -29,6 +35,7 @@ class Folder(models.Model): ) objects = FolderManager() + on_organization = CurrentOrganizationManager() def __str__(self): return self.label diff --git a/mayan/apps/folders/views.py b/mayan/apps/folders/views.py index 796fe8d6d1..81f84d65aa 100644 --- a/mayan/apps/folders/views.py +++ b/mayan/apps/folders/views.py @@ -33,7 +33,6 @@ logger = logging.getLogger(__name__) class FolderEditView(SingleObjectEditView): fields = ('label',) - model = Folder object_permission = permission_folder_edit post_action_redirect = reverse_lazy('folders:folder_list') @@ -43,6 +42,9 @@ class FolderEditView(SingleObjectEditView): 'title': _('Edit folder: %s') % self.get_object(), } + def get_document_queryset(self): + return Folder.on_organization.all() + class FolderListView(SingleObjectListView): object_permission = permission_folder_view @@ -54,7 +56,7 @@ class FolderListView(SingleObjectListView): } def get_folder_queryset(self): - return Folder.objects.all() + return Folder.on_organization.all() def get_queryset(self): self.queryset = self.get_folder_queryset() @@ -63,7 +65,6 @@ class FolderListView(SingleObjectListView): class FolderCreateView(SingleObjectCreateView): fields = ('label',) - model = Folder view_permission = permission_folder_create def form_valid(self, form): @@ -90,9 +91,12 @@ class FolderCreateView(SingleObjectCreateView): 'title': _('Create folder'), } + def get_queryset(self): + return Folder.on_organization.all() + def folder_delete(request, folder_id): - folder = get_object_or_404(Folder, pk=folder_id) + folder = get_object_or_404(Folder.on_organization, pk=folder_id) try: Permission.check_permissions(request.user, (permission_folder_delete,)) @@ -142,7 +146,7 @@ class FolderDetailView(DocumentListView): } def get_folder(self): - folder = get_object_or_404(Folder, pk=self.kwargs['pk']) + folder = get_object_or_404(Folder.on_organization, pk=self.kwargs['pk']) try: Permission.check_permissions( @@ -267,7 +271,7 @@ class DocumentFolderListView(FolderListView): def folder_document_remove(request, folder_id, document_id=None, document_id_list=None): post_action_redirect = None - folder = get_object_or_404(Folder, pk=folder_id) + folder = get_object_or_404(Folder.on_organization, pk=folder_id) if document_id: queryset = Document.objects.filter(pk=document_id) diff --git a/mayan/apps/organizations/__init__.py b/mayan/apps/organizations/__init__.py new file mode 100644 index 0000000000..6167f9bfee --- /dev/null +++ b/mayan/apps/organizations/__init__.py @@ -0,0 +1,3 @@ +from __future__ import unicode_literals + +default_app_config = 'organizations.apps.OrganizationApp' diff --git a/mayan/apps/organizations/admin.py b/mayan/apps/organizations/admin.py new file mode 100644 index 0000000000..3581b8c36d --- /dev/null +++ b/mayan/apps/organizations/admin.py @@ -0,0 +1,11 @@ +from __future__ import unicode_literals + +from django.contrib import admin + +from .models import Organization + + +@admin.register(Organization) +class OrganizationAdmin(admin.ModelAdmin): + list_display = ('label',) + search_fields = ('label',) diff --git a/mayan/apps/organizations/apps.py b/mayan/apps/organizations/apps.py new file mode 100644 index 0000000000..847c307836 --- /dev/null +++ b/mayan/apps/organizations/apps.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + +from django.utils.translation import ugettext_lazy as _ + +from common.apps import MayanAppConfig + + +class OrganizationApp(AppConfig): + name = 'organizations' + verbose_name = _('Organizations') diff --git a/mayan/apps/organizations/management.py b/mayan/apps/organizations/management.py new file mode 100644 index 0000000000..da47183bd6 --- /dev/null +++ b/mayan/apps/organizations/management.py @@ -0,0 +1,43 @@ +""" +Creates the default Organization object. +""" + +from django.apps import apps +from django.core.management.color import no_style +from django.db import DEFAULT_DB_ALIAS, connections, router +from django.db.models import signals + + +def create_default_organization(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): + try: + Organization = apps.get_model('organizations', 'Organization') + except LookupError: + return + + if not router.allow_migrate(using, Organization): + return + + if not Organization.objects.using(using).exists(): + # The default settings set ORGANIZATION_ID = 1, and some tests in Django's test + # suite rely on this value. However, if database sequences are reused + # (e.g. in the test suite after flush/syncdb), it isn't guaranteed that + # the next id will be 1, so we coerce it. See #15573 and #16353. This + # can also crop up outside of tests - see #15346. + if verbosity >= 2: + print("Creating default Organization object") + Organization(pk=1, label='Default').save(using=using) + + # We set an explicit pk instead of relying on auto-incrementation, + # so we need to reset the database sequence. See #17415. + sequence_sql = connections[using].ops.sequence_reset_sql(no_style(), [Organization]) + if sequence_sql: + if verbosity >= 2: + print('Resetting sequence') + with connections[using].cursor() as cursor: + for command in sequence_sql: + cursor.execute(command) + + Organization.objects.clear_cache() + + +signals.post_migrate.connect(create_default_organization, sender=apps.get_app_config('organizations')) diff --git a/mayan/apps/organizations/managers.py b/mayan/apps/organizations/managers.py new file mode 100644 index 0000000000..48c9c31981 --- /dev/null +++ b/mayan/apps/organizations/managers.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.core import checks +from django.db import models +from django.db.models.fields import FieldDoesNotExist + + +class CurrentOrganizationManager(models.Manager): + "Use this to limit objects to those associated with the current organization." + + def __init__(self, field_name=None): + super(CurrentOrganizationManager, self).__init__() + self.__field_name = field_name + + def check(self, **kwargs): + errors = super(CurrentOrganizationManager, self).check(**kwargs) + errors.extend(self._check_field_name()) + return errors + + def _check_field_name(self): + field_name = self._get_field_name() + try: + field = self.model._meta.get_field(field_name) + except FieldDoesNotExist: + return [ + checks.Error( + "CurrentOrganizationManager could not find a field named '%s'." % field_name, + hint=None, + obj=self, + id='organizations.E001', + ) + ] + + if not isinstance(field, (models.ForeignKey, models.ManyToManyField)): + return [ + checks.Error( + "CurrentOrganizationManager cannot use '%s.%s' as it is not a ForeignKey or ManyToManyField." % ( + self.model._meta.object_name, field_name + ), + hint=None, + obj=self, + id='organizations.E002', + ) + ] + + return [] + + def _get_field_name(self): + """ Return self.__field_name or 'organization' or 'organizations'. """ + + if not self.__field_name: + try: + self.model._meta.get_field('organization') + except FieldDoesNotExist: + self.__field_name = 'organizations' + else: + self.__field_name = 'organization' + return self.__field_name + + def get_queryset(self): + return super(CurrentOrganizationManager, self).get_queryset().filter( + **{self._get_field_name() + '__id': settings.ORGANIZATION_ID}) diff --git a/mayan/apps/organizations/middleware.py b/mayan/apps/organizations/middleware.py new file mode 100644 index 0000000000..d46001546e --- /dev/null +++ b/mayan/apps/organizations/middleware.py @@ -0,0 +1,10 @@ +from .models import Organization + + +class CurrentOrganizationMiddleware(object): + """ + Middleware that sets `organization` attribute to request object. + """ + + def process_request(self, request): + request.organization = Organization.objects.get_current() diff --git a/mayan/apps/organizations/migrations/0001_initial.py b/mayan/apps/organizations/migrations/0001_initial.py new file mode 100644 index 0000000000..2c0b1e6246 --- /dev/null +++ b/mayan/apps/organizations/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Organization', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('label', models.CharField(max_length=50, verbose_name='Label')), + ], + options={ + 'ordering': ('label',), + 'verbose_name': 'Organization', + 'verbose_name_plural': 'Organizations', + }, + bases=(models.Model,), + ), + ] diff --git a/mayan/apps/organizations/migrations/__init__.py b/mayan/apps/organizations/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mayan/apps/organizations/models.py b/mayan/apps/organizations/models.py new file mode 100644 index 0000000000..93b0df81ce --- /dev/null +++ b/mayan/apps/organizations/models.py @@ -0,0 +1,71 @@ +from __future__ import unicode_literals + +import string +import warnings + +from django.core.exceptions import ImproperlyConfigured, ValidationError +from django.db import models +from django.db.models.signals import pre_save, pre_delete +from django.utils.deprecation import RemovedInDjango19Warning +from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import ugettext_lazy as _ + +ORGANIZATION_CACHE = {} + + +class OrganizationManager(models.Manager): + def get_current(self): + """ + Returns the current ``Organization`` based on the ORGANIZATION_ID in + the project's settings. The ``Organization`` object is cached the first + time it's retrieved from the database. + """ + from django.conf import settings + try: + oid = settings.ORGANIZATION_ID + except AttributeError: + raise ImproperlyConfigured( + "You're using the Django \"organizations framework\" without " + "having set the ORGANIZATION_ID setting. Create a site in " + "your database and set the SITE_ID setting to fix this error." + ) + try: + current_organization = ORGANIZATION_CACHE[oid] + except KeyError: + current_organization = self.get(pk=oid) + ORGANIZATION_CACHE[oid] = current_organization + return current_organization + + def clear_cache(self): + """Clears the ``Organization`` object cache.""" + global ORGANIZATION_CACHE + ORGANIZATION_CACHE = {} + + +@python_2_unicode_compatible +class Organization(models.Model): + label = models.CharField(max_length=50, verbose_name=_('Label')) + objects = OrganizationManager() + + class Meta: + verbose_name = _('Organization') + verbose_name_plural = _('Organizations') + ordering = ('label',) + + def __str__(self): + return self.label + + +def clear_organization_cache(sender, **kwargs): + """ + Clears the cache (if primed) each time a organization is saved or deleted + """ + instance = kwargs['instance'] + try: + del ORGANIZATION_CACHE[instance.pk] + except KeyError: + pass + + +pre_save.connect(clear_organization_cache, sender=Organization) +pre_delete.connect(clear_organization_cache, sender=Organization) diff --git a/mayan/apps/organizations/shortcuts.py b/mayan/apps/organizations/shortcuts.py new file mode 100644 index 0000000000..84480ac072 --- /dev/null +++ b/mayan/apps/organizations/shortcuts.py @@ -0,0 +1,8 @@ +from __future__ import unicode_literals + +from django.apps import apps + + +def get_current_organization(): + from .models import Organization + return Organization.objects.get_current().pk diff --git a/mayan/apps/organizations/tests.py b/mayan/apps/organizations/tests.py new file mode 100644 index 0000000000..cec5350d56 --- /dev/null +++ b/mayan/apps/organizations/tests.py @@ -0,0 +1,149 @@ +from __future__ import unicode_literals + +import unittest + +from django.apps import apps +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist, ValidationError +from django.db import connections, router +from django.http import HttpRequest +from django.test import TestCase, modify_settings, override_settings + +from .management import create_default_organization +from .middleware import CurrentOrganizationMiddleware +from .models import Organization +from .shortcuts import get_current_organization + + +class OrganizationsFrameworkTests(TestCase): + + def setUp(self): + Organization(id=settings.ORGANIZATION_ID, domain="example.com", name="example.com").save() + + def test_organization_manager(self): + # Make sure that get_current() does not return a deleted Organization object. + s = Organization.objects.get_current() + self.assertTrue(isinstance(s, Organization)) + s.delete() + self.assertRaises(ObjectDoesNotExist, Organization.objects.get_current) + + def test_organization_cache(self): + # After updating a Organization object (e.g. via the admin), we shouldn't return a + # bogus value from the ORGANIZATION_CACHE. + organization = Organization.objects.get_current() + self.assertEqual("example.com", organization.name) + s2 = Organization.objects.get(id=settings.ORGANIZATION_ID) + s2.name = "Example organization" + s2.save() + organization = Organization.objects.get_current() + self.assertEqual("Example organization", organization.name) + + def test_delete_all_organizations_clears_cache(self): + # When all organization objects are deleted the cache should also + # be cleared and get_current() should raise a DoesNotExist. + self.assertIsInstance(Organization.objects.get_current(), Organization) + Organization.objects.all().delete() + self.assertRaises(Organization.DoesNotExist, Organization.objects.get_current) + + @override_settings(ALLOWED_HOSTS=['example.com']) + def test_get_current_organization(self): + # Test that the correct Organization object is returned + request = HttpRequest() + request.META = { + "SERVER_NAME": "example.com", + "SERVER_PORT": "80", + } + organization = get_current_organization(request) + self.assertTrue(isinstance(organization, Organization)) + self.assertEqual(organization.id, settings.ORGANIZATION_ID) + + # Test that an exception is raised if the organizations framework is installed + # but there is no matching Organization + organization.delete() + self.assertRaises(ObjectDoesNotExist, get_current_organization, request) + + # A RequestOrganization is returned if the organizations framework is not installed + with self.modify_settings(INSTALLED_APPS={'remove': 'django.contrib.organizations'}): + organization = get_current_organization(request) + self.assertTrue(isinstance(organization, RequestOrganization)) + self.assertEqual(organization.name, "example.com") + + def test_domain_name_with_whitespaces(self): + # Regression for #17320 + # Domain names are not allowed contain whitespace characters + organization = Organization(name="test name", domain="test test") + self.assertRaises(ValidationError, organization.full_clean) + organization.domain = "test\ttest" + self.assertRaises(ValidationError, organization.full_clean) + organization.domain = "test\ntest" + self.assertRaises(ValidationError, organization.full_clean) + + +class JustOtherRouter(object): + def allow_migrate(self, db, model): + return db == 'other' + + +@modify_settings(INSTALLED_APPS={'append': 'django.contrib.organizations'}) +class CreateDefaultOrganizationTests(TestCase): + multi_db = True + + def setUp(self): + self.app_config = apps.get_app_config('organizations') + # Delete the organization created as part of the default migration process. + Organization.objects.all().delete() + + def test_basic(self): + """ + #15346, #15573 - create_default_organization() creates an example organization only if + none exist. + """ + create_default_organization(self.app_config, verbosity=0) + self.assertEqual(Organization.objects.count(), 1) + + create_default_organization(self.app_config, verbosity=0) + self.assertEqual(Organization.objects.count(), 1) + + @unittest.skipIf('other' not in connections, "Requires 'other' database connection.") + def test_multi_db_with_router(self): + """ + #16353, #16828 - The default organization creation should respect db routing. + """ + old_routers = router.routers + router.routers = [JustOtherRouter()] + try: + create_default_organization(self.app_config, using='default', verbosity=0) + create_default_organization(self.app_config, using='other', verbosity=0) + self.assertFalse(Organization.objects.using('default').exists()) + self.assertTrue(Organization.objects.using('other').exists()) + finally: + router.routers = old_routers + + @unittest.skipIf('other' not in connections, "Requires 'other' database connection.") + def test_multi_db(self): + create_default_organization(self.app_config, using='default', verbosity=0) + create_default_organization(self.app_config, using='other', verbosity=0) + self.assertTrue(Organization.objects.using('default').exists()) + self.assertTrue(Organization.objects.using('other').exists()) + + def test_save_another(self): + """ + #17415 - Another organization can be created right after the default one. + + On some backends the sequence needs to be reset after saving with an + explicit ID. Test that there isn't a sequence collisions by saving + another organization. This test is only meaningful with databases that use + sequences for automatic primary keys such as PostgreSQL and Oracle. + """ + create_default_organization(self.app_config, verbosity=0) + Organization(domain='example2.com', name='example2.com').save() + + +class MiddlewareTest(TestCase): + + def test_request(self): + """ Makes sure that the request has correct `organization` attribute. """ + middleware = CurrentOrganizationMiddleware() + request = HttpRequest() + middleware.process_request(request) + self.assertEqual(request.organization.id, settings.ORGANIZATION_ID) diff --git a/mayan/apps/tags/forms.py b/mayan/apps/tags/forms.py index e8b70b496f..77820e95fe 100644 --- a/mayan/apps/tags/forms.py +++ b/mayan/apps/tags/forms.py @@ -21,7 +21,7 @@ class TagListForm(forms.Form): logger.debug('user: %s', user) super(TagListForm, self).__init__(*args, **kwargs) - queryset = Tag.objects.all() + queryset = Tag.on_organization.all() try: Permission.check_permissions(user, (permission_tag_view,)) except PermissionDenied: diff --git a/mayan/apps/tags/migrations/0007_tag_organization.py b/mayan/apps/tags/migrations/0007_tag_organization.py new file mode 100644 index 0000000000..cd7fbd7b55 --- /dev/null +++ b/mayan/apps/tags/migrations/0007_tag_organization.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import organizations.shortcuts + + +class Migration(migrations.Migration): + + dependencies = [ + ('organizations', '0001_initial'), + ('tags', '0006_documenttag'), + ] + + operations = [ + migrations.AddField( + model_name='tag', + name='organization', + field=models.ForeignKey(default=organizations.shortcuts.get_current_organization, to='organizations.Organization'), + preserve_default=True, + ), + ] diff --git a/mayan/apps/tags/models.py b/mayan/apps/tags/models.py index 941654ffca..3b0a265437 100644 --- a/mayan/apps/tags/models.py +++ b/mayan/apps/tags/models.py @@ -11,11 +11,17 @@ from colorful.fields import RGBColorField from acls.models import AccessControlList from documents.models import Document from documents.permissions import permission_document_view +from organizations.models import Organization +from organizations.managers import CurrentOrganizationManager +from organizations.shortcuts import get_current_organization from permissions import Permission @python_2_unicode_compatible class Tag(models.Model): + organization = models.ForeignKey( + Organization, default=get_current_organization + ) label = models.CharField( db_index=True, max_length=128, unique=True, verbose_name=_('Label') ) @@ -24,6 +30,9 @@ class Tag(models.Model): Document, related_name='tags', verbose_name=_('Documents') ) + objects = models.Manager() + on_organization = CurrentOrganizationManager() + def __str__(self): return self.label diff --git a/mayan/apps/tags/views.py b/mayan/apps/tags/views.py index 32e8234830..bfd48a33c0 100644 --- a/mayan/apps/tags/views.py +++ b/mayan/apps/tags/views.py @@ -33,10 +33,12 @@ logger = logging.getLogger(__name__) class TagCreateView(SingleObjectCreateView): extra_context = {'title': _('Create tag')} fields = ('label', 'color') - model = Tag post_action_redirect = reverse_lazy('tags:tag_list') view_permission = permission_tag_create + def get_queryset(self): + return Tag.on_organization.all() + def tag_attach(request, document_id=None, document_id_list=None): if document_id: @@ -144,17 +146,18 @@ class TagListView(SingleObjectListView): return super(TagListView, self).get_queryset() def get_tag_queryset(self): - return Tag.objects.all() + return Tag.on_organization.all() def tag_delete(request, tag_id=None, tag_id_list=None): post_action_redirect = None + queryset = Tag.on_organization.all() if tag_id: - queryset = Tag.objects.filter(pk=tag_id) + queryset = organization.filter(pk=tag_id) post_action_redirect = reverse('tags:tag_list') elif tag_id_list: - queryset = Tag.objects.filter(pk__in=tag_id_list) + queryset = organization.filter(pk__in=tag_id_list) if not queryset: messages.error(request, _('Must provide at least one tag.')) @@ -221,7 +224,6 @@ def tag_multiple_delete(request): class TagEditView(SingleObjectEditView): fields = ('label', 'color') - model = Tag object_permission = permission_tag_edit post_action_redirect = reverse_lazy('tags:tag_list') @@ -231,10 +233,13 @@ class TagEditView(SingleObjectEditView): 'title': _('Edit tag: %s') % self.get_object(), } + def get_queryset(self): + return Tag.on_organization.all() + class TagTaggedItemListView(DocumentListView): def get_tag(self): - return get_object_or_404(Tag, pk=self.kwargs['pk']) + return get_object_or_404(Tag.on_organization, pk=self.kwargs['pk']) def get_document_queryset(self): return self.get_tag().documents.all() @@ -309,17 +314,20 @@ def tag_remove(request, document_id=None, document_id_list=None, tag_id=None, ta } template = 'appearance/generic_confirm.html' + + queryset = Tag.on_organization.all() + if tag_id: - tags = Tag.objects.filter(pk=tag_id) + tags = queryset.filter(pk=tag_id) elif tag_id_list: - tags = Tag.objects.filter(pk__in=tag_id_list) + tags = queryset.filter(pk__in=tag_id_list) else: template = 'appearance/generic_form.html' if request.method == 'POST': form = TagListForm(request.POST, user=request.user) if form.is_valid(): - tags = Tag.objects.filter(pk=form.cleaned_data['tag'].pk) + tags = Tag.on_organization.filter(pk=form.cleaned_data['tag'].pk) else: if not tag_id and not tag_id_list: form = TagListForm(user=request.user) diff --git a/mayan/settings/base.py b/mayan/settings/base.py index b10c746d48..f5fc541218 100644 --- a/mayan/settings/base.py +++ b/mayan/settings/base.py @@ -73,6 +73,7 @@ INSTALLED_APPS = ( 'lock_manager', 'mimetype', 'navigation', + 'organizations', 'permissions', 'smart_settings', 'user_management', @@ -113,6 +114,7 @@ MIDDLEWARE_CLASSES = ( 'common.middleware.strip_spaces_widdleware.SpacelessMiddleware', 'authentication.middleware.login_required_middleware.LoginRequiredMiddleware', 'common.middleware.ajax_redirect.AjaxRedirect', + 'organizations.middleware.CurrentOrganizationMiddleware', ) ROOT_URLCONF = 'mayan.urls' @@ -284,3 +286,5 @@ SWAGGER_SETTINGS = { # ------ Timezone -------- TIMEZONE_COOKIE_NAME = 'django_timezone' TIMEZONE_SESSION_KEY = 'django_timezone' +# ------ Organization ------- +ORGANIZATION_ID = 1