diff --git a/src/servala/core/crd.py b/src/servala/core/crd.py index fe8edbb..276e9c2 100644 --- a/src/servala/core/crd.py +++ b/src/servala/core/crd.py @@ -328,8 +328,9 @@ class CrdModelFormMixin: field.required = False # Mark advanced fields with a CSS class and data attribute + advanced_fields = getattr(self, "ADVANCED_FIELDS", []) for name, field in self.fields.items(): - if self.is_field_advanced(name): + if name in advanced_fields: field.widget.attrs.update( { "class": ( @@ -355,17 +356,6 @@ class CrdModelFormMixin: return True return False - def is_field_advanced(self, field_name): - advanced_fields = getattr(self, "ADVANCED_FIELDS", []) - return field_name in advanced_fields or any( - field_name.startswith(f"{af}.") for af in advanced_fields - ) - - def are_all_fields_advanced(self, field_list): - if not field_list: - return False - return all(self.is_field_advanced(field_name) for field_name in field_list) - def get_fieldsets(self): fieldsets = [] @@ -381,7 +371,6 @@ class CrdModelFormMixin: "fields": general_fields, "fieldsets": [], "has_mandatory": self.has_mandatory_fields(general_fields), - "is_advanced": self.are_all_fields_advanced(general_fields), } if all( [ @@ -448,9 +437,6 @@ class CrdModelFormMixin: title = f"{fieldset['title']}: {sub_fieldset['title']}: " for field in sub_fieldset["fields"]: self.strip_title(field, title) - sub_fieldset["is_advanced"] = self.are_all_fields_advanced( - sub_fieldset["fields"] - ) nested_fieldsets_list.append(sub_fieldset) fieldset["fieldsets"] = nested_fieldsets_list @@ -467,8 +453,6 @@ class CrdModelFormMixin: all_fields.extend(sub_fieldset["fields"]) fieldset["has_mandatory"] = self.has_mandatory_fields(all_fields) - fieldset["is_advanced"] = self.are_all_fields_advanced(all_fields) - fieldsets.append(fieldset) # Add 'others' tab if there are any fields @@ -479,7 +463,6 @@ class CrdModelFormMixin: "fields": others, "fieldsets": [], "has_mandatory": self.has_mandatory_fields(others), - "is_advanced": self.are_all_fields_advanced(others), } ) diff --git a/src/servala/core/models/organization.py b/src/servala/core/models/organization.py index bbcc16f..1669f39 100644 --- a/src/servala/core/models/organization.py +++ b/src/servala/core/models/organization.py @@ -2,7 +2,6 @@ import secrets import rules import urlman -from auditlog.registry import auditlog from django.conf import settings from django.contrib.sites.shortcuts import get_current_site from django.core.mail import send_mail @@ -468,7 +467,6 @@ class OrganizationInvitation(ServalaModelMixin, models.Model): class urls(urlman.Urls): accept = "/invitations/{self.secret}/accept/" - delete = "{self.organization.urls.details}invitations/{self.pk}/delete/" class Meta: verbose_name = _("Organization invitation") @@ -538,7 +536,3 @@ The Servala Team""" recipient_list=[self.email], fail_silently=False, ) - - -auditlog.register(OrganizationInvitation, serialize_data=True) -auditlog.register(OrganizationMembership, serialize_data=True) diff --git a/src/servala/core/rules.py b/src/servala/core/rules.py index e1a0992..cf4dc1c 100644 --- a/src/servala/core/rules.py +++ b/src/servala/core/rules.py @@ -14,26 +14,20 @@ def has_organization_role(user, org, roles): @rules.predicate def is_organization_owner(user, obj): - from servala.core.models.organization import OrganizationRole - if hasattr(obj, "organization"): org = obj.organization else: org = obj - return has_organization_role(user, org, [OrganizationRole.OWNER]) + return has_organization_role(user, org, ["owner"]) @rules.predicate def is_organization_admin(user, obj): - from servala.core.models.organization import OrganizationRole - if hasattr(obj, "organization"): org = obj.organization else: org = obj - return has_organization_role( - user, org, [OrganizationRole.OWNER, OrganizationRole.ADMIN] - ) + return has_organization_role(user, org, ["owner", "admin"]) @rules.predicate diff --git a/src/servala/frontend/templates/frontend/forms/dynamic_array.html b/src/servala/frontend/templates/frontend/forms/dynamic_array.html index 9d61825..4b7e68c 100644 --- a/src/servala/frontend/templates/frontend/forms/dynamic_array.html +++ b/src/servala/frontend/templates/frontend/forms/dynamic_array.html @@ -1,9 +1,6 @@
+ data-name="{{ widget.name }}">
{% for item in value_list %}
diff --git a/src/servala/frontend/templates/frontend/organizations/update.html b/src/servala/frontend/templates/frontend/organizations/update.html index 73c2c69..d55dc56 100644 --- a/src/servala/frontend/templates/frontend/organizations/update.html +++ b/src/servala/frontend/templates/frontend/organizations/update.html @@ -67,66 +67,43 @@
-{% endpartialdef members-list %} -{% partialdef pending-invitations-card %} {% if pending_invitations %} -
-
-

