diff --git a/kobo/apps/organizations/admin/__init__.py b/kobo/apps/organizations/admin/__init__.py
index 5a2df4ebad..fe3509bf1f 100644
--- a/kobo/apps/organizations/admin/__init__.py
+++ b/kobo/apps/organizations/admin/__init__.py
@@ -1,6 +1,5 @@
from .organization import OrgAdmin
-from .organization_invite import OrgInvitationAdmin
from .organization_owner import OrgOwnerAdmin
from .organization_user import OrgUserAdmin
-__all__ = ['OrgAdmin', 'OrgOwnerAdmin', 'OrgInvitationAdmin', 'OrgUserAdmin']
+__all__ = ['OrgAdmin', 'OrgOwnerAdmin', 'OrgUserAdmin']
diff --git a/kobo/apps/organizations/admin/organization.py b/kobo/apps/organizations/admin/organization.py
index bd0f64a856..d8c8f354e8 100644
--- a/kobo/apps/organizations/admin/organization.py
+++ b/kobo/apps/organizations/admin/organization.py
@@ -1,5 +1,6 @@
from django.contrib import admin, messages
from django.db.models import Count
+from django.urls import reverse
from django.utils.safestring import mark_safe
from organizations.base_admin import BaseOrganizationAdmin
@@ -9,7 +10,7 @@
from ..tasks import transfer_user_ownership_to_org
from ..utils import revoke_org_asset_perms
from .organization_owner import OwnerInline
-from .organization_user import OrgUserInline
+from .organization_user import OrgUserInline, max_users_for_edit_mode
@admin.register(Organization)
@@ -17,7 +18,34 @@ class OrgAdmin(BaseOrganizationAdmin):
inlines = [OwnerInline, OrgUserInline]
view_on_site = False
readonly_fields = ['id']
- fields = ['id', 'name', 'slug', 'is_active', 'mmo_override']
+ fields = ['id', 'name', 'mmo_override']
+ search_fields = ['name']
+
+ # parent overrides
+ list_display = ['name']
+ list_filter = ()
+ prepopulated_fields = {}
+
+ def change_view(self, request, object_id, form_url='', extra_context=None):
+ organization = self.get_object(request, object_id)
+ if (
+ organization
+ and organization.organization_users.count() > max_users_for_edit_mode()
+ and request.method == 'GET'
+ ):
+ link = reverse('admin:organizations_organizationuser_changelist')
+ message = (
+ f'Note: Adding/Editing/Removing users is disabled on this page due '
+ f'to the size of the organization. Please use the Import/Export '
+ f'feature available in the Organization Users '
+ f'section instead.'
+ )
+ self.message_user(
+ request,
+ mark_safe(message),
+ level=messages.WARNING,
+ )
+ return super().change_view(request, object_id, form_url, extra_context)
def save_related(self, request, form, formsets, change):
super().save_related(request, form, formsets, change)
diff --git a/kobo/apps/organizations/admin/organization_invite.py b/kobo/apps/organizations/admin/organization_invite.py
deleted file mode 100644
index 87751c72ba..0000000000
--- a/kobo/apps/organizations/admin/organization_invite.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.contrib import admin
-
-from ..models import OrganizationInvitation
-
-
-@admin.register(OrganizationInvitation)
-class OrgInvitationAdmin(admin.ModelAdmin):
- pass
diff --git a/kobo/apps/organizations/admin/organization_user.py b/kobo/apps/organizations/admin/organization_user.py
index 99a556312f..e4c106c61e 100644
--- a/kobo/apps/organizations/admin/organization_user.py
+++ b/kobo/apps/organizations/admin/organization_user.py
@@ -18,13 +18,13 @@
from ..utils import revoke_org_asset_perms
-def _max_users_for_edit_mode():
+def max_users_for_edit_mode():
"""
This function represents an arbitrary limit
to prevent the form's POST request from exceeding
`settings.DATA_UPLOAD_MAX_NUMBER_FIELDS`.
"""
- return settings.DATA_UPLOAD_MAX_NUMBER_FIELDS // 3
+ return int(settings.DATA_UPLOAD_MAX_NUMBER_FIELDS * 0.4)
class OrgUserInlineFormSet(forms.models.BaseInlineFormSet):
@@ -32,7 +32,7 @@ def clean(self):
if self.is_valid():
members = 0
users = []
- if len(self.forms) >= _max_users_for_edit_mode():
+ if len(self.forms) > max_users_for_edit_mode():
return
for form in self.forms:
@@ -63,9 +63,17 @@ def clean(self):
)
+class OrgUserInlineForm(forms.ModelForm):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ if self.instance.pk:
+ self.fields['user'].disabled = True
+
+
class OrgUserInline(admin.StackedInline):
model = OrganizationUser
formset = OrgUserInlineFormSet
+ form = OrgUserInlineForm
raw_id_fields = ('user',)
view_on_site = False
extra = 0
@@ -85,8 +93,8 @@ def get_readonly_fields(self, request, obj=None):
if not obj:
return []
- if obj.organization_users.count() >= _max_users_for_edit_mode():
- return ['user', 'is_admin']
+ if obj.organization_users.count() > max_users_for_edit_mode():
+ return ['is_admin']
return []
@@ -94,13 +102,13 @@ def has_add_permission(self, request, obj=None):
if not obj:
return True
- return obj.organization_users.count() < _max_users_for_edit_mode()
+ return obj.organization_users.count() <= max_users_for_edit_mode()
def has_delete_permission(self, request, obj=None):
if not obj:
return True
- return obj.organization_users.count() < _max_users_for_edit_mode()
+ return obj.organization_users.count() <= max_users_for_edit_mode()
class OrgUserResource(resources.ModelResource):
@@ -156,6 +164,13 @@ class OrgUserAdmin(ImportExportModelAdmin, BaseOrganizationUserAdmin):
search_fields = ('user__username',)
autocomplete_fields = ['user', 'organization']
form = OrgUserAdminForm
+ view_on_site = False
+
+ def has_add_permission(self, request):
+ return False
+
+ def has_change_permission(self, request, obj=None):
+ return False
def get_search_results(self, request, queryset, search_term):
auto_complete = request.path == '/admin/autocomplete/'
diff --git a/kobo/apps/organizations/models.py b/kobo/apps/organizations/models.py
index 062f42e1ae..9b9277ed61 100644
--- a/kobo/apps/organizations/models.py
+++ b/kobo/apps/organizations/models.py
@@ -49,7 +49,8 @@ class OrganizationType(models.TextChoices):
class Organization(AbstractOrganization):
id = KpiUidField(uid_prefix='org', primary_key=True)
mmo_override = models.BooleanField(
- default=False, verbose_name='Multi-members override'
+ default=False,
+ verbose_name='Make organization multi-member (necessary for adding users)'
)
website = models.CharField(default='', max_length=255)
organization_type = models.CharField(
@@ -235,7 +236,7 @@ def owner_user_object(self) -> 'User':
class OrganizationUser(AbstractOrganizationUser):
def __str__(self):
- return f''
+ return f'{self.user.username} (#{self.pk})'
@property
def active_subscription_statuses(self):