diff --git a/accounts/context_processors.py b/accounts/context_processors.py index fe6810d..98689de 100644 --- a/accounts/context_processors.py +++ b/accounts/context_processors.py @@ -1,27 +1,27 @@ from accounts import resource_action from django.core.cache import cache -CACHE_KEY_RESOURCE_ACTION_CONSTANTS = 'resource_action_constants' +# CACHE_KEY_RESOURCE_ACTION_CONSTANTS = 'resource_action_constants' -def resource_action_constants(request): - # Add debugging output - print("Resource and action constants context processor is executing.") - # Try to retrieve the constants from the cache - resource_action_constants = cache.get(CACHE_KEY_RESOURCE_ACTION_CONSTANTS) - # print(f"The value of '{CACHE_KEY_RESOURCE_ACTION_CONSTANTS}' in the cache is: {resource_action_constants}") - if resource_action_constants is None: - # Compute the constants if not found in the cache - resource_action_constants = compute_resource_action_constants() - print() +# def resource_action_constants(request): +# # Add debugging output +# print("Resource and action constants context processor is executing.") +# # Try to retrieve the constants from the cache +# resource_action_constants = cache.get(CACHE_KEY_RESOURCE_ACTION_CONSTANTS) +# # print(f"The value of '{CACHE_KEY_RESOURCE_ACTION_CONSTANTS}' in the cache is: {resource_action_constants}") +# if resource_action_constants is None: +# # Compute the constants if not found in the cache +# resource_action_constants = compute_resource_action_constants() +# print() - # Store the constants in the cache with a timeout (e.g., 3600 seconds for 1 hour) - cache.set(CACHE_KEY_RESOURCE_ACTION_CONSTANTS, resource_action_constants, 3600) +# # Store the constants in the cache with a timeout (e.g., 3600 seconds for 1 hour) +# cache.set(CACHE_KEY_RESOURCE_ACTION_CONSTANTS, resource_action_constants, 3600) - return { - 'resource_context': resource_action_constants, - } +# return { +# 'resource_context': resource_action_constants, +# } -def compute_resource_action_constants(): +def compute_resource_action_constants(request): constants_dict = { 'ACTION_CREATE': resource_action.ACTION_CREATE, 'ACTION_READ': resource_action.ACTION_READ, @@ -49,4 +49,6 @@ def compute_resource_action_constants(): 'RESOURCE_IAM_GROUP': resource_action.RESOURCE_IAM_GROUP, 'RESOURCE_IAM_ROLE': resource_action.RESOURCE_IAM_ROLE, } - return constants_dict + return { + 'resource_context': constants_dict, + } diff --git a/accounts/forms.py b/accounts/forms.py index 2b22247..d7343de 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -319,3 +319,40 @@ class IAmPrincipalRoleAppResourceActionLinkForm(forms.ModelForm): if instance is None: self.fields.pop("active") + + +class IAmPrincipalResourceLinkForm(IAmPrincipalForm): + + class Meta: + model = models.IAmPrincipal + fields = [ + "principal_type", + # "first_name", + # "last_name", + "email", + # "password", + # "confirm_password", + "principal_resource", + ] + + principal_resource = forms.ModelMultipleChoiceField( + label="Module Permission", + queryset=models.IAmAppResource.objects.filter(active=True, deleted=False), + required=False, + widget=forms.widgets.SelectMultiple( + attrs={"class": "form_select js-example-basic-multiple"} + ), + ) + + def save(self, commit=True): + # First, save the instance of the IAmPrincipal model as usual + principal = super().save(commit=False) + # If the principal_resource field has data + if self.cleaned_data['principal_resource']: + # Get the principal_resource data + principal_resource_data = self.cleaned_data['principal_resource'] + # Update the many-to-many relationship + principal.principal_resource.set(principal_resource_data) + # Save the instance to the database + if commit: + principal.save() \ No newline at end of file diff --git a/accounts/migrations/0007_iamprincipalresourcelink_and_more.py b/accounts/migrations/0007_iamprincipalresourcelink_and_more.py new file mode 100644 index 0000000..d5c612c --- /dev/null +++ b/accounts/migrations/0007_iamprincipalresourcelink_and_more.py @@ -0,0 +1,57 @@ +# Generated by Django 5.0.2 on 2024-04-08 10:13 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0006_iamprincipal_facebook_profile_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="IAmPrincipalResourceLink", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "principal", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="principal_resource_link_principal", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "principal_resource", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="principal_resource_link_resource", + to="accounts.iamappresource", + ), + ), + ], + options={ + "db_table": "iam_principal_resource_group_link", + }, + ), + migrations.AddField( + model_name="iamprincipal", + name="principal_resource", + field=models.ManyToManyField( + related_name="principal_resources", + through="accounts.IAmPrincipalResourceLink", + to="accounts.iamappresource", + ), + ), + ] diff --git a/accounts/migrations/0008_iamprincipal_website.py b/accounts/migrations/0008_iamprincipal_website.py new file mode 100644 index 0000000..4406bea --- /dev/null +++ b/accounts/migrations/0008_iamprincipal_website.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-04-08 11:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("accounts", "0007_iamprincipalresourcelink_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="iamprincipal", + name="website", + field=models.URLField(blank=True, null=True, verbose_name="Website"), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index effadb9..8d8e4a2 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -239,6 +239,11 @@ class IAmPrincipal(AbstractUser): on_delete=models.CASCADE, null=True, ) + principal_resource = models.ManyToManyField( + IAmAppResource, + through="IAmPrincipalResourceLink", + related_name="principal_resources", + ) email = models.EmailField(unique=True) gender = models.CharField(max_length=5, blank=True, null=True) date_of_birth = models.DateField(blank=True, null=True) @@ -309,6 +314,12 @@ class IAmPrincipal(AbstractUser): blank=True, null=True, ) + website = models.URLField( + verbose_name="Website", + max_length=200, + blank=True, + null=True, + ) USERNAME_FIELD = "email" REQUIRED_FIELDS = [] @@ -322,6 +333,22 @@ class IAmPrincipal(AbstractUser): return f"{self.email}" +class IAmPrincipalResourceLink(models.Model): + principal = models.ForeignKey( + IAmPrincipal, + related_name="principal_resource_link_principal", + on_delete=models.CASCADE, + ) + principal_resource = models.ForeignKey( + IAmAppResource, + related_name="principal_resource_link_resource", + on_delete=models.CASCADE, + ) + + class Meta: + db_table = "iam_principal_resource_group_link" + + class IAmPrincipalGroupLink(models.Model): principal = models.ForeignKey( IAmPrincipal, diff --git a/accounts/permission.py b/accounts/permission.py index 35adc2b..4faac72 100644 --- a/accounts/permission.py +++ b/accounts/permission.py @@ -3,18 +3,74 @@ from django.core.exceptions import PermissionDenied from . import models from django.db.models import Q from rest_framework import permissions + # import logging # logger = logging.getLogger(__name__) -class CustomPermissionRequiredMixin: - resource = None - action = None +# class CustomPermissionRequiredMixin: +# resource = None +# action = None - def has_custom_permission(self, user, resource, action): - if not self.resource or not self.action: - raise AttributeError("Resource and action attributes must be defined in the view") +# def has_custom_permission(self, user, resource, action): +# if not self.resource or not self.action: +# raise AttributeError("Resource and action attributes must be defined in the view") + +# # if not request.user.is_authenticated: +# # return self.handle_no_permission() + +# if user.is_superuser: # will chagne to principal type for admin +# return True + +# permission_query = Q( +# principal_group__role__app_resource_action__app_resource__name=resource, +# principal_group__role__app_resource_action__app_action__name=action +# ) +# return models.IAmPrincipal.objects.filter(permission_query, id=user.id).exists() + +# def dispatch(self, request, *args, **kwargs): +# if not self.has_custom_permission(request.user, self.resource, self.action): +# # logger.warning(f"Permission denied for user {request.user} accessing {self.resource}:{self.action}") +# raise PermissionDenied("You do not have permission to access this resource.") +# return super().dispatch(request, *args, **kwargs) + +# @classmethod +# def as_decorator(cls, resource, action): +# def decorator(view_func): +# @wraps(view_func) +# def _wrapped_view(request, *args, **kwargs): +# instance = cls() +# instance.resource = resource +# instance.action = action +# if not instance.has_custom_permission(request.user, instance.resource, instance.action): +# raise PermissionDenied("You do not have permission to access this resource.") +# return view_func(request, *args, **kwargs) +# return _wrapped_view +# return decorator + + +# class IsOwnerOrReadOnly(permissions.BasePermission): +# """ +# Custom permission to only allow owners of an object to edit it. +# """ + +# def has_object_permission(self, request, view, obj): +# # Read permissions are allowed to any request, +# # so we'll always allow GET, HEAD or OPTIONS requests. +# if request.method in permissions.SAFE_METHODS: +# return True + +# # Write permissions are only allowed to the owner of the object. +# return obj.created_by == request.user + + +class ResourcePermissionRequiredMixin: + resource = None + + def has_resource_permission(self, user, resource): + # if not self.resource or resource: + # raise AttributeError("Resource attributes must be defined in the view") # if not request.user.is_authenticated: # return self.handle_no_permission() @@ -23,42 +79,33 @@ class CustomPermissionRequiredMixin: return True permission_query = Q( - principal_group__role__app_resource_action__app_resource__name=resource, - principal_group__role__app_resource_action__app_action__name=action + principal_resource__name=resource, ) return models.IAmPrincipal.objects.filter(permission_query, id=user.id).exists() def dispatch(self, request, *args, **kwargs): - if not self.has_custom_permission(request.user, self.resource, self.action): + if not self.has_resource_permission(request.user, self.resource): # logger.warning(f"Permission denied for user {request.user} accessing {self.resource}:{self.action}") - raise PermissionDenied("You do not have permission to access this resource.") + raise PermissionDenied( + "You do not have permission to access this resource." + ) return super().dispatch(request, *args, **kwargs) @classmethod - def as_decorator(cls, resource, action): + def as_decorator(cls, resource): def decorator(view_func): @wraps(view_func) def _wrapped_view(request, *args, **kwargs): instance = cls() instance.resource = resource - instance.action = action - if not instance.has_custom_permission(request.user, instance.resource, instance.action): - raise PermissionDenied("You do not have permission to access this resource.") + if not instance.has_resource_permission( + request.user, instance.resource + ): + raise PermissionDenied( + "You do not have permission to access this resource." + ) return view_func(request, *args, **kwargs) + return _wrapped_view + return decorator - - -class IsOwnerOrReadOnly(permissions.BasePermission): - """ - Custom permission to only allow owners of an object to edit it. - """ - - def has_object_permission(self, request, view, obj): - # Read permissions are allowed to any request, - # so we'll always allow GET, HEAD or OPTIONS requests. - if request.method in permissions.SAFE_METHODS: - return True - - # Write permissions are only allowed to the owner of the object. - return obj.created_by == request.user diff --git a/accounts/templatetags/custom_permissions.py b/accounts/templatetags/custom_permissions.py index 5c08ee3..b0ede77 100644 --- a/accounts/templatetags/custom_permissions.py +++ b/accounts/templatetags/custom_permissions.py @@ -1,11 +1,35 @@ from django import template -from accounts.permission import CustomPermissionRequiredMixin +from accounts.permission import ResourcePermissionRequiredMixin register = template.Library() +# @register.filter(name='has_resource_permission') +# def has_resource_permission(user, resource_action): +# """ +# Check if a user has a specific resource and action permission. + +# Args: +# user (User): The user to check for permission. +# resource_action (str): The resource and action string (e.g., "resource_name.action_name"). + +# Returns: +# bool: True if the user has the specified permission, False otherwise. + +# Example usage in a template: +# {% if user|has_resource_permission:"article.add" %} +# +# {% else %} +# +# {% endif %} +# """ +# resource, action = resource_action.split(".") +# return CustomPermissionRequiredMixin().has_custom_permission(user, resource, action) + + + @register.filter(name='has_resource_permission') -def has_resource_permission(user, resource_action): +def has_resource_permission(user, resource): """ Check if a user has a specific resource and action permission. @@ -17,11 +41,11 @@ def has_resource_permission(user, resource_action): bool: True if the user has the specified permission, False otherwise. Example usage in a template: - {% if user|has_resource_permission:"article.add" %} + {% if user|has_resource_permission:"article" %} {% else %} {% endif %} """ - resource, action = resource_action.split(".") - return CustomPermissionRequiredMixin().has_custom_permission(user, resource, action) + # resource, action = resource_action.split(".") + return ResourcePermissionRequiredMixin().has_resource_permission(user, resource) \ No newline at end of file diff --git a/accounts/urls.py b/accounts/urls.py index f2fd388..d9dc83a 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -24,6 +24,8 @@ urlpatterns = [ path('principal/add/', views.PrincipalCreateOrUpdateView.as_view(), name="principal_add"), path('principal/edit/', views.PrincipalCreateOrUpdateView.as_view(), name="principal_edit"), # path('principal/delete/', views.PrincipalDeleteView.as_view(), name="principal_delete"), + path('principal/resource/permission/edit//', views.PrincipalResourcePermissionEditView.as_view(), + name="principal_resource_permission_edit"), path('principal/group/link/', views.PrincipalGroupLinkListView.as_view(), name="principal_group_link_list"), path('principal/group/link/edit//', views.PrincipalGroupLinkEditView.as_view(), name="principal_group_link_edit"), diff --git a/accounts/views.py b/accounts/views.py index cc926d3..96b5c7c 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -18,6 +18,7 @@ from django.urls import reverse_lazy from django.views import generic from django.db import models, transaction, IntegrityError +from accounts import permission from goodtimes import constants from . import resource_action @@ -25,6 +26,7 @@ from .forms import ( CustomAuthenticationForm, IAmPrincipalForm, IAmPrincipalGroupRoleLinkForm, + IAmPrincipalResourceLinkForm, IAmPrincipalRoleAppResourceActionLinkForm, IAmPrincipalGroupLinkForm, ProfileEditForm, @@ -229,6 +231,45 @@ class PrincipalCreateOrUpdateView(LoginRequiredMixin, generic.View): return render(request, template_name=self.template_name, context=context) +class PrincipalResourcePermissionEditView(permission.ResourcePermissionRequiredMixin, LoginRequiredMixin, generic.View): + page_name = resource_action.RESOURCE_IAM_PRINCIPAL + resource = resource_action.RESOURCE_IAM_PRINCIPAL_GROUP + model = IAmPrincipal + template_name = "accounts/iam_module/iam_principal_resource_permission_edit.html" + form_class = IAmPrincipalResourceLinkForm + success_url = reverse_lazy("accounts:principal_group_link_list") + success_message = "Record Updated Successfully" + error_message = "An error occurred while saving the data" + + def get_object(self): + pk = self.kwargs.get("pk") + return get_object_or_404(self.model, pk=pk) if pk else None + + def get_context_data(self, **kwargs): + context = { + "page_name": self.page_name, + "operation": "Edit", + } + context.update(kwargs) # Include any additional context data passed to the view + return context + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + form = self.form_class(instance=self.object) + context = self.get_context_data(form=form) + return render(request, self.template_name, context=context) + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + form = self.form_class(request.POST, instance=self.object) + if not form.is_valid(): + context = self.get_context_data(form=form) + return render(request, self.template_name, context=context) + form.save() + messages.success(request, self.success_message) + return redirect(self.success_url) + + """Principal Group Link""" diff --git a/goodtimes/services.py b/goodtimes/services.py index 500f569..25b528d 100644 --- a/goodtimes/services.py +++ b/goodtimes/services.py @@ -343,7 +343,9 @@ class PaymentProcessingService: ) def _update_transaction_success(self): + principal_subscription = self._get_principal_subscription_by_id() self.transaction.transaction_status = TransactionStatus.SUCCESS + self.transaction.principal_subscription = principal_subscription.id self.transaction.save() def _update_transaction_failure(self): @@ -409,6 +411,7 @@ class EventFilterService: filtered_events = filtered_events.filter( Q(title__icontains=search_query) | Q(key_guest__icontains=search_query) + | Q(tags__name__in=[search_query]) | Q(venue__address__icontains=search_query) ) diff --git a/goodtimes/settings/base.py b/goodtimes/settings/base.py index 7c7e7cd..88e01ff 100644 --- a/goodtimes/settings/base.py +++ b/goodtimes/settings/base.py @@ -115,7 +115,7 @@ TEMPLATES = [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", - "accounts.context_processors.resource_action_constants", # resource action context processor + "accounts.context_processors.compute_resource_action_constants", # resource action context processor ], }, }, diff --git a/manage_events/admin.py b/manage_events/admin.py index 648653b..ac72d3d 100644 --- a/manage_events/admin.py +++ b/manage_events/admin.py @@ -31,6 +31,7 @@ class EventAdmin(admin.ModelAdmin): "end_date", "venue", "status", + # "tags", "draft", "deleted", "active", diff --git a/manage_events/api/serializers.py b/manage_events/api/serializers.py index 9e9371c..a84b649 100644 --- a/manage_events/api/serializers.py +++ b/manage_events/api/serializers.py @@ -1,5 +1,8 @@ from rest_framework import serializers +from taggit_serializer.serializers import TagListSerializerField from accounts.models import IAmPrincipalLocation +from manage_cms.api.serializers import TagSerializer + from manage_events.utils import get_location_info from accounts.api.serializers import ProfileSerializer from manage_events.models import ( @@ -43,6 +46,7 @@ class EventCategorySerializer(serializers.ModelSerializer): class EventDetailSerializer(serializers.ModelSerializer): + tags = TagSerializer(many=True, read_only=True) images = serializers.SerializerMethodField() venue = VenueSerializer(read_only=True) # Use VenueSerializer for the venue field category = EventCategorySerializer(read_only=True) @@ -72,6 +76,7 @@ class EventDetailSerializer(serializers.ModelSerializer): "images", "is_favorited", "reviews", + "tags", "principal_interaction", ] @@ -109,6 +114,7 @@ class EventDetailSerializer(serializers.ModelSerializer): class CreateEventSerializer(serializers.ModelSerializer): + tags = TagListSerializerField() images = serializers.ListField( child=serializers.ImageField(), write_only=True, required=False ) @@ -134,18 +140,21 @@ class CreateEventSerializer(serializers.ModelSerializer): "age_group", "draft", "venue", + "tags", ] def create(self, validated_data): + tags = validated_data.pop('tags') images_data = validated_data.pop("images", None) event = Event.objects.create(**validated_data) + event.tags.add(*tags) if images_data: for image_data in images_data: EventImage.objects.create(event=event, image=image_data) return event - + class CreateVenueSerializer(serializers.ModelSerializer): class Meta: diff --git a/manage_events/api/views.py b/manage_events/api/views.py index 906937c..81d392f 100644 --- a/manage_events/api/views.py +++ b/manage_events/api/views.py @@ -1,12 +1,9 @@ -from datetime import timedelta -from django.db import transaction from django.shortcuts import get_object_or_404 from django.utils import timezone from rest_framework import status, generics, mixins from rest_framework.views import APIView from django.conf import settings from accounts.models import IAmPrincipalLocation -from accounts.permission import IsOwnerOrReadOnly from goodtimes import constants from django.db.models import Q from django.utils.dateparse import parse_date @@ -40,7 +37,7 @@ from manage_events.models import ( ) import requests -from manage_events.utils import filter_events_by_location, haversine_one +from manage_events.utils import haversine_one class CreateEventApi(APIView): @@ -48,6 +45,7 @@ class CreateEventApi(APIView): permission_classes = [IsAuthenticated] def post(self, request): + print("Data: ", request.data) serializer = CreateEventSerializer(data=request.data) serializer.is_valid(raise_exception=True) @@ -160,6 +158,7 @@ class EventsAPIView(APIView): "tomorrow", "category", "key_guest", + "tags", ] if filter not in params: return ApiResponse.error( @@ -177,6 +176,10 @@ class EventsAPIView(APIView): events = services.EventFilterService.filter_events_by_search( search_query=query ) + elif filter == "tags": + events = services.EventFilterService.filter_events_by_search( + search_query=query + ) elif filter == "category" and category_id is not None: events = services.EventFilterService.filter_events_by_category( int(category_id) @@ -335,7 +338,8 @@ class VenueListView(generics.ListAPIView): class VenueDetailView(generics.RetrieveUpdateDestroyAPIView): queryset = Venue.objects.all() serializer_class = VenueSerializer - permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] + authentication_classes = [JWTAuthentication] + permission_classes = [IsAuthenticated] def get_queryset(self): # This ensures a user can only access their own venues diff --git a/manage_events/forms.py b/manage_events/forms.py index 8da26e8..df46ea9 100644 --- a/manage_events/forms.py +++ b/manage_events/forms.py @@ -36,6 +36,7 @@ class EventForm(forms.ModelForm): "entry_fee", "key_guest", "age_group", + "tags", "draft", ] widgets = { diff --git a/manage_events/migrations/0007_event_tags.py b/manage_events/migrations/0007_event_tags.py new file mode 100644 index 0000000..97d1f96 --- /dev/null +++ b/manage_events/migrations/0007_event_tags.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.2 on 2024-04-08 11:31 + +import taggit.managers +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("manage_events", "0006_alter_venue_latitude_alter_venue_longitude"), + ( + "taggit", + "0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx", + ), + ] + + operations = [ + migrations.AddField( + model_name="event", + name="tags", + field=taggit.managers.TaggableManager( + blank=True, + help_text="A comma-separated list of tags.", + through="taggit.TaggedItem", + to="taggit.Tag", + verbose_name="Tags", + ), + ), + ] diff --git a/manage_events/models.py b/manage_events/models.py index c49621f..a1e4032 100644 --- a/manage_events/models.py +++ b/manage_events/models.py @@ -1,6 +1,7 @@ from django.db import models from accounts.models import BaseModel, IAmPrincipal from django.db import transaction +from taggit.managers import TaggableManager # from django.contrib.gis.db import models as gis_models @@ -22,8 +23,12 @@ class Venue(BaseModel): address = models.TextField(null=True, blank=True) image = models.ImageField(upload_to="venue", null=True, blank=True) url = models.URLField(max_length=200, blank=True, null=True) - latitude = models.DecimalField(max_digits=14, decimal_places=8, blank=True, null=True) - longitude = models.DecimalField(max_digits=14, decimal_places=8, blank=True, null=True) + latitude = models.DecimalField( + max_digits=14, decimal_places=8, blank=True, null=True + ) + longitude = models.DecimalField( + max_digits=14, decimal_places=8, blank=True, null=True + ) def __str__(self): return self.title @@ -76,6 +81,7 @@ class Event(BaseModel): max_digits=14, decimal_places=2, default=0.00 ) # Assuming it's an integer. Use DecimalField if you need to handle cents. key_guest = models.TextField(blank=True, null=True) + tags = TaggableManager(blank=True) age_group = models.CharField(max_length=100, blank=True, null=True) draft = models.BooleanField(default=False) diff --git a/requirements.txt b/requirements.txt index f01b285..94dd958 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,6 +25,7 @@ django-extensions==3.2.3 django-phonenumber-field==7.3.0 django-quill-editor==0.1.40 django-taggit==5.0.1 +django-taggit-serializer==0.1.7 django-widget-tweaks==1.5.0 djangorestframework==3.14.0 djangorestframework-simplejwt==5.3.1 @@ -65,7 +66,7 @@ sqlparse==0.4.4 stripe==8.2.0 tqdm==4.66.2 Twisted==23.10.0 -# twisted-iocpsupport==1.0.4 +twisted-iocpsupport==1.0.4 txaio==23.1.1 typing_extensions==4.9.0 tzdata==2024.1 diff --git a/templates/accounts/iam_module/iam_principal_group_link_list.html b/templates/accounts/iam_module/iam_principal_group_link_list.html index 70031bd..dc350c1 100644 --- a/templates/accounts/iam_module/iam_principal_group_link_list.html +++ b/templates/accounts/iam_module/iam_principal_group_link_list.html @@ -128,7 +128,7 @@ Email Groups + style="width: 143.516px;">Resource Principal Type {{ subadmin_principal.first_name }} {{ subadmin_principal.last_name }} {{ subadmin_principal.email }} - {% if subadmin_principal.principal_group.all %} - {% for group in subadmin_principal.principal_group.all %} + {% if subadmin_principal.principal_resource.all %} + {% for group in subadmin_principal.principal_resource.all %} {{ group.name }} {% endfor %} {% else %} @@ -154,7 +154,7 @@ {{ subadmin_principal.principal_type }} - --> + + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_CUSTOMER %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_WALLET %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_PAYMENT %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_BANK_ACCOUNTS %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_WITHDRAWALS %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_SUBSCRIPTIONS %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_PRINCIPAL_SUBSCRIPTIONS %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_REFERRALS %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_VENUES %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_EVENTS %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_CONTACT_US %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_CMS %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_FEEDBACK %} + {% endif %} + {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_NOTIFICATIONS %} + {% endif %}