- {% translate "Pending Invitations" %} -

-
-
-
-
- - - - - - - - - - - {% for invitation in pending_invitations %} - - - - - - - {% endfor %} - -
{% translate "Email" %}{% translate "Role" %}{% translate "Sent" %}{% translate "Actions" %}
{{ invitation.email }} - - {{ invitation.get_role_display }} - - {{ invitation.created_at|date:"Y-m-d H:i" }} - -
- {% csrf_token %} - - -
-
-
-
-
+
+ {% translate "Pending Invitations" %} +
+
+ + + + + + + + + + + {% for invitation in pending_invitations %} + + + + + + + {% endfor %} + +
{% translate "Email" %}{% translate "Role" %}{% translate "Sent" %}{% translate "Link" %}
{{ invitation.email }} + + {{ invitation.get_role_display }} + + {{ invitation.created_at|date:"Y-m-d H:i" }} + +
{% endif %} -{% endpartialdef pending-invitations-card %} +{% endpartialdef members-list %} {% block content %}
@@ -237,7 +214,6 @@
{% partial members-list %}
-
{% partial pending-invitations-card %}

@@ -246,22 +222,6 @@

-
-
- {% translate "Role Permissions" %} -
-
    -
  • - {% translate "Owner" %}: {% translate "Can manage all organization settings, members, services, and can appoint administrators." %} -
  • -
  • - {% translate "Administrator" %}: {% translate "Can manage members, invite users, and manage all services and instances." %} -
  • -
  • - {% translate "Member" %}: {% translate "Can view organization details, create and manage their own service instances." %} -
  • -
-
{% csrf_token %}
{{ invitation_form }}
diff --git a/src/servala/frontend/templates/includes/tabbed_fieldset_form.html b/src/servala/frontend/templates/includes/tabbed_fieldset_form.html index 74fa22a..5857bdf 100644 --- a/src/servala/frontend/templates/includes/tabbed_fieldset_form.html +++ b/src/servala/frontend/templates/includes/tabbed_fieldset_form.html @@ -21,8 +21,7 @@
+

{{ subfieldset.title }}

