manage coupons list, add, edit, delete views
This commit is contained in:
@@ -44,6 +44,7 @@ def compute_resource_action_constants(request):
|
||||
'RESOURCE_MANAGE_NOTIFICATIONS': resource_action.RESOURCE_MANAGE_NOTIFICATIONS,
|
||||
'RESOURCE_MANAGE_REFERRALS': resource_action.RESOURCE_MANAGE_REFERRALS,
|
||||
'RESOURCE_MANAGE_FEEDBACK': resource_action.RESOURCE_MANAGE_FEEDBACK,
|
||||
'RESOURCE_MANAGE_COUPONS': resource_action.RESOURCE_MANAGE_COUPONS,
|
||||
'RESOURCE_IAM_PRINCIPAL': resource_action.RESOURCE_IAM_PRINCIPAL,
|
||||
'RESOURCE_IAM_PRINCIPAL_GROUP': resource_action.RESOURCE_IAM_PRINCIPAL_GROUP,
|
||||
'RESOURCE_IAM_GROUP': resource_action.RESOURCE_IAM_GROUP,
|
||||
|
||||
@@ -27,7 +27,8 @@ from accounts.resource_action import (
|
||||
RESOURCE_MANAGE_REFERRALS,
|
||||
RESOURCE_MANAGE_FEEDBACK,
|
||||
RESOURCE_MANAGE_WITHDRAWALS,
|
||||
RESOURCE_MANAGE_BANK_ACCOUNTS
|
||||
RESOURCE_MANAGE_BANK_ACCOUNTS,
|
||||
RESOURCE_MANAGE_COUPONS
|
||||
)
|
||||
# this variable store the data of model principaltype, action, resource
|
||||
fixture_data = [
|
||||
@@ -334,4 +335,16 @@ fixture_data = [
|
||||
"action": [1, 2, 3, 4],
|
||||
},
|
||||
},
|
||||
{
|
||||
"model": "accounts.iamappresource",
|
||||
"pk": 18,
|
||||
"fields": {
|
||||
"name": RESOURCE_MANAGE_COUPONS,
|
||||
"label": RESOURCE_MANAGE_COUPONS,
|
||||
"slug": RESOURCE_MANAGE_COUPONS,
|
||||
"created_on": "2023-09-28T16:17:42.815",
|
||||
"modified_on": "2023-09-28T16:17:42.815",
|
||||
"action": [1, 2, 3, 4],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -386,5 +386,22 @@
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "accounts.iamappresource",
|
||||
"pk": 18,
|
||||
"fields": {
|
||||
"name": "manage_coupons",
|
||||
"label": "manage_coupons",
|
||||
"slug": "manage_coupons",
|
||||
"created_on": "2023-09-28T16:17:42.815",
|
||||
"modified_on": "2023-09-28T16:17:42.815",
|
||||
"action": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -28,6 +28,7 @@ RESOURCE_MANAGE_REFERRALS = "manage_referrals"
|
||||
RESOURCE_MANAGE_NOTIFICATIONS = "manage_notifications"
|
||||
RESOURCE_MANAGE_WITHDRAWALS = "manage_withdrawals"
|
||||
RESOURCE_MANAGE_BANK_ACCOUNTS = "manage_bank_accounts"
|
||||
RESOURCE_MANAGE_COUPONS = "manage_coupons"
|
||||
|
||||
|
||||
# These constants are used solely for managing the active and inactive state of pages
|
||||
|
||||
@@ -53,5 +53,6 @@ STATICFILES_DIRS = [BASE_DIR.joinpath("static")]
|
||||
|
||||
STRIPE_CHECKOUT_URL = "http://localhost:8000/subscriptions/stripe-subscription/"
|
||||
STRIPE_FINAL_URL = "http://localhost:8000/subscriptions/create-checkout-session/"
|
||||
COUPON_VALIDITY_CHECK_URL = "http://localhost:8000/subscriptions/coupon-validity-check/"
|
||||
|
||||
LOGO_PATH = "static"
|
||||
|
||||
@@ -82,5 +82,6 @@ STRIPE_CHECKOUT_URL = (
|
||||
STRIPE_FINAL_URL = (
|
||||
"https://admin.goodtimesltd.co.uk/subscriptions/create-checkout-session/"
|
||||
)
|
||||
COUPON_VALIDITY_CHECK_URL = "https://admin.goodtimesltd.co.uk/subscriptions/coupon-validity-check/"
|
||||
|
||||
LOGO_PATH = "/var/www/goodtimes_prod/goodtimes/static"
|
||||
|
||||
@@ -82,5 +82,6 @@ STRIPE_CHECKOUT_URL = (
|
||||
STRIPE_FINAL_URL = (
|
||||
"https://staging.goodtimesltd.co.uk/subscriptions/create-checkout-session/"
|
||||
)
|
||||
COUPON_VALIDITY_CHECK_URL = "https://staging.goodtimesltd.co.uk/subscriptions/coupon-validity-check/"
|
||||
|
||||
LOGO_PATH = "/var/www/goodtimes/static"
|
||||
|
||||
@@ -53,6 +53,9 @@ urlpatterns = [
|
||||
path("subscriptions/", include("manage_subscriptions.urls")),
|
||||
path("api/subscriptions/", include("manage_subscriptions.api.urls")),
|
||||
|
||||
path("coupons/", include("manage_coupons.urls")),
|
||||
# path("api/coupons/", include("manage_coupons.api.urls")),
|
||||
|
||||
path("notifications/", include("manage_notifications.urls")),
|
||||
path("api/notifications/", include("manage_notifications.api.urls")),
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from datetime import timedelta
|
||||
from django.utils import timezone
|
||||
from onesignal_sdk.client import Client as OneSignalClient
|
||||
from accounts.models import IAmPrincipal
|
||||
from manage_coupons.models import Coupon
|
||||
from manage_notifications.models import (
|
||||
IAmPrincipalNotificationSettings,
|
||||
InAppNotification,
|
||||
@@ -132,6 +133,16 @@ class WebhookService:
|
||||
def get_order_id(self):
|
||||
return self.charge_data["metadata"]["order_id"]
|
||||
|
||||
def get_coupon(self):
|
||||
coupon_code = self.charge_data["metadata"].get("couponCode")
|
||||
if coupon_code:
|
||||
try:
|
||||
return Coupon.objects.get(coupon_code=coupon_code)
|
||||
except Coupon.DoesNotExist:
|
||||
logger.error(f"Invalid coupon code: {coupon_code}")
|
||||
raise ValueError(f"Invalid coupon code: {coupon_code}")
|
||||
return None
|
||||
|
||||
|
||||
class ReferralRewardService:
|
||||
def __init__(self, principal, principal_subscription, subscription):
|
||||
@@ -225,7 +236,7 @@ class SubscriptionService:
|
||||
def __init__(self):
|
||||
self.principal_subscription = None
|
||||
|
||||
def create_principal_subscription(self, principal, subscription, order_id):
|
||||
def create_principal_subscription(self, principal, subscription, order_id, coupon=None):
|
||||
subscription_days = subscription.plan.days
|
||||
today = timezone.now().date()
|
||||
last_date = today + timedelta(days=subscription_days)
|
||||
@@ -237,7 +248,11 @@ class SubscriptionService:
|
||||
start_date=today,
|
||||
end_date=last_date,
|
||||
grace_period_end_date=last_date + timedelta(days=15),
|
||||
coupon_code=coupon.coupon_code if coupon else None,
|
||||
)
|
||||
if coupon:
|
||||
coupon.no_of_redeems += 1
|
||||
coupon.save()
|
||||
self.principal_subscription = principal_subscription
|
||||
return principal_subscription
|
||||
|
||||
@@ -260,6 +275,7 @@ class PaymentProcessingService:
|
||||
self.transaction = self.webhook_service.get_transaction()
|
||||
self.subscription = self.webhook_service.get_subscription()
|
||||
self.order_id = self.webhook_service.get_order_id()
|
||||
self.coupon = self.webhook_service.get_coupon()
|
||||
self.subscription_service = SubscriptionService()
|
||||
self.principal_subscription = None
|
||||
|
||||
@@ -274,7 +290,7 @@ class PaymentProcessingService:
|
||||
# Create or update the principal subscription
|
||||
self.principal_subscription = (
|
||||
self.subscription_service.create_principal_subscription(
|
||||
self.principal, self.subscription, self.order_id
|
||||
self.principal, self.subscription, self.order_id, self.coupon
|
||||
)
|
||||
)
|
||||
print("First Part Done....!!!!!")
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from manage_coupons.models import Coupon
|
||||
|
||||
|
||||
class CouponForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Coupon
|
||||
fields = [
|
||||
"title",
|
||||
"coupon_code",
|
||||
"description",
|
||||
"image",
|
||||
"discount_amount",
|
||||
"discount_percentage",
|
||||
"valid_from",
|
||||
"valid_to",
|
||||
"max_redeems",
|
||||
]
|
||||
widgets = {
|
||||
"valid_from": forms.DateTimeInput(attrs={"type": "datetime-local"}),
|
||||
"valid_to": forms.DateTimeInput(attrs={"type": "datetime-local"}),
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
discount_amount = cleaned_data.get("discount_amount")
|
||||
discount_percentage = cleaned_data.get("discount_percentage")
|
||||
valid_from = cleaned_data.get("valid_from")
|
||||
valid_to = cleaned_data.get("valid_to")
|
||||
|
||||
if discount_amount and discount_percentage:
|
||||
raise ValidationError(
|
||||
"You can only set either a discount amount or a discount percentage, not both."
|
||||
)
|
||||
|
||||
if not discount_amount and not discount_percentage:
|
||||
raise ValidationError(
|
||||
"You must set either a discount amount or a discount percentage."
|
||||
)
|
||||
|
||||
if valid_from and valid_to and valid_from >= valid_to:
|
||||
raise ValidationError(
|
||||
"The valid_from date must be earlier than the valid_to date."
|
||||
)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
81
manage_coupons/migrations/0001_initial.py
Normal file
81
manage_coupons/migrations/0001_initial.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# Generated by Django 5.0.2 on 2024-07-22 12:20
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Coupon",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("active", models.BooleanField(default=True)),
|
||||
("deleted", models.BooleanField(default=False)),
|
||||
("created_on", models.DateTimeField(auto_now_add=True)),
|
||||
("modified_on", models.DateTimeField(auto_now=True)),
|
||||
("title", models.CharField(max_length=255)),
|
||||
("coupon_code", models.CharField(max_length=50, unique=True)),
|
||||
("no_of_redeems", models.IntegerField(default=0)),
|
||||
("description", models.TextField(blank=True, null=True)),
|
||||
(
|
||||
"image",
|
||||
models.ImageField(blank=True, null=True, upload_to="coupon_img"),
|
||||
),
|
||||
(
|
||||
"discount_amount",
|
||||
models.DecimalField(
|
||||
blank=True, decimal_places=2, max_digits=10, null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"discount_percentage",
|
||||
models.DecimalField(
|
||||
blank=True, decimal_places=2, max_digits=5, null=True
|
||||
),
|
||||
),
|
||||
("valid_from", models.DateTimeField()),
|
||||
("valid_to", models.DateTimeField()),
|
||||
("max_redeems", models.IntegerField(default=0)),
|
||||
(
|
||||
"created_by",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="%(class)s_created",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"modified_by",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="%(class)s_modified",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"db_table": "coupon",
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -2,12 +2,11 @@ from django.db import models
|
||||
from django.utils import timezone
|
||||
from accounts.models import BaseModel, IAmPrincipalType
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
class Coupon(BaseModel):
|
||||
title = models.CharField(max_length=255)
|
||||
coupon_code = models.CharField(max_length=50, unique=True)
|
||||
no_of_redeems = models.IntegerField(default=0)
|
||||
description = models.TextField(null=True, blank=True)
|
||||
image = models.ImageField(upload_to="coupon_img", null=True, blank=True)
|
||||
discount_amount = models.DecimalField(
|
||||
@@ -16,11 +15,9 @@ class Coupon(BaseModel):
|
||||
discount_percentage = models.DecimalField(
|
||||
max_digits=5, decimal_places=2, null=True, blank=True
|
||||
)
|
||||
principal_types = models.ManyToManyField(
|
||||
IAmPrincipalType, related_name="principal_type_coupons", blank=True
|
||||
)
|
||||
valid_from = models.DateTimeField()
|
||||
valid_to = models.DateTimeField()
|
||||
max_redeems = models.IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
db_table = "coupon"
|
||||
@@ -28,6 +25,13 @@ class Coupon(BaseModel):
|
||||
def __str__(self):
|
||||
return self.coupon_code
|
||||
|
||||
# If max_redeems is 0, it means that we are allowing unlimited redeems
|
||||
|
||||
def is_valid(self):
|
||||
now = timezone.now()
|
||||
return self.active and not self.deleted and self.valid_from <= now <= self.valid_to
|
||||
return (
|
||||
self.active
|
||||
and not self.deleted
|
||||
and self.valid_from <= now <= self.valid_to
|
||||
and (self.max_redeems == 0 or self.no_of_redeems < self.max_redeems)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = "manage_coupons"
|
||||
|
||||
urlpatterns = [
|
||||
path("coupon/list/", views.CouponView.as_view(), name="coupon_list"),
|
||||
path(
|
||||
"coupon/add/",
|
||||
views.CouponCreateOrUpdateView.as_view(),
|
||||
name="coupon_add",
|
||||
),
|
||||
path(
|
||||
"coupon/edit/<int:pk>/",
|
||||
views.CouponCreateOrUpdateView.as_view(),
|
||||
name="coupon_edit",
|
||||
),
|
||||
path(
|
||||
"coupon/delete/<int:pk>/",
|
||||
views.CouponDeleteView.as_view(),
|
||||
name="coupon_delete",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,3 +1,114 @@
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
from django.views import generic
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from accounts import resource_action
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib import messages
|
||||
from goodtimes import constants
|
||||
from manage_coupons.forms import CouponForm
|
||||
from manage_coupons.models import Coupon
|
||||
|
||||
# Create your views here.
|
||||
|
||||
|
||||
class CouponView(LoginRequiredMixin, generic.ListView):
|
||||
page_name = resource_action.RESOURCE_MANAGE_COUPONS
|
||||
resource = resource_action.RESOURCE_MANAGE_COUPONS
|
||||
action = resource_action.ACTION_READ
|
||||
model = Coupon
|
||||
template_name = "manage_coupons/coupon_list.html"
|
||||
context_object_name = "coupon_obj"
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().filter(deleted=False, active=True)
|
||||
return queryset.order_by("-created_on")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["page_name"] = self.page_name
|
||||
return context
|
||||
|
||||
|
||||
class CouponCreateOrUpdateView(LoginRequiredMixin, generic.View):
|
||||
# Set the page_name and resource
|
||||
page_name = resource_action.RESOURCE_MANAGE_COUPONS
|
||||
resource = resource_action.RESOURCE_MANAGE_COUPONS
|
||||
|
||||
# Initialize the action as ACTION_CREATE (can change based on logic)
|
||||
action = resource_action.ACTION_CREATE
|
||||
|
||||
template_name = "manage_coupons/coupon_add.html"
|
||||
model = Coupon
|
||||
form_class = CouponForm
|
||||
success_url = reverse_lazy("manage_coupons:coupon_list")
|
||||
error_message = "An error occurred while saving the data."
|
||||
|
||||
# Determine the success message dynamically based on whether it's an update or create
|
||||
def get_success_message(self):
|
||||
self.success_message = (
|
||||
constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED
|
||||
)
|
||||
return self.success_message
|
||||
|
||||
# Get the object (if exists) based on URL parameter 'pk'
|
||||
def get_object(self):
|
||||
pk = self.kwargs.get("pk")
|
||||
return get_object_or_404(self.model, pk=pk) if pk else None
|
||||
|
||||
# Add page_name and operation to the context
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
"page_name": self.page_name,
|
||||
"operation": "Add" if not self.object else "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()
|
||||
|
||||
# If an object is found, change action to ACTION_UPDATE
|
||||
if self.object is not None:
|
||||
self.action = resource_action.ACTION_UPDATE
|
||||
|
||||
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()
|
||||
|
||||
# If an object is found, change action to ACTION_UPDATE
|
||||
if self.object is not None:
|
||||
self.action = resource_action.ACTION_UPDATE
|
||||
|
||||
form = self.form_class(request.POST, request.FILES, instance=self.object)
|
||||
if not form.is_valid():
|
||||
print(form.errors)
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
form.save()
|
||||
messages.success(self.request, self.get_success_message())
|
||||
return redirect(self.success_url)
|
||||
|
||||
|
||||
class CouponDeleteView(LoginRequiredMixin, generic.View):
|
||||
page_name = resource_action.RESOURCE_MANAGE_COUPONS
|
||||
resource = resource_action.RESOURCE_MANAGE_COUPONS
|
||||
action = resource_action.ACTION_DELETE
|
||||
model = Coupon
|
||||
success_url = reverse_lazy("manage_coupons:coupon_list")
|
||||
success_message = constants.RECORD_DELETED
|
||||
error_message = constants.RECORD_NOT_FOUND
|
||||
|
||||
def get(self, request, pk):
|
||||
try:
|
||||
type_obj = self.model.objects.get(id=pk)
|
||||
type_obj.deleted = True
|
||||
type_obj.active = False
|
||||
type_obj.save()
|
||||
messages.success(request, self.success_message)
|
||||
except self.model.DoesNotExist:
|
||||
messages.success(request, self.error_message)
|
||||
|
||||
return redirect(self.success_url)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django import forms
|
||||
from accounts.models import IAmPrincipalType
|
||||
from manage_subscriptions.models import PrincipalSubscription, Subscription, Plan
|
||||
|
||||
|
||||
@@ -31,7 +32,15 @@ class SubscriptionForm(forms.ModelForm):
|
||||
"active",
|
||||
"deleted",
|
||||
"is_free",
|
||||
] # Include all fields you want from the model
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SubscriptionForm, self).__init__(*args, **kwargs)
|
||||
event_user = IAmPrincipalType.objects.get(name="event_user")
|
||||
event_manager = IAmPrincipalType.objects.get(name="event_manager")
|
||||
self.fields["principal_types"].queryset = IAmPrincipalType.objects.filter(
|
||||
id__in=[event_user.id, event_manager.id]
|
||||
)
|
||||
|
||||
|
||||
class PrincipalSubscriptionForm(forms.ModelForm):
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.2 on 2024-07-22 12:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("manage_subscriptions", "0008_subscription_is_free"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="principalsubscription",
|
||||
name="coupon_code",
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
@@ -71,6 +71,7 @@ class PrincipalSubscription(BaseModel):
|
||||
payment_intent_client_secret = models.CharField(
|
||||
max_length=255, null=True, blank=True
|
||||
)
|
||||
coupon_code = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "principal_subscription"
|
||||
|
||||
@@ -70,6 +70,11 @@ urlpatterns = [
|
||||
views.create_checkout_session,
|
||||
name="create_checkout_session",
|
||||
),
|
||||
path(
|
||||
"coupon-validity-check/",
|
||||
views.validate_coupon,
|
||||
name="validate_coupon",
|
||||
),
|
||||
path("stripe/", views.SubscriptionPageView.as_view(), name="stripe"),
|
||||
path("success/", views.SuccessView.as_view(), name="success"),
|
||||
path("cancel/", views.CancelView.as_view(), name="cancel"),
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
import json
|
||||
from django.http import HttpResponseBadRequest, JsonResponse, HttpResponseRedirect
|
||||
from django.http import HttpResponseBadRequest, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
import stripe
|
||||
from accounts import resource_action
|
||||
from accounts.models import IAmPrincipal
|
||||
from goodtimes.utils import ApiResponse
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth import login
|
||||
import jwt
|
||||
from rest_framework_simplejwt.tokens import AccessToken
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth import get_user_model
|
||||
from manage_coupons.models import Coupon
|
||||
from manage_subscriptions.forms import (
|
||||
PlanForm,
|
||||
SubscriptionForm,
|
||||
PrincipalSubscriptionForm,
|
||||
)
|
||||
@@ -24,7 +22,6 @@ from manage_wallets.models import (
|
||||
)
|
||||
from .models import Plan, Subscription, PrincipalSubscription
|
||||
from django.views import generic
|
||||
from rest_framework import status
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib import messages
|
||||
@@ -32,12 +29,10 @@ from goodtimes import constants
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.conf import settings
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
from django.db.models import Q
|
||||
# Create your views here.
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
class SubscriptionCreateOrUpdateView(LoginRequiredMixin, generic.View):
|
||||
# Set the page_name and resource
|
||||
@@ -101,7 +96,10 @@ class SubscriptionCreateOrUpdateView(LoginRequiredMixin, generic.View):
|
||||
# This code ensures that only one free plan can be created by checking for existing free plans before saving a new one.
|
||||
if form.cleaned_data.get("is_free"):
|
||||
if self.model.objects.filter(Q(is_free=True) & Q(active=True)).exists():
|
||||
messages.error(self.request, "A free plan is already available. Please deactivate the existing one before creating a new one.")
|
||||
messages.error(
|
||||
self.request,
|
||||
"A free plan is already available. Please deactivate the existing one before creating a new one.",
|
||||
)
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
|
||||
@@ -119,7 +117,12 @@ class SubscriptionView(LoginRequiredMixin, generic.ListView):
|
||||
context_object_name = "subscription_obj"
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().filter(deleted=False, active=True).prefetch_related("principal_types")
|
||||
queryset = (
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(deleted=False, active=True)
|
||||
.prefetch_related("principal_types")
|
||||
)
|
||||
return queryset.order_by("-created_on")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@@ -395,15 +398,19 @@ class SubscriptionPageView(TemplateView):
|
||||
if request.user.is_authenticated:
|
||||
print("request.user: ", request.user)
|
||||
subscriptions = Subscription.objects.filter(
|
||||
principal_types=request.user.principal_type, active=True, deleted=False, is_free=False
|
||||
principal_types=request.user.principal_type,
|
||||
active=True,
|
||||
deleted=False,
|
||||
is_free=False,
|
||||
)
|
||||
|
||||
if subscriptions.exists():
|
||||
context["subscriptions"] = subscriptions
|
||||
context["stripeCheckoutUrl"] = settings.STRIPE_CHECKOUT_URL
|
||||
context["stripeFinalUrl"] = settings.STRIPE_FINAL_URL
|
||||
context["couponValidityCheckUrl"] = settings.COUPON_VALIDITY_CHECK_URL
|
||||
else:
|
||||
# Handle the case where no subscriptions are found for the principal type.
|
||||
# Handling the case where no subscriptions are found for the principal type.
|
||||
context["error"] = "No subscriptions found for your user type."
|
||||
return context
|
||||
|
||||
@@ -420,18 +427,37 @@ def stripe_config(request):
|
||||
def validate_coupon(request):
|
||||
data = json.loads(request.body)
|
||||
coupon_code = data.get("couponCode", None)
|
||||
subscription_id = data.get("subscriptionId", None)
|
||||
|
||||
# Validate coupon code
|
||||
try:
|
||||
subscription = Subscription.objects.get(id=subscription_id)
|
||||
except Subscription.DoesNotExist:
|
||||
return JsonResponse({"error": "Subscription not found."}, status=404)
|
||||
|
||||
# Validating Coupon
|
||||
if coupon_code:
|
||||
try:
|
||||
coupon = stripe.Coupon.retrieve(coupon_code)
|
||||
if coupon["valid"] is False:
|
||||
return JsonResponse({"error": "Invalid or expired coupon code."}, status=400)
|
||||
return JsonResponse({"success": "Coupon code is valid."})
|
||||
except stripe.error.InvalidRequestError:
|
||||
return JsonResponse({"error": "Invalid coupon code."}, status=400)
|
||||
else:
|
||||
return JsonResponse({"error": "Coupon code not provided."}, status=400)
|
||||
coupon = Coupon.objects.get(coupon_code=coupon_code)
|
||||
if not coupon.is_valid():
|
||||
return JsonResponse({"error": "Coupon is not valid."}, status=400)
|
||||
if coupon.discount_amount and coupon.discount_amount > subscription.amount:
|
||||
return JsonResponse(
|
||||
{"error": "Coupon discount amount exceeds subscription amount."},
|
||||
status=400,
|
||||
)
|
||||
if (
|
||||
coupon.discount_percentage
|
||||
and (coupon.discount_percentage / Decimal("100")) * subscription.amount
|
||||
> subscription.amount
|
||||
):
|
||||
return JsonResponse(
|
||||
{
|
||||
"error": "Coupon discount percentage exceeds subscription amount."
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
except Coupon.DoesNotExist:
|
||||
return JsonResponse({"error": "Coupon not found."}, status=404)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@@ -442,6 +468,7 @@ def create_checkout_session(request):
|
||||
data = json.loads(request.body)
|
||||
print("data: ", data)
|
||||
subscription_id = data.get("subscriptionId", None)
|
||||
coupon_code = data.get("couponCode", None)
|
||||
principal_id = request.user.id
|
||||
|
||||
try:
|
||||
@@ -453,7 +480,28 @@ def create_checkout_session(request):
|
||||
"order_" + str(timezone.localtime().timestamp()) + str(request.user.email)
|
||||
)
|
||||
print("order_id: ", order_id)
|
||||
|
||||
# Calculating the final amount after applying the coupon discount
|
||||
final_amount = subscription.amount
|
||||
coupon = None
|
||||
if coupon_code:
|
||||
try:
|
||||
coupon = Coupon.objects.get(coupon_code=coupon_code)
|
||||
if coupon.is_valid():
|
||||
if coupon.discount_amount:
|
||||
final_amount -= coupon.discount_amount
|
||||
elif coupon.discount_percentage:
|
||||
final_amount -= final_amount * (
|
||||
coupon.discount_percentage / Decimal("100")
|
||||
)
|
||||
final_amount = max(
|
||||
0, final_amount
|
||||
) # Ensuring the amount is not negative
|
||||
else:
|
||||
return JsonResponse(
|
||||
{"error": "Invalid or expired coupon code."}, status=400
|
||||
)
|
||||
except Coupon.DoesNotExist:
|
||||
return JsonResponse({"error": "Coupon not found."}, status=404)
|
||||
# Create a Transaction object with status INITIATE
|
||||
transaction = Transaction.objects.create(
|
||||
principal=request.user,
|
||||
@@ -461,7 +509,7 @@ def create_checkout_session(request):
|
||||
transaction_type=TransactionType.PAYMENT, # or PAYMENT, as applicable
|
||||
payment_method=PaymentMethod.CARD, # Assuming CARD for this example
|
||||
transaction_status=TransactionStatus.INITIATE,
|
||||
amount=subscription.amount, # Fetching amount from the Subscription object
|
||||
amount=final_amount, # Fetching amount from the Subscription object
|
||||
order_id=order_id,
|
||||
comment="Principal Subscription Initiated",
|
||||
)
|
||||
@@ -492,7 +540,7 @@ def create_checkout_session(request):
|
||||
"name": subscription.title, # Adjust with your subscription/product name
|
||||
},
|
||||
"unit_amount": int(
|
||||
subscription.amount * 100
|
||||
final_amount * 100
|
||||
), # Unit amount in cents/pence
|
||||
"tax_behavior": "inclusive", # or 'exclusive', based on your tax settings
|
||||
},
|
||||
@@ -509,6 +557,7 @@ def create_checkout_session(request):
|
||||
"order_id": str(order_id),
|
||||
"subscription_id": str(subscription.id),
|
||||
"transaction_id": str(transaction.id),
|
||||
"couponCode": str(coupon.coupon_code) if coupon else None,
|
||||
# "principal_subscription_id": str(principal_subscription.id),
|
||||
},
|
||||
)
|
||||
@@ -523,38 +572,3 @@ class SuccessView(TemplateView):
|
||||
|
||||
class CancelView(TemplateView):
|
||||
template_name = "stripe_html/cancel.html"
|
||||
|
||||
|
||||
# class IndexView(TemplateView):
|
||||
# template_name = "stripe_html/index.html"
|
||||
|
||||
# def get(self, request, *args, **kwargs):
|
||||
# # Example of extracting the token from a query parameter or cookie
|
||||
# token = request.GET.get("token")
|
||||
# # token = request.GET.get("token") or request.COOKIES.get("jwt")
|
||||
# print("token: ", token)
|
||||
# if token:
|
||||
# try:
|
||||
# # Decode and validate token
|
||||
# payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
# print("payload: ", payload)
|
||||
# try:
|
||||
# UserModel = get_user_model()
|
||||
# user = UserModel.objects.get(id=payload["user_id"])
|
||||
# # Manually specify the authentication backend
|
||||
# user.backend = "django.contrib.auth.backends.ModelBackend"
|
||||
# # Log the user in
|
||||
# login(request, user)
|
||||
# print("Logged in user: ", user)
|
||||
|
||||
# except IAmPrincipal.DoesNotExist:
|
||||
# # Handle expired token
|
||||
# return HttpResponseBadRequest("No Principal Found")
|
||||
|
||||
# except jwt.ExpiredSignatureError:
|
||||
# # Handle expired token
|
||||
# return HttpResponseBadRequest("Expired Signature Error")
|
||||
# except jwt.InvalidTokenError:
|
||||
# return HttpResponseBadRequest("Invalid Token Error")
|
||||
|
||||
# return super().get(request, *args, **kwargs)
|
||||
|
||||
@@ -154,6 +154,17 @@
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_COUPONS %}
|
||||
<li class="menu {% if page_name == resource_context.RESOURCE_MANAGE_COUPONS %}active{% endif %}">
|
||||
<a href="{% url 'manage_coupons:coupon_list'%}" aria-expanded="false"
|
||||
class="dropdown-toggle">
|
||||
<div class="">
|
||||
<span class="material-symbols-outlined">local_offer</span>
|
||||
<span>Manage Coupons</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user|has_resource_permission:resource_context.RESOURCE_PRINCIPAL_SUBSCRIPTIONS %}
|
||||
<li class="menu {% if page_name == resource_context.RESOURCE_PRINCIPAL_SUBSCRIPTIONS %}active{% endif %}">
|
||||
<a href="{% url 'manage_subscriptions:principal_subscriptions_list'%}" aria-expanded="false"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="col-lg-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<h3>Add Stocks</h3>
|
||||
<h3>{{operation}} {{page_name}}</h3>
|
||||
</div>
|
||||
<div class="col text-end">
|
||||
<button class="btn btn-dark mb-2 me-4" onclick="history.back()">
|
||||
@@ -30,7 +30,7 @@
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
|
||||
<form method="POST">
|
||||
<form method="POST" novalidate>
|
||||
{% csrf_token %}
|
||||
{% include 'includes/dynamic_template_form.html' with form=form %}
|
||||
<div class="mt-4 mb-0">
|
||||
@@ -67,69 +67,3 @@
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
|
||||
{% include "cdn_through_html/filepond_cdn_js.html" %}
|
||||
{% include "cdn_through_html/quill_cdn_js.html" %}
|
||||
{% include "cdn_through_html/tagify_cdn_js.html" %}
|
||||
|
||||
|
||||
<script>
|
||||
/**
|
||||
* ===================================
|
||||
* Blog Description Editor
|
||||
* ===================================
|
||||
*/
|
||||
var quill = new Quill('#description', {
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ header: [1, 2, false] }],
|
||||
['bold', 'italic', 'underline'],
|
||||
['image', 'code-block']
|
||||
]
|
||||
},
|
||||
placeholder: 'Write description...',
|
||||
theme: 'snow' // or 'bubble'
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* ====================
|
||||
* File Pond
|
||||
* ====================
|
||||
*/
|
||||
|
||||
// We want to preview images, so we register
|
||||
// the Image Preview plugin, We also register
|
||||
// exif orientation (to correct mobile image
|
||||
// orientation) and size validation, to prevent
|
||||
// large files from being added
|
||||
FilePond.registerPlugin(
|
||||
FilePondPluginImagePreview,
|
||||
FilePondPluginImageExifOrientation,
|
||||
FilePondPluginFileValidateSize,
|
||||
// FilePondPluginImageEdit
|
||||
);
|
||||
|
||||
// Select the file input and use
|
||||
// create() to turn it into a pond
|
||||
var ecommerce = FilePond.create(document.querySelector('.file-upload-multiple'));
|
||||
|
||||
|
||||
/**
|
||||
* =====================
|
||||
* Blog Tags
|
||||
* =====================
|
||||
*/
|
||||
// The DOM element you wish to replace with Tagify
|
||||
var input = document.querySelector('#id_tags');
|
||||
|
||||
// initialize Tagify on the above input node reference
|
||||
new Tagify(input,{
|
||||
originalInputValueFormat: valuesArr => valuesArr.map(item => item.value).join(', ')
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
163
templates/manage_coupons/coupon_list.html
Normal file
163
templates/manage_coupons/coupon_list.html
Normal file
@@ -0,0 +1,163 @@
|
||||
{% extends 'layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
{% include "cdn_through_html/datatable_cdn_css.html" %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h3>Manage Coupons</h3>
|
||||
</div>
|
||||
<div class="col-sm-6 text-md-end">
|
||||
<!--
|
||||
<button class="btn btn-dark mb-2 me-md-4" onclick="history.back()">
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
Back
|
||||
</button>
|
||||
-->
|
||||
<a class="btn btn-primary mb-2" href="{% url 'manage_coupons:coupon_add' %}">Add Coupons</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row layout-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
<div id="style-3_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
|
||||
<div class="table-responsive">
|
||||
<table id="style-3" class="table style-3 dt-table-hover dataTable no-footer" role="grid"
|
||||
aria-describedby="style-3_info">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Record Id </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Title </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Code </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Used </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Max Allowed </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> From </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> To </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Amount </th>
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Percentage </th>
|
||||
<th class="sorting" tabindex="7" aria-controls="style-3"
|
||||
style="width: 79.7969px;">Active</th>
|
||||
<th class="dt-no-sorting sorting" tabindex="8"
|
||||
aria-controls="style-3"
|
||||
style="width: 100.625px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for data_obj in coupon_obj %}
|
||||
<tr role="row">
|
||||
<td class="checkbox-column text-center sorting_1"> {{data_obj.id}}</td>
|
||||
<td>{{data_obj.title}}</td>
|
||||
<td>{{data_obj.coupon_code}}</td>
|
||||
<td>{{data_obj.no_of_redeems}}</td>
|
||||
<td>{{data_obj.max_redeems}}</td>
|
||||
<td>{{data_obj.valid_from}}</td>
|
||||
<td>{{data_obj.valid_to}}</td>
|
||||
<td>{{data_obj.discount_amount}}</td>
|
||||
<td>{{data_obj.discount_percentage}}</td>
|
||||
<td class="text-center">
|
||||
<span class="shadow-none badge {% if data_obj.active %}badge-primary{% else %}badge-danger{% endif %}">{{data_obj.active}}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<ul class="table-controls">
|
||||
<li><a href="{% url 'manage_coupons:coupon_edit' data_obj.id %}" class="bs-tooltip"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title=""
|
||||
data-original-title="Edit" data-bs-original-title="Edit"
|
||||
aria-label="Edit"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-edit-2 p-1 br-8 mb-1">
|
||||
<path
|
||||
d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
<li><a href="{% url 'manage_coupons:coupon_delete' data_obj.id %}" class="bs-tooltip"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title=""
|
||||
data-original-title="Delete" data-bs-original-title="Delete"
|
||||
aria-label="Delete"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-trash p-1 br-8 mb-1">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path
|
||||
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<!-- include required js cdn link through html here -->
|
||||
{% include "cdn_through_html/datatable_cdn_js.html" %}
|
||||
|
||||
<script>
|
||||
c3 = $('#style-3').DataTable({
|
||||
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
|
||||
"<'table-responsive'tr>" +
|
||||
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
|
||||
"oLanguage": {
|
||||
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
|
||||
"sInfo": "Showing page _PAGE_ of _PAGES_",
|
||||
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
|
||||
"sSearchPlaceholder": "Search...",
|
||||
"sLengthMenu": "Results : _MENU_",
|
||||
},
|
||||
"order": [[ 0, "desc" ]],
|
||||
"stripeClasses": [],
|
||||
"lengthMenu": [5, 10, 20, 50],
|
||||
"pageLength": 10
|
||||
});
|
||||
|
||||
multiCheck(c3);
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,135 +0,0 @@
|
||||
{% extends 'layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
|
||||
{% include "cdn_through_html/filepond_cdn_css.html" %}
|
||||
{% include "cdn_through_html/quill_cdn_css.html" %}
|
||||
{% include "cdn_through_html/tagify_cdn_css.html" %}
|
||||
{{form.media}}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<h3>Add Stock Index</h3>
|
||||
</div>
|
||||
<div class="col text-end">
|
||||
<button class="btn btn-dark mb-2 me-4" onclick="history.back()">
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row layout-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{% include 'includes/dynamic_template_form.html' with form=form %}
|
||||
<div class="mt-4 mb-0">
|
||||
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
|
||||
</div>
|
||||
{% comment %} <div class="mb-3">
|
||||
<label for="title" class="form-label">Title</label>
|
||||
<input type="text" class="form-control" id="title" aria-describedby="title">
|
||||
<div id="emailHelp" class="form-text" style="color: grey;">We'll never share your email with anyone else.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<div id="description"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="product-images">Image</label>
|
||||
<div class="multiple-file-upload">
|
||||
<div class="filepond--root filepond file-upload-multiple filepond--hopper" id="images" data-style-button-remove-item-position="left" data-style-button-process-item-position="right" data-style-load-indicator-position="right" data-style-progress-indicator-position="right" data-style-button-remove-item-align="false" style="height: 57px;"><input class="filepond--browser" type="file" id="filepond--browser-feeq8o6dj" name="filepond" aria-controls="filepond--assistant-feeq8o6dj" aria-labelledby="filepond--drop-label-feeq8o6dj" multiple=""><a class="filepond--credits" aria-hidden="true" href="https://pqina.nl/" target="_blank" rel="noopener noreferrer" style="transform: translateY(49px);">Powered by PQINA</a><div class="filepond--drop-label" style="transform: translate3d(0px, 0px, 0px); opacity: 1;"><label for="filepond--browser-feeq8o6dj" id="filepond--drop-label-feeq8o6dj" aria-hidden="true">Drag & Drop your files or <span class="filepond--label-action" tabindex="0">Browse</span></label></div><div class="filepond--list-scroller" style="transform: translate3d(0px, 41px, 0px);"><ul class="filepond--list" role="list"></ul></div><div class="filepond--panel filepond--panel-root" data-scalable="true"><div class="filepond--panel-top filepond--panel-root"></div><div class="filepond--panel-center filepond--panel-root" style="transform: translate3d(0px, 8px, 0px) scale3d(1, 0.41, 1);"></div><div class="filepond--panel-bottom filepond--panel-root" style="transform: translate3d(0px, 49px, 0px);"></div></div><span class="filepond--assistant" id="filepond--assistant-feeq8o6dj" role="status" aria-live="polite" aria-relevant="additions"></span><div class="filepond--drip"></div><fieldset class="filepond--data"></fieldset></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="tags">Tags</label>
|
||||
<input id="tags" class="tags" value="">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button> {% endcomment %}
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
|
||||
{% include "cdn_through_html/filepond_cdn_js.html" %}
|
||||
{% include "cdn_through_html/quill_cdn_js.html" %}
|
||||
{% include "cdn_through_html/tagify_cdn_js.html" %}
|
||||
|
||||
|
||||
<script>
|
||||
/**
|
||||
* ===================================
|
||||
* Blog Description Editor
|
||||
* ===================================
|
||||
*/
|
||||
var quill = new Quill('#description', {
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ header: [1, 2, false] }],
|
||||
['bold', 'italic', 'underline'],
|
||||
['image', 'code-block']
|
||||
]
|
||||
},
|
||||
placeholder: 'Write description...',
|
||||
theme: 'snow' // or 'bubble'
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* ====================
|
||||
* File Pond
|
||||
* ====================
|
||||
*/
|
||||
|
||||
// We want to preview images, so we register
|
||||
// the Image Preview plugin, We also register
|
||||
// exif orientation (to correct mobile image
|
||||
// orientation) and size validation, to prevent
|
||||
// large files from being added
|
||||
FilePond.registerPlugin(
|
||||
FilePondPluginImagePreview,
|
||||
FilePondPluginImageExifOrientation,
|
||||
FilePondPluginFileValidateSize,
|
||||
// FilePondPluginImageEdit
|
||||
);
|
||||
|
||||
// Select the file input and use
|
||||
// create() to turn it into a pond
|
||||
var ecommerce = FilePond.create(document.querySelector('.file-upload-multiple'));
|
||||
|
||||
|
||||
/**
|
||||
* =====================
|
||||
* Blog Tags
|
||||
* =====================
|
||||
*/
|
||||
// The DOM element you wish to replace with Tagify
|
||||
var input = document.querySelector('#id_tags');
|
||||
|
||||
// initialize Tagify on the above input node reference
|
||||
new Tagify(input,{
|
||||
originalInputValueFormat: valuesArr => valuesArr.map(item => item.value).join(', ')
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,237 +0,0 @@
|
||||
{% extends 'layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
{% include "cdn_through_html/datatable_cdn_css.html" %}
|
||||
{% include "cdn_through_html/tabs_cdn_css.html" %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<h3>Manage Stock</h3>
|
||||
</div>
|
||||
<div class="col text-end">
|
||||
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
Back
|
||||
</button> {% endcomment %}
|
||||
<a class="btn btn-success mb-2 me-4" href="{% url 'manage_stock:stock_index_add' %}">Add Stock Index</a>
|
||||
<a class="btn btn-primary mb-2 me-4" href="{% url 'manage_stock:stock_add' %}">Add Stock</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div id="tabsSimple" class="col-xl-12 col-12 layout-spacing">
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
<div class="simple-pill">
|
||||
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="pills-category-tab" data-bs-toggle="pill" data-bs-target="#pills-category" type="button" role="tab" aria-controls="pills-category" aria-selected="false">Stock Index List</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="pills-new-article-tab" data-bs-toggle="pill" data-bs-target="#pills-new-article" type="button" role="tab" aria-controls="pills-new-article" aria-selected="true">Stock List</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="pills-tabContent">
|
||||
<div class="tab-pane fade" id="pills-category" role="tabpanel" aria-labelledby="pills-category-tab" tabindex="0">
|
||||
<div id="category_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="category" class="table style-3 dt-table-hover dataTable no-footer" role="grid"
|
||||
aria-describedby="style-3_info">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Record Id </th>
|
||||
<th class="sorting" tabindex="0" aria-controls="style-3"
|
||||
colspan="1"
|
||||
style="width: 44.2344px;">Category Name</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="style-3"
|
||||
|
||||
style="width: 79.7969px;">Active</th>
|
||||
<th class="dt-no-sorting sorting" tabindex="0"
|
||||
aria-controls="style-3"
|
||||
style="width: 51.625px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for category in stock_index_obj %}
|
||||
<tr role="row">
|
||||
<td class="checkbox-column text-center sorting_1"> {{category.id}}</td>
|
||||
<td>{{category.title}}</td>
|
||||
<td class="text-center">
|
||||
<span class="shadow-none badge {% if category.active %}badge-primary{% else %}badge-danger{% endif %}">{{category.active}}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<ul class="table-controls">
|
||||
<li><a href="{% url 'manage_stock:stock_index_edit' category.id %}" class="bs-tooltip"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title=""
|
||||
data-original-title="Edit" data-bs-original-title="Edit"
|
||||
aria-label="Edit"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-edit-2 p-1 br-8 mb-1">
|
||||
<path
|
||||
d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z">
|
||||
</path>
|
||||
</svg></a></li>
|
||||
<li><a href="{% url 'manage_stock:stock_index_delete' category.id %}" class="bs-tooltip"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title=""
|
||||
data-original-title="Delete" data-bs-original-title="Delete"
|
||||
aria-label="Delete"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-trash p-1 br-8 mb-1">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path
|
||||
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2">
|
||||
</path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade active show" id="pills-new-article" role="tabpanel" aria-labelledby="pills-new-article-tab" tabindex="0">
|
||||
<div id="new-article_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
|
||||
<div class="table-responsive">
|
||||
<table id="new-article" class="table style-3 dt-table-hover dataTable no-footer" role="grid"
|
||||
aria-describedby="style-3_info">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th class="checkbox-column text-center sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" rowspan="1" colspan="1" aria-sort="ascending"
|
||||
|
||||
style="width: 69.2656px;"> Record Id </th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
|
||||
colspan="1"
|
||||
style="width: 44.2344px;">Category</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
|
||||
|
||||
style="width: 79.7969px;">Title</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
|
||||
|
||||
style="width: 79.7969px;">Active</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
|
||||
|
||||
|
||||
|
||||
style="width: 51.625px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for data_obj in stock_obj %}
|
||||
<tr role="row">
|
||||
<td class="checkbox-column text-center sorting_1">{{data_obj.id}}</td>
|
||||
|
||||
<td>{{data_obj.index_type.title}}</td>
|
||||
<td>{{data_obj.title}}</td>
|
||||
|
||||
|
||||
<td class="text-center">
|
||||
<span class="shadow-none badge {% if data_obj.active %}badge-primary{% else %}badge-danger{% endif %}">{{data_obj.active}}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<ul class="table-controls">
|
||||
<li><a href="{% url 'manage_stock:stock_edit' data_obj.id %}" class="bs-tooltip"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title=""
|
||||
data-original-title="Edit" data-bs-original-title="Edit"
|
||||
aria-label="Edit"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-edit-2 p-1 br-8 mb-1">
|
||||
<path
|
||||
d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z">
|
||||
</path>
|
||||
</svg></a></li>
|
||||
<li><a href="{% url 'manage_stock:stock_delete' data_obj.id %}" class="bs-tooltip"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title=""
|
||||
data-original-title="Delete" data-bs-original-title="Delete"
|
||||
aria-label="Delete"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-trash p-1 br-8 mb-1">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path
|
||||
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2">
|
||||
</path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<!-- include required js cdn link through html here -->
|
||||
{% include "cdn_through_html/datatable_cdn_js.html" %}
|
||||
|
||||
<script>
|
||||
category = $('#category').DataTable({
|
||||
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
|
||||
"<'table-responsive'tr>" +
|
||||
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
|
||||
"oLanguage": {
|
||||
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
|
||||
"sInfo": "Showing page _PAGE_ of _PAGES_",
|
||||
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
|
||||
"sSearchPlaceholder": "Search...",
|
||||
"sLengthMenu": "Results : _MENU_",
|
||||
},
|
||||
"stripeClasses": [],
|
||||
"lengthMenu": [5, 10, 20, 50],
|
||||
"pageLength": 10
|
||||
});
|
||||
|
||||
new_article = $('#new-article').DataTable({
|
||||
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
|
||||
"<'table-responsive'tr>" +
|
||||
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
|
||||
"oLanguage": {
|
||||
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
|
||||
"sInfo": "Showing page _PAGE_ of _PAGES_",
|
||||
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
|
||||
"sSearchPlaceholder": "Search...",
|
||||
"sLengthMenu": "Results : _MENU_",
|
||||
},
|
||||
"stripeClasses": [],
|
||||
"lengthMenu": [5, 10, 20, 50],
|
||||
"pageLength": 10
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,175 +0,0 @@
|
||||
{% extends 'layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
{% include "cdn_through_html/datatable_cdn_css.html" %}
|
||||
{% include "cdn_through_html/tabs_cdn_css.html" %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<h3>Manage Stock</h3>
|
||||
</div>
|
||||
<div class="col text-end">
|
||||
{% comment %} <button class="btn btn-dark mb-2 me-4" onclick="history.back()">
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
Back
|
||||
</button> {% endcomment %}
|
||||
<a class="btn btn-success mb-2 me-4" href="{% url 'manage_stock:stock_index_add' %}">Add Stock Index</a>
|
||||
<a class="btn btn-primary mb-2 me-4" href="{% url 'manage_stock:stock_add' %}">Add Stock</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div id="tabsSimple" class="col-xl-12 col-12 layout-spacing">
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
<div class="simple-pill">
|
||||
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="pills-category-tab" data-bs-toggle="pill" data-bs-target="#pills-category" type="button" role="tab" aria-controls="pills-category" aria-selected="false">Stock Index List</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="pills-new-article-tab" data-bs-toggle="pill" data-bs-target="#pills-new-article" type="button" role="tab" aria-controls="pills-new-article" aria-selected="true">Stock List</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="pills-tabContent">
|
||||
<div class="tab-pane fade" id="pills-category" role="tabpanel" aria-labelledby="pills-category-tab" tabindex="0">
|
||||
<div id="category_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="category" class="table style-3 dt-table-hover dataTable no-footer" role="grid"
|
||||
aria-describedby="style-3_info">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th class="checkbox-column sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Record Id </th>
|
||||
<th class="sorting" tabindex="0" aria-controls="style-3"
|
||||
colspan="1"
|
||||
style="width: 44.2344px;">Category Name</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="style-3"
|
||||
|
||||
style="width: 79.7969px;">Active</th>
|
||||
<th class="dt-no-sorting sorting" tabindex="0"
|
||||
aria-controls="style-3"
|
||||
style="width: 51.625px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for category in stock_price_obj %}
|
||||
<tr role="row">
|
||||
<td class="checkbox-column text-center sorting_1"> {{category.id}}</td>
|
||||
<td>{{category.title}}</td>
|
||||
<td class="text-center">
|
||||
<span class="shadow-none badge {% if category.active %}badge-primary{% else %}badge-danger{% endif %}">{{category.active}}</span>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade active show" id="pills-new-article" role="tabpanel" aria-labelledby="pills-new-article-tab" tabindex="0">
|
||||
<div id="new-article_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
|
||||
<div class="table-responsive">
|
||||
<table id="new-article" class="table style-3 dt-table-hover dataTable no-footer" role="grid"
|
||||
aria-describedby="style-3_info">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th class="checkbox-column text-center sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" rowspan="1" colspan="1" aria-sort="ascending"
|
||||
|
||||
style="width: 69.2656px;"> Record Id </th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
|
||||
colspan="1"
|
||||
style="width: 44.2344px;">Category</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
|
||||
|
||||
style="width: 79.7969px;">Title</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
|
||||
|
||||
|
||||
|
||||
style="width: 51.625px;">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for data_obj in stock_price_obj %}
|
||||
<tr role="row">
|
||||
<td class="checkbox-column text-center sorting_1">{{data_obj.id}}</td>
|
||||
|
||||
<td>{{data_obj.index_type.title}}</td>
|
||||
<td>{{data_obj.title}}</td>
|
||||
|
||||
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<!-- include required js cdn link through html here -->
|
||||
{% include "cdn_through_html/datatable_cdn_js.html" %}
|
||||
|
||||
<script>
|
||||
category = $('#category').DataTable({
|
||||
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
|
||||
"<'table-responsive'tr>" +
|
||||
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
|
||||
"oLanguage": {
|
||||
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
|
||||
"sInfo": "Showing page _PAGE_ of _PAGES_",
|
||||
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
|
||||
"sSearchPlaceholder": "Search...",
|
||||
"sLengthMenu": "Results : _MENU_",
|
||||
},
|
||||
"stripeClasses": [],
|
||||
"lengthMenu": [5, 10, 20, 50],
|
||||
"pageLength": 10
|
||||
});
|
||||
|
||||
new_article = $('#new-article').DataTable({
|
||||
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
|
||||
"<'table-responsive'tr>" +
|
||||
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
|
||||
"oLanguage": {
|
||||
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
|
||||
"sInfo": "Showing page _PAGE_ of _PAGES_",
|
||||
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
|
||||
"sSearchPlaceholder": "Search...",
|
||||
"sLengthMenu": "Results : _MENU_",
|
||||
},
|
||||
"stripeClasses": [],
|
||||
"lengthMenu": [5, 10, 20, 50],
|
||||
"pageLength": 10
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,156 +0,0 @@
|
||||
{% extends 'layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
{% include "cdn_through_html/datatable_cdn_css.html" %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h3>{{data_objs.0.team.title}}</h3>
|
||||
</div>
|
||||
<div class="col-sm-6 text-md-end">
|
||||
<!--
|
||||
<button class="btn btn-dark mb-2 me-md-4" onclick="history.back()">
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
Back
|
||||
</button>
|
||||
-->
|
||||
{% comment %} <a class="btn btn-primary mb-2 me-md-4" href="{% url 'manage_games:game_entryfee_add' %}">Example form</a> {% endcomment %}
|
||||
{% comment %} <a class="btn btn-primary mb-2" href="{% url 'manage_games:game_entryfee_add' %}">Add Entry Fee</a> {% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row layout-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
<div id="style-3_wrapper" class="dataTables_wrapper container-fluid dt-bootstrap4 no-footer">
|
||||
<div class="table-responsive">
|
||||
<table id="style-3" class="table style-3 dt-table-hover dataTable no-footer" role="grid"
|
||||
aria-describedby="style-3_info">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<!-- <th class="checkbox-column text-center sorting_asc" tabindex="0"
|
||||
aria-controls="style-3" rowspan="1" colspan="1" aria-sort="ascending"
|
||||
style="width: 69.2656px;"> Record Id </th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
|
||||
style="width: 44.2344px;">Team</th> -->
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
|
||||
style="width: 44.2344px;">Stock</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
|
||||
style="width: 44.2344px;">Quantity</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
|
||||
style="width: 44.2344px;">Operation</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
|
||||
style="width: 44.2344px;">Position</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
|
||||
style="width: 44.2344px;">Last Closing</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
|
||||
style="width: 44.2344px;">Current Closing</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
|
||||
style="width: 44.2344px;">Current Closing Percentage</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1"
|
||||
style="width: 44.2344px;">Stock Score</th>
|
||||
<th class="text-center sorting" tabindex="0" aria-controls="style-3" rowspan="1" colspan="1"
|
||||
style="width: 98.875px;">Active</th>
|
||||
<!-- <th class="text-center dt-no-sorting" tabindex="0"
|
||||
aria-controls="style-3" rowspan="1" colspan="1"
|
||||
style="width: 51.625px;">Action</th> -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for data_obj in data_objs %}
|
||||
<tr role="row">
|
||||
<!-- <td class="checkbox-column text-center sorting_1"> {{ data_obj.id }} </td>
|
||||
|
||||
<td>{{ data_obj.team }}</td> -->
|
||||
<td>{{ data_obj.stock }}</td>
|
||||
<td>{{ data_obj.quantity }}</td>
|
||||
<td>{{ data_obj.get_operation_display }}</td>
|
||||
<td>{{ data_obj.get_position_display }}</td>
|
||||
<td>{{ data_obj.last_closing }}</td>
|
||||
<td>{{ data_obj.current_closing }}</td>
|
||||
<td>{{ data_obj.current_closing_percentage }}</td>
|
||||
<td>{{ data_obj.stock_score }}</td>
|
||||
|
||||
<td class="text-center">
|
||||
<span class="shadow-none badge {% if data_obj.active %}badge-primary{% else %}badge-danger{% endif %}">
|
||||
{{ data_obj.active }}
|
||||
</span>
|
||||
</td>
|
||||
<!-- <td class="text-center">
|
||||
<ul class="table-controls">
|
||||
<li><a href="{% url 'manage_games:game_entryfee_edit' data_obj.id%}" class="bs-tooltip"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title=""
|
||||
data-original-title="Edit" data-bs-original-title="Edit"
|
||||
aria-label="Edit"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-edit-2 p-1 br-8 mb-1">
|
||||
<path
|
||||
d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z">
|
||||
</path>
|
||||
</svg></a></li>
|
||||
<li><a href="javascript:void(0);" class="bs-tooltip"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title=""
|
||||
data-original-title="Delete" data-bs-original-title="Delete"
|
||||
aria-label="Delete"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-trash p-1 br-8 mb-1">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path
|
||||
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2">
|
||||
</path>
|
||||
</svg></a></li>
|
||||
</ul>
|
||||
</td> -->
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<!-- include required js cdn link through html here -->
|
||||
{% include "cdn_through_html/datatable_cdn_js.html" %}
|
||||
|
||||
<script>
|
||||
c3 = $('#style-3').DataTable({
|
||||
"dom": "<'dt--top-section'<'row'<'col-12 col-sm-6 d-flex justify-content-sm-start justify-content-center'l><'col-12 col-sm-6 d-flex justify-content-sm-end justify-content-center mt-sm-0 mt-3'f>>>" +
|
||||
"<'table-responsive'tr>" +
|
||||
"<'dt--bottom-section d-sm-flex justify-content-sm-between text-center'<'dt--pages-count mb-sm-0 mb-3'i><'dt--pagination'p>>",
|
||||
"oLanguage": {
|
||||
"oPaginate": { "sPrevious": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>', "sNext": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>' },
|
||||
"sInfo": "Showing page _PAGE_ of _PAGES_",
|
||||
"sSearch": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
|
||||
"sSearchPlaceholder": "Search...",
|
||||
"sLengthMenu": "Results : _MENU_",
|
||||
},
|
||||
"stripeClasses": [],
|
||||
"lengthMenu": [5, 10, 20, 50],
|
||||
"pageLength": 20
|
||||
});
|
||||
|
||||
multiCheck(c3);
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -67,69 +67,3 @@
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
|
||||
{% include "cdn_through_html/filepond_cdn_js.html" %}
|
||||
{% include "cdn_through_html/quill_cdn_js.html" %}
|
||||
{% include "cdn_through_html/tagify_cdn_js.html" %}
|
||||
|
||||
|
||||
<script>
|
||||
/**
|
||||
* ===================================
|
||||
* Blog Description Editor
|
||||
* ===================================
|
||||
*/
|
||||
var quill = new Quill('#description', {
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ header: [1, 2, false] }],
|
||||
['bold', 'italic', 'underline'],
|
||||
['image', 'code-block']
|
||||
]
|
||||
},
|
||||
placeholder: 'Write description...',
|
||||
theme: 'snow' // or 'bubble'
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* ====================
|
||||
* File Pond
|
||||
* ====================
|
||||
*/
|
||||
|
||||
// We want to preview images, so we register
|
||||
// the Image Preview plugin, We also register
|
||||
// exif orientation (to correct mobile image
|
||||
// orientation) and size validation, to prevent
|
||||
// large files from being added
|
||||
FilePond.registerPlugin(
|
||||
FilePondPluginImagePreview,
|
||||
FilePondPluginImageExifOrientation,
|
||||
FilePondPluginFileValidateSize,
|
||||
// FilePondPluginImageEdit
|
||||
);
|
||||
|
||||
// Select the file input and use
|
||||
// create() to turn it into a pond
|
||||
var ecommerce = FilePond.create(document.querySelector('.file-upload-multiple'));
|
||||
|
||||
|
||||
/**
|
||||
* =====================
|
||||
* Blog Tags
|
||||
* =====================
|
||||
*/
|
||||
// The DOM element you wish to replace with Tagify
|
||||
var input = document.querySelector('#id_tags');
|
||||
|
||||
// initialize Tagify on the above input node reference
|
||||
new Tagify(input,{
|
||||
originalInputValueFormat: valuesArr => valuesArr.map(item => item.value).join(', ')
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -130,8 +130,9 @@
|
||||
<div class="Adventure-btn text-center">
|
||||
<input type="text" placeholder="Enter Coupon Code" class="form-control coupon-code-input" size="20">
|
||||
<!-- Add a data attribute to store subscription ID -->
|
||||
<button class="common-btn subscribe-btn" data-subscription-id="{{ subscription.id }}">Join
|
||||
now</button>
|
||||
<button class="common-btn subscribe-btn" data-subscription-id="{{ subscription.id }}">Join now</button>
|
||||
<!-- Error message container -->
|
||||
<div class="alert alert-danger coupon-error-message mt-2" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -518,38 +519,77 @@
|
||||
console.log("Sanity check!");
|
||||
var stripeCheckoutUrl = "{{ stripeCheckoutUrl }}";
|
||||
var stripeFinalUrl = "{{ stripeFinalUrl }}";
|
||||
var couponValidityCheckUrl = "{{ couponValidityCheckUrl }}";
|
||||
console.log("stripeCheckoutUrl: ", stripeCheckoutUrl);
|
||||
console.log("stripeFinalUrl: ", stripeFinalUrl);
|
||||
// Get Stripe publishable key
|
||||
console.log("couponValidityCheckUrl: ", couponValidityCheckUrl);
|
||||
// Geting Stripe publishable key
|
||||
fetch(stripeCheckoutUrl)
|
||||
.then((result) => { return result.json(); })
|
||||
.then((result) => {
|
||||
return result.json();
|
||||
})
|
||||
.then((data) => {
|
||||
// Initialize Stripe.js
|
||||
// Initializing Stripe.js -- getting stripe public key to generate stripe object for creating checkout session
|
||||
const stripe = Stripe(data.publicKey);
|
||||
console.log("loaded stripe public key");
|
||||
document.querySelectorAll(".subscribe-btn").forEach(button => {
|
||||
button.addEventListener("click", () => {
|
||||
const subscriptionId = button.getAttribute("data-subscription-id");
|
||||
const couponCode = button.previousElementSibling.value;
|
||||
const errorMessageContainer = button.nextElementSibling;
|
||||
console.log("subscriptionId: ", subscriptionId);
|
||||
console.log("couponCode: ", couponCode);
|
||||
button.disabled = true;
|
||||
button.previousElementSibling.value = "";
|
||||
// Create checkout session for the selected subscription
|
||||
fetch(stripeFinalUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ subscriptionId: subscriptionId }),
|
||||
})
|
||||
.then((result) => { return result.json(); })
|
||||
.then((data) => {
|
||||
// Redirect to Stripe Checkout
|
||||
return stripe.redirectToCheckout({ sessionId: data.sessionId })
|
||||
|
||||
// Handling any coupon validation errors here before creating stripe final checkout session
|
||||
fetch(couponValidityCheckUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
subscriptionId: subscriptionId,
|
||||
couponCode: couponCode
|
||||
}),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then(data => {
|
||||
throw new Error(data.error);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
// Creating checkout session for the selected subscription
|
||||
fetch(stripeFinalUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
subscriptionId: subscriptionId
|
||||
}),
|
||||
})
|
||||
.then((result) => {
|
||||
return result.json();
|
||||
})
|
||||
.then((data) => {
|
||||
// Redirects to Stripe Checkout
|
||||
return stripe.redirectToCheckout({
|
||||
sessionId: data.sessionId
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
button.disabled = false;
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
errorMessageContainer.style.display = 'block';
|
||||
errorMessageContainer.innerText = error.message;
|
||||
button.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user