From 9f4f8cfae5ae62bce78a8baedfd18c931494d426 Mon Sep 17 00:00:00 2001 From: sab-LC Date: Fri, 25 Oct 2024 14:41:35 -0700 Subject: [PATCH] Updated bundle form UI and optimized code --- accounts/forms.py | 66 ++++---- accounts/models.py | 2 +- accounts/urls.py | 5 - accounts/views.py | 143 +----------------- helpers/utils.py | 60 +++++++- institutions/urls.py | 1 + institutions/views.py | 42 ++++- localcontexts/static/css/main.css | 14 ++ localcontexts/static/javascript/main.js | 31 ++++ researchers/urls.py | 1 + researchers/views.py | 33 +++- templates/account-settings-base.html | 2 +- .../account_settings_pages/_subscription.html | 26 +++- 13 files changed, 234 insertions(+), 192 deletions(-) diff --git a/accounts/forms.py b/accounts/forms.py index 2a4130c22..6314b9673 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -284,38 +284,40 @@ def clean_inquiry_type(self): class BundleTypeForm(forms.Form): - bundle_type = forms.MultipleChoiceField( - choices=[], - widget=forms.CheckboxSelectMultiple, - label="Bundle Types", - ) + bundle_details = { + 'User': { + 'label': 'User Bundle', + 'description': 'Add 5 more users to this account.', + }, + 'API': { + 'label': 'API Bundle', + 'description': 'Add 3 more API keys to this account.', + }, + 'Project': { + 'label': 'Project Bundle', + 'description': 'Add 10 more projects to this account.', + }, + 'Notification': { + 'label': 'Notification Bundle', + 'description': 'Add 10 more notifications to this account.', + }, + } def __init__(self, *args, **kwargs): - super(BundleTypeForm, self).__init__(*args, **kwargs) - - self.bundle_details = { - 'User': { - 'label': 'User Bundle', - 'description': 'Add 5 more users to this account.', - 'quantity': 5 - }, - 'API': { - 'label': 'API Bundle', - 'description': 'Add 3 more API keys to this account.', - 'quantity': 3 - }, - 'Project': { - 'label': 'Project Bundle', - 'description': 'Add 10 more projects to this account.', - 'quantity': 10 - }, - 'Notification': { - 'label': 'Notification Bundle', - 'description': 'Add 10 more notifications to this account.', - 'quantity': 10 - }, - } + super().__init__(*args, **kwargs) + + self.fields.update({ + f"bundle_{key}": forms.IntegerField( + label=f"{details['label']}", + min_value=0, + required=False, + initial=0, + help_text=details['description'] + ) + for key, details in self.bundle_details.items() + }) - self.fields['bundle_type'].choices = [ - (key, details['label']) for key, details in self.bundle_details.items() - ] + def clean(self): + return { + key: value for key, value in super().clean().items() if key.startswith('bundle_') + } diff --git a/accounts/models.py b/accounts/models.py index e695d5d6c..b452580d9 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -256,7 +256,7 @@ class BundleType(models.Model): def __str__(self): return f"{self.bundle_type} - {self.created_at}" - + def clean(self): super().clean() if self.quantity <= 0: diff --git a/accounts/urls.py b/accounts/urls.py index cd0b37a15..4bdee6d36 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -76,9 +76,4 @@ ), name="password_reset_complete" ), - path( - "subscription///", - views.subscription, - name="subscription" - ), ] diff --git a/accounts/views.py b/accounts/views.py index 7bd0a7c89..4cc4e0937 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -8,7 +8,6 @@ from django.contrib import auth, messages from django.contrib.auth import update_session_auth_hash from django.contrib.auth.decorators import login_required -from django.utils import timezone from django.contrib.auth.models import User from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.contrib.auth.views import (PasswordChangeForm, PasswordResetView, SetPasswordForm) @@ -36,7 +35,7 @@ from .forms import ( RegistrationForm, ResendEmailActivationForm, CustomPasswordResetForm, UserCreateProfileForm, ProfileCreationForm, UserUpdateForm, ProfileUpdateForm, SignUpInvitationForm, - SubscriptionForm, BundleTypeForm + SubscriptionForm ) from .utils import ( @@ -44,14 +43,12 @@ institute_account_subscription, escape_single_quotes, determine_user_role, remove_user_from_account ) -from institutions.utils import get_institution from localcontexts.utils import dev_prod_or_local from researchers.utils import is_user_researcher from helpers.utils import ( - accept_member_invite, validate_email, validate_recaptcha, check_member_role, - create_bundle_call, get_access_token_of_SF + accept_member_invite, validate_email, validate_recaptcha ) -from .models import SignUpInvitation, Profile, UserAffiliation, Subscription, BundleType +from .models import SignUpInvitation, Profile, UserAffiliation, Subscription from helpers.models import HubActivity from projects.models import Project from communities.models import InviteMember, Community @@ -904,137 +901,3 @@ def subscription_inquiry(request): "service_providers": service_providers, }, ) - - -@login_required(login_url="login") -def subscription(request, pk, account_type, related=None): - if dev_prod_or_local(request.get_host()) == "SANDBOX": - return redirect("dashboard") - - renew = False - - form = BundleTypeForm(request.POST or None) - if account_type == 'institution' and ( - request.user in get_institution(pk).get_admins() - or - request.user == get_institution(pk).institution_creator - ): - institution = get_institution(pk) - member_role = check_member_role(request.user, institution) - try: - subscription = Subscription.objects.get(institution=institution) - except Subscription.DoesNotExist: - subscription = None - if subscription is not None: - if subscription.end_date and subscription.end_date < timezone.now(): - renew = True - - if request.method == "POST": - if form.is_valid(): - try: - access_token = get_access_token_of_SF(request) - for bundle in form.cleaned_data['bundle_type']: - bundle_data = form.bundle_details[bundle] - quantity = bundle_data['quantity'] - bundle_data = { - "hubId": str(request.user.id) + "_i", - "isBundle": True, - "BundleType": bundle, - "Quantity": quantity, - } - BundleType.objects.create(institution=institution, bundle_type=bundle) - create_bundle_call(request, bundle_data, access_token) - messages.add_message( - request, - messages.INFO, - ( - "Thank you for your submission, " - "our team will review and be in " - "contact with the bundle contract. " - "You will be notified once your " - "request has been processed." - ), - ) - return redirect("subscription", institution.id, 'institution') - except Exception: - messages.add_message( - request, - messages.ERROR, - "An unexpected error has occurred here." - " Please contact support@localcontexts.org.", - ) - return redirect("subscription", institution.id, 'institute') - context = { - "institution": institution, - "subscription": subscription, - "start_date": subscription.start_date.strftime('%d %B %Y') - if subscription and subscription.start_date is not None - else None, - "end_date": subscription.end_date.strftime('%d %B %Y') - if subscription and subscription.end_date is not None - else None, - "renew": renew, - "member_role": member_role, - "form": form, - } - elif account_type == 'researcher': - researcher = Researcher.objects.get(id=pk) - if researcher.is_subscribed: - subscription = Subscription.objects.filter(researcher=researcher).first() - else: - subscription = None - if subscription is not None: - if subscription.end_date and subscription.end_date < timezone.now(): - renew = True - - if request.method == "POST": - if form.is_valid(): - try: - access_token = get_access_token_of_SF(request) - for bundle in form.cleaned_data['bundle_type']: - bundle_data = BundleTypeForm().bundle_details[bundle] - quantity = bundle_data['quantity'] - bundle_data = { - "hubId": str(request.user.id) + "_r", - "isBundle": True, - "BundleType": bundle, - "Quantity": quantity, - } - BundleType.objects.create(researcher=researcher, bundle_type=bundle) - create_bundle_call(request, bundle_data, access_token) - messages.add_message( - request, - messages.INFO, - ( - "Thank you for your submission, " - "our team will review and be in " - "contact with the bundle contract. " - "You will be notified once your " - "request has been processed." - ), - ) - return redirect("subscription", researcher.id, 'researcher') - except Exception: - messages.add_message( - request, - messages.ERROR, - "An unexpected error has occurred here." - " Please contact support@localcontexts.org.", - ) - return redirect("subscription", researcher.id, 'researcher') - context = { - "researcher": researcher, - "subscription": subscription, - "start_date": subscription.start_date.strftime('%d %B %Y') - if subscription and subscription.start_date is not None - else None, - "end_date": subscription.end_date.strftime('%d %B %Y') - if subscription and subscription.end_date is not None - else None, - "renew": renew, - "form": form, - } - return render( - request, 'account_settings_pages/_subscription.html', - context - ) diff --git a/helpers/utils.py b/helpers/utils.py index 270eb9551..59d150550 100644 --- a/helpers/utils.py +++ b/helpers/utils.py @@ -8,7 +8,7 @@ from django.conf import settings from django.template.loader import get_template ,render_to_string from io import BytesIO -from accounts.models import UserAffiliation, Subscription +from accounts.models import UserAffiliation, Subscription, BundleType from tklabels.models import TKLabel from bclabels.models import BCLabel from helpers.models import ( @@ -28,7 +28,7 @@ from .models import Notice from notifications.models import * -from accounts.forms import UserCreateProfileForm, SubscriptionForm +from accounts.forms import UserCreateProfileForm, SubscriptionForm, BundleTypeForm from accounts.utils import get_users_name, confirm_subscription from notifications.utils import send_user_notification_member_invite_accept @@ -897,3 +897,59 @@ def get_certified_service_providers(request): results = service_providers return results + + +def fetch_subscription(entity, entity_type): + try: + subscription = Subscription.objects.filter(**{entity_type: entity}).first() + renew = subscription.end_date < timezone.now() if subscription and subscription.end_date else False + return subscription, renew + except: + return None, None + + +def process_bundles(request, entity, entity_id, entity_prefix, entity_type): + access_token = get_access_token_of_SF(request) + for key, quantity in request.POST.items(): + if key.startswith('bundle_') and int(quantity) > 0: + bundle_type = key[7:] + bundle_data = { + "hubId": f"{entity_id}_{entity_prefix}", + "isBundle": True, + "BundleType": bundle_type, + "Quantity": int(quantity), + } + BundleType.objects.create(**{entity_type: entity, "bundle_type": bundle_type, "quantity": int(quantity)}) + create_bundle_call(request, bundle_data, access_token) + + +def handle_post_request(request, entity, entity_id, entity_prefix, entity_type, redirect_name): + form = BundleTypeForm(request.POST) + if form.is_valid(): + try: + process_bundles(request, entity, entity_id, entity_prefix, entity_type) + messages.add_message( + request, + messages.INFO, + ( + "Thank you for your submission, " + "our team will review and be in contact with the bundle contract. " + "You will be notified once your request has been processed." + ), + ) + return redirect(redirect_name, entity_id) + except Exception: + messages.add_message( + request, + messages.ERROR, + "An unexpected error has occurred here. Please contact support@localcontexts.org.", + ) + return redirect(redirect_name, entity_id) + else: + messages.add_message( + request, + messages.ERROR, + "An unexpected error has occurred here. Please contact support@localcontexts.org.", + ) + return redirect(redirect_name, entity_id) + diff --git a/institutions/urls.py b/institutions/urls.py index be04fc60e..0df51fe85 100644 --- a/institutions/urls.py +++ b/institutions/urls.py @@ -16,6 +16,7 @@ path('update//', views.update_institution, name="update-institution"), path('preferences//', views.account_preferences, name="preferences-institution"), path('api-key//', views.api_keys, name="institution-api-key"), + path('subscription//', views.institution_subscription, name="institution-subscription"), path('connect-service-provider//', views.connect_service_provider, name="institution-connect-service-provider"), path('subscription-form//', views.create_institution_subscription, name="institution-create-subscription-form"), diff --git a/institutions/views.py b/institutions/views.py index e7d456533..5f30977e9 100644 --- a/institutions/views.py +++ b/institutions/views.py @@ -22,8 +22,9 @@ from api.models import AccountAPIKey from django.contrib.auth.models import User -from accounts.models import UserAffiliation, Subscription, ServiceProviderConnections - +from accounts.models import ( + UserAffiliation, Subscription, ServiceProviderConnections, +) from projects.forms import * from helpers.forms import ( ProjectCommentForm, @@ -35,6 +36,7 @@ ContactOrganizationForm, SignUpInvitationForm, SubscriptionForm, + BundleTypeForm ) from api.forms import APIKeyGeneratorForm from .forms import * @@ -1644,6 +1646,42 @@ def api_keys(request, pk): return render(request, 'account_settings_pages/_api-keys.html', context) except: raise Http404() + +@login_required(login_url="login") +def institution_subscription(request, pk): + if dev_prod_or_local(request.get_host()) == "SANDBOX": + return redirect("dashboard") + + institution = get_institution(pk) + user = request.user + form = BundleTypeForm(request.POST or None) + + if (user in institution.get_admins() or + user == institution.institution_creator): + + member_role = check_member_role(user, institution) + subscription, renew = fetch_subscription(institution, "institution") + + if request.method == "POST": + response = handle_post_request(request, institution, institution.id, "i", "institution", "institution-subscription") + return response + + context = { + "institution": institution, + "subscription": subscription, + "start_date": subscription.start_date.strftime('%d %B %Y') + if subscription and subscription.start_date is not None + else None, + "end_date": subscription.end_date.strftime('%d %B %Y') + if subscription and subscription.end_date is not None + else None, + "renew": renew, + "member_role": member_role, + "form": form, + } + return render(request, 'account_settings_pages/_subscription.html', context) + else: + return redirect("dashboard") @login_required(login_url="login") def create_institution_subscription(request, pk): diff --git a/localcontexts/static/css/main.css b/localcontexts/static/css/main.css index dacf1f062..d85c8b2fd 100644 --- a/localcontexts/static/css/main.css +++ b/localcontexts/static/css/main.css @@ -398,6 +398,7 @@ input:focus:not(.toggle) { /* HEIGHTS */ .h-25 { height: 25%; } +.h-40 { height: 40%;} .h-100 { height: 100% } /* ACTION Buttons / Register,Sign in, etc. */ @@ -1854,4 +1855,17 @@ input[type="checkbox"]:focus { background: none; border: none; box-shadow: none; +} + +.decrement-btn, .increment-btn { + width: 21px; + height: 21px; + background-color: #f0f0f0; + border-radius: 5px; + border: 1px solid #ccc; + font-size: 20px; + cursor: pointer; + line-height: 0; + margin: 0px 5px; + padding: 0 0; } \ No newline at end of file diff --git a/localcontexts/static/javascript/main.js b/localcontexts/static/javascript/main.js index 0b58b3b3d..5bc17b784 100644 --- a/localcontexts/static/javascript/main.js +++ b/localcontexts/static/javascript/main.js @@ -2488,4 +2488,35 @@ if (window.location.href.includes('/subscription/')) { modal.classList.replace('show', 'hide'); } } + document.addEventListener('DOMContentLoaded', () => { + // Handle increment and decrement buttons + const incrementButtons = document.querySelectorAll('.increment-btn'); + const decrementButtons = document.querySelectorAll('.decrement-btn'); + + incrementButtons.forEach(button => { + button.addEventListener('click', function() { + let inputId = this.getAttribute('data-target'); + let inputField = document.getElementById(inputId); + let currentValue = parseInt(inputField.value); + let maxValue = parseInt(inputField.getAttribute('max')); + + if (currentValue < maxValue) { + inputField.value = currentValue + 1; + } + }); + }); + + decrementButtons.forEach(button => { + button.addEventListener('click', function() { + let inputId = this.getAttribute('data-target'); + let inputField = document.getElementById(inputId); + let currentValue = parseInt(inputField.value); + let minValue = parseInt(inputField.getAttribute('min')); + + if (currentValue > minValue) { + inputField.value = currentValue - 1; + } + }); + }); + }); }; \ No newline at end of file diff --git a/researchers/urls.py b/researchers/urls.py index a38bd4f29..9a3b36437 100644 --- a/researchers/urls.py +++ b/researchers/urls.py @@ -21,6 +21,7 @@ views.connect_service_provider, name="researcher-connect-service-provider" ), + path('subscription//', views.researcher_subscription, name="researcher-subscription"), path( 'subscription-form//', views.create_researcher_subscription, diff --git a/researchers/views.py b/researchers/views.py index 49c035967..832141ccc 100644 --- a/researchers/views.py +++ b/researchers/views.py @@ -17,7 +17,7 @@ from helpers.utils import ( crud_notices, create_or_update_boundary, get_notice_defaults, get_notice_translations, check_subscription, validate_recaptcha, form_initiation, get_certified_service_providers, - handle_confirmation_and_subscription, + handle_confirmation_and_subscription, fetch_subscription, handle_post_request ) from accounts.utils import get_users_name from notifications.utils import ( @@ -29,7 +29,7 @@ from communities.models import Community from institutions.models import Institution from notifications.models import ActionNotification -from accounts.models import ServiceProviderConnections, Subscription +from accounts.models import ServiceProviderConnections, Subscription, BundleType from helpers.models import ( OpenToCollaborateNoticeURL, HubActivity, ProjectStatus, EntitiesNotified, ProjectComment, Notice @@ -46,7 +46,8 @@ from helpers.forms import ProjectCommentForm, OpenToCollaborateNoticeURLForm from accounts.forms import ( ContactOrganizationForm, - SubscriptionForm + SubscriptionForm, + BundleTypeForm ) from api.forms import APIKeyGeneratorForm @@ -1400,6 +1401,32 @@ def api_keys(request, researcher, related=None): except Exception as e: raise Http404(str(e)) +@login_required(login_url="login") +def researcher_subscription(request, pk): + if dev_prod_or_local(request.get_host()) == "SANDBOX": + return redirect("dashboard") + + form = BundleTypeForm(request.POST or None) + researcher = Researcher.objects.get(id=pk) + subscription, renew = fetch_subscription(researcher, "researcher") + + if request.method == "POST": + response = handle_post_request(request, researcher, pk, "r", "researcher", "researcher-subscription") + return response + context = { + "researcher": researcher, + "subscription": subscription, + "start_date": subscription.start_date.strftime('%d %B %Y') + if subscription and subscription.start_date is not None + else None, + "end_date": subscription.end_date.strftime('%d %B %Y') + if subscription and subscription.end_date is not None + else None, + "renew": renew, + "form": form, + } + return render(request, 'account_settings_pages/_subscription.html', context) + @login_required(login_url="login") def create_researcher_subscription(request, pk): researcher = Researcher.objects.get(id=pk) diff --git a/templates/account-settings-base.html b/templates/account-settings-base.html index d75665277..73215d34c 100644 --- a/templates/account-settings-base.html +++ b/templates/account-settings-base.html @@ -74,7 +74,7 @@

Account Settings

{% endif %} {% if institution or researcher and envi != "SANDBOX" %} - +
diff --git a/templates/account_settings_pages/_subscription.html b/templates/account_settings_pages/_subscription.html index 7de4b027a..dc2a440d5 100644 --- a/templates/account_settings_pages/_subscription.html +++ b/templates/account_settings_pages/_subscription.html @@ -87,20 +87,34 @@

Add a Bundle

-
+ {% csrf_token %} +
- {% for key, bundle in form.bundle_details.items %} + {% for field in form %}
- +
+ + + +
- -

{{ bundle.description }}

+ +

{{ field.help_text }}

{% endfor %}
- +