+ {% for field in subfieldset.fields %} + {% with field=form|get_field:field %}{{ field.as_field_group }}{% endwith %} + {% endfor %} {% endif %} {% endfor %}
diff --git a/src/servala/frontend/urls.py b/src/servala/frontend/urls.py index 73d0759..3aa9b08 100644 --- a/src/servala/frontend/urls.py +++ b/src/servala/frontend/urls.py @@ -30,11 +30,6 @@ urlpatterns = [ views.OrganizationUpdateView.as_view(), name="organization.details", ), - path( - "details/invitations//delete/", - views.InvitationDeleteView.as_view(), - name="invitation.delete", - ), path( "services/", views.ServiceListView.as_view(), diff --git a/src/servala/frontend/views/__init__.py b/src/servala/frontend/views/__init__.py index 33b0560..6167221 100644 --- a/src/servala/frontend/views/__init__.py +++ b/src/servala/frontend/views/__init__.py @@ -9,7 +9,6 @@ from .generic import ( ) from .organization import ( InvitationAcceptView, - InvitationDeleteView, OrganizationCreateView, OrganizationDashboardView, OrganizationUpdateView, @@ -28,7 +27,6 @@ from .support import SupportView __all__ = [ "IndexView", "InvitationAcceptView", - "InvitationDeleteView", "LogoutView", "OrganizationCreateView", "OrganizationDashboardView", diff --git a/src/servala/frontend/views/organization.py b/src/servala/frontend/views/organization.py index c4c1336..7013a6c 100644 --- a/src/servala/frontend/views/organization.py +++ b/src/servala/frontend/views/organization.py @@ -5,7 +5,7 @@ from django.utils import timezone from django.utils.decorators import method_decorator from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, DeleteView, DetailView, TemplateView +from django.views.generic import CreateView, DetailView, TemplateView from django_scopes import scopes_disabled from rules.contrib.views import AutoPermissionRequiredMixin @@ -14,6 +14,7 @@ from servala.core.models import ( Organization, OrganizationInvitation, OrganizationMembership, + OrganizationRole, ServiceInstance, ) from servala.frontend.forms.organization import ( @@ -21,11 +22,7 @@ from servala.frontend.forms.organization import ( OrganizationForm, OrganizationInvitationForm, ) -from servala.frontend.views.mixins import ( - HtmxUpdateView, - HtmxViewMixin, - OrganizationViewMixin, -) +from servala.frontend.views.mixins import HtmxUpdateView, OrganizationViewMixin class OrganizationCreateView(AutoPermissionRequiredMixin, CreateView): @@ -111,8 +108,10 @@ class OrganizationDashboardView( return context -class OrganizationMembershipMixin: +class OrganizationUpdateView(OrganizationViewMixin, HtmxUpdateView): template_name = "frontend/organizations/update.html" + form_class = OrganizationForm + fragments = ("org-name", "org-name-edit", "members-list") @cached_property def user_role(self): @@ -127,9 +126,10 @@ class OrganizationMembershipMixin: @cached_property def can_manage_members(self): - return self.request.user.has_perm( - "core.change_organization", self.request.organization - ) + return self.user_role in [ + OrganizationRole.ADMIN, + OrganizationRole.OWNER, + ] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -159,18 +159,6 @@ class OrganizationMembershipMixin: return context - -class OrganizationUpdateView( - OrganizationViewMixin, OrganizationMembershipMixin, HtmxUpdateView -): - form_class = OrganizationForm - fragments = ( - "org-name", - "org-name-edit", - "members-list", - "pending-invitations-card", - ) - def post(self, request, *args, **kwargs): if "invite_email" in request.POST: return self.handle_invitation(request) @@ -211,11 +199,7 @@ class OrganizationUpdateView( ) else: for error in form.errors.values(): - for error_msg in error: - messages.error(request, error_msg) - - if self.is_htmx and self._get_fragment(): - return self.get(request, *self.args, **self.kwargs) + messages.error(request, error.as_text()) return redirect(self.get_success_url()) @@ -275,61 +259,3 @@ class InvitationAcceptView(TemplateView): request.session.pop("invitation_next", None) return redirect(invitation.organization.urls.base) - - -class InvitationDeleteView(HtmxViewMixin, OrganizationMembershipMixin, DeleteView): - model = OrganizationInvitation - http_method_names = ["get", "post"] - fragments = ("pending-invitations-card",) - - def get_queryset(self): - return OrganizationInvitation.objects.filter(accepted_by__isnull=True) - - def get_success_url(self): - return self.object.organization.urls.details - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - organization = self.request.organization - context["pending_invitations"] = OrganizationInvitation.objects.filter( - organization=organization, accepted_by__isnull=True - ).order_by("-created_at") - return context - - def _check_permission(self): - return self.request.user.has_perm( - "core.change_organization", self.request.organization - ) - - def get_object(self): - if self.request.method == "POST" and self.is_htmx: - try: - return super().get_object() - except Exception: - return - return super().get_object() - - def post(self, request, *args, **kwargs): - self.object = self.get_object() - organization = self.object.organization - - if not self._check_permission(): - if not self.is_htmx: - messages.error( - request, - _("You do not have permission to delete this invitation."), - ) - return redirect(organization.urls.details) - - email = self.object.email - self.object.delete() - if not self.is_htmx: - messages.success( - request, - _("Invitation for {email} has been deleted.").format(email=email), - ) - - if self.is_htmx and self._get_fragment(): - return self.get(request, *args, **kwargs) - - return redirect(self.get_success_url())