refactor(subscription): removed unnecessary code

This commit is contained in:
bobbyvish
2024-08-23 12:26:09 +05:30
parent 5d107ad17a
commit 1f580c099d
19 changed files with 75 additions and 717 deletions

View File

@@ -51,7 +51,7 @@ STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR.joinpath("static")]
STRIPE_CHECKOUT_URL = "https://c2f5-122-179-140-110.ngrok-free.app/subscriptions/create-checkout-session/"
COUPON_VALIDITY_CHECK_URL = "https://c2f5-122-179-140-110.ngrok-free.app/subscriptions/coupon-validity-check/"
STRIPE_CHECKOUT_URL = "https://deciding-firmly-fly.ngrok-free.app/subscriptions/create-checkout-session/"
COUPON_VALIDITY_CHECK_URL = "https://deciding-firmly-fly.ngrok-free.app/subscriptions/coupon-validity-check/"
LOGO_PATH = "static"

View File

@@ -30,10 +30,10 @@ class SubscriptionService:
):
"""Create a principal subscription and return it."""
start_date, end_date = self._calculate_dates(
current_period_start, current_period_end, subscription.calulate_days()
current_period_start, current_period_end, subscription.calculate_days()
)
PrincipalSubscription.objects.filter(principal=principal, status=SubscriptionStatus.ACTIVE).update(status=SubscriptionStatus.EXPIRED)
PrincipalSubscription.objects.filter(principal=principal, status=SubscriptionStatus.ACTIVE).update(status=SubscriptionStatus.EXPIRED, active=False)
principal_subscription = PrincipalSubscription.objects.create(
principal=principal,

View File

@@ -110,7 +110,6 @@ class Command(BaseCommand):
return IAmPrincipalNotificationSettings.objects.filter(
principal__principal_subscription__end_date=target_date,
principal__principal_subscription__status=SubscriptionStatus.ACTIVE,
principal__principal_subscription__cancelled=False,
principal__principal_subscription__deleted=False,
notification_category=NotificationCategoryChoices.SUBSCRIPTION,
# is_enabled=True,

View File

@@ -1,32 +1,19 @@
from django.contrib import admin
from .models import (
Plan,
PrincipalSubscription,
StripeProduct,
Subscription,
WebhookEvent,
) # Update this with the correct import path for your models
# Plan ModelAdmin
class PlanAdmin(admin.ModelAdmin):
list_display = ("id", "title", "days") # Include 'id' field here
search_fields = ("title",) # Add search functionality by title
# Register Plan with the admin site
admin.site.register(Plan, PlanAdmin)
# Subscription ModelAdmin
class SubscriptionAdmin(admin.ModelAdmin):
list_display = ("id", "title", "plan", "amount") # Include 'id' field here
list_select_related = ("plan",) # Optimizes queries for the plan field
list_display = ("id", "title", "interval", "amount") # Include 'id' field here
list_select_related = ("interval",) # Optimizes queries for the interval field
search_fields = (
"title",
"plan__title",
) # Add search functionality by title and plan's title
raw_id_fields = ("plan",) # Use a raw ID widget for the plan ForeignKey field
"interval",
) # Add search functionality by title and interval's title
# Register Subscription with the admin site
@@ -48,7 +35,7 @@ class PrincipalSubscriptionAdmin(admin.ModelAdmin):
"is_paid",
"auto_renew",
"status",
"cancelled",
# "cancelled",
) # Enable filtering by these fields
search_fields = (
"subscription__title",
@@ -64,27 +51,6 @@ class PrincipalSubscriptionAdmin(admin.ModelAdmin):
admin.site.register(PrincipalSubscription, PrincipalSubscriptionAdmin)
class StripeProductAdmin(admin.ModelAdmin):
list_display = ("id", "title", "product_id", "default_price_id")
search_fields = ("title", "product_id", "description")
list_filter = ("default_price_id",)
readonly_fields = ("product_id", "default_price_id")
fields = (
"title",
"description",
"metadata",
"image_url",
"product_id",
"default_price_id",
"active",
"deleted",
)
admin.site.register(StripeProduct, StripeProductAdmin)
@admin.register(WebhookEvent)
class WebhookEventAdmin(admin.ModelAdmin):
list_display = ("event_id", "received_at", "event_type", "processed_at", "status")

View File

@@ -315,7 +315,7 @@ class CancelSubscription(APIView):
)
with transaction.atomic():
if subscription.is_stripe_subscription:
if subscription.stripe_subscription_id:
# Cancel Stripe subscription
try:
stripe.Subscription.modify(subscription.stripe_subscription_id, cancel_at_period_end=True)
@@ -328,7 +328,7 @@ class CancelSubscription(APIView):
# Updating subscription status in the local database
subscription.status = SubscriptionStatus.INACTIVE
subscription.cancelled = True
# subscription.cancelled = True
subscription.cancelled_date_time = timezone.now()
subscription.save()

View File

@@ -2,25 +2,9 @@ from django import forms
from accounts.models import IAmPrincipalType
from manage_subscriptions.models import (
PrincipalSubscription,
StripeProduct,
Subscription,
Plan,
)
class PlanForm(forms.ModelForm):
class Meta:
model = Plan
fields = ["title", "days"] # Include all fields you want from the model
# You can add custom validation for Plan fields here if needed
# Example:
# def clean_title(self):
# title = self.cleaned_data.get('title')
# # Add your validation logic here
# return title
class SubscriptionForm(forms.ModelForm):
class Meta:
model = Subscription
@@ -66,13 +50,3 @@ class PrincipalSubscriptionForm(forms.ModelForm):
}
class StripeProductForm(forms.ModelForm):
class Meta:
model = StripeProduct
fields = [
"title",
"description",
]
widgets = {
"description": forms.Textarea(attrs={"rows": 3}),
}

View File

@@ -0,0 +1,43 @@
# Generated by Django 5.0.2 on 2024-08-21 18:10
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('manage_subscriptions', '0012_subscription_interval_subscription_interval_count'),
]
operations = [
migrations.RemoveField(
model_name='subscription',
name='plan',
),
migrations.RemoveField(
model_name='stripeproduct',
name='created_by',
),
migrations.RemoveField(
model_name='stripeproduct',
name='modified_by',
),
migrations.RemoveField(
model_name='subscription',
name='stripe_product',
),
migrations.RemoveField(
model_name='principalsubscription',
name='cancelled',
),
migrations.RemoveField(
model_name='principalsubscription',
name='is_stripe_subscription',
),
migrations.DeleteModel(
name='Plan',
),
migrations.DeleteModel(
name='StripeProduct',
),
]

View File

@@ -5,34 +5,6 @@ from django.core.exceptions import ValidationError
from accounts.models import BaseModel, IAmPrincipal, IAmPrincipalType
from django.utils.translation import gettext_lazy as _
# Create your models here.
class Plan(BaseModel):
title = models.CharField(max_length=255)
days = models.PositiveIntegerField()
class Meta:
db_table = "plan"
def __str__(self):
return self.title
class StripeProduct(BaseModel):
title = models.CharField(max_length=255)
product_id = models.CharField(max_length=255, blank=True, null=True)
description = models.TextField(blank=True, null=True)
metadata = models.JSONField(blank=True, null=True)
image_url = models.URLField(blank=True, null=True)
default_price_id = models.CharField(max_length=255, blank=True, null=True)
class Meta:
db_table = "stripe_product"
def __str__(self):
return self.title
class Subscription(BaseModel):
MONTH = "month"
@@ -49,19 +21,9 @@ class Subscription(BaseModel):
title = models.CharField(max_length=255)
price_id = models.CharField(max_length=255, blank=True, null=True)
product_id = models.CharField(max_length=255, blank=True, null=True)
stripe_product = models.ForeignKey(
StripeProduct,
related_name="subscription_product",
on_delete=models.CASCADE,
null=True,
blank=True,
)
short_description = models.CharField(max_length=255, null=True, blank=True)
long_description = models.TextField(null=True, blank=True)
image = models.ImageField(upload_to="subscription_img", null=True, blank=True)
plan = models.ForeignKey(
Plan, related_name="subscription_plan", on_delete=models.CASCADE
)
interval = models.CharField(max_length=10, choices=INTERVAL_TYPES)
interval_count = models.IntegerField(default=1)
high_amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
@@ -110,13 +72,13 @@ class Subscription(BaseModel):
# Create new product and price
price = StripeService.create_price(
product_data={
"name": self.title,
"name": self.txitle,
"description": self.short_description,
},
unit_amount=int(self.amount * 100),
currency="gbp",
recurring={
"interval": self.plan.title,
"interval": self.interval,
"interval_count": self.interval_count,
},
metadata={
@@ -133,15 +95,14 @@ class Subscription(BaseModel):
super().save(*args, **kwargs)
def calculate_date(self):
count = {
self.DAY: 1,
self.MONTH: 30, # assuming a month is 30 days
self.YEAR: 365,
self.WEEK: 7
}
return count[self.interval] * self.interval_count
def calculate_days(self):
count = {
self.DAY: 1,
self.MONTH: 30, # assuming a month is 30 days
self.YEAR: 365,
self.WEEK: 7
}
return count[self.interval] * self.interval_count
@@ -169,13 +130,11 @@ class PrincipalSubscription(BaseModel):
start_date = models.DateField()
end_date = models.DateField()
order_id = models.CharField(max_length=255, null=True, blank=True)
cancelled = models.BooleanField(default=False)
cancelled_date_time = models.DateTimeField(null=True, blank=True)
grace_period_end_date = models.DateField(null=True, blank=True)
stripe_customer_id = models.CharField(max_length=255, null=True, blank=True)
stripe_subscription_id = models.CharField(max_length=255, null=True, blank=True)
comments = models.CharField(max_length=255, null=True, blank=True)
is_stripe_subscription = models.BooleanField(default=False)
payment_intent_id = models.CharField(max_length=255, null=True, blank=True)
payment_intent_client_secret = models.CharField(
max_length=255, null=True, blank=True
@@ -203,7 +162,6 @@ class PrincipalSubscription(BaseModel):
return cls.objects.filter(
principal=principal,
is_paid=True,
# cancelled=False,
active=True,
status=SubscriptionStatus.ACTIVE,
grace_period_end_date__gt=timezone.now().date(),
@@ -214,18 +172,16 @@ class PrincipalSubscription(BaseModel):
return cls.objects.filter(
principal=principal,
is_paid=True,
# cancelled=False,
active=True,
status=SubscriptionStatus.ACTIVE,
end_date__gt=timezone.now().date(),
)
).order_by('-end_date').last()
@classmethod
def get_principal_subscription(cls, principal):
return cls.objects.filter(
principal=principal,
is_paid=True,
# cancelled=False,
active=True,
status=SubscriptionStatus.ACTIVE,
).order_by("-grace_period_end_date").first()

View File

@@ -18,17 +18,6 @@ urlpatterns = [
views.SubscriptionDeleteView.as_view(),
name="subscription_delete",
),
# Stripe Products
path(
"product/list/", views.StripeProductView.as_view(), name="stripe_product_list"
),
path(
"product/add/",
views.StripeProductCreateOrUpdateView.as_view(),
name="stripe_product_add",
),
# PLANS
path("plan/list/", views.PlanView.as_view(), name="plan_list"),
# Principal Subscription
path(
@@ -36,11 +25,6 @@ urlpatterns = [
views.PrincipalSubscriptionView.as_view(),
name="principal_subscriptions_list",
),
# path(
# "principal_subscription/add/",
# views.PrincipalSubscriptionCreateOrUpdateView.as_view(),
# name="principal_subscription_add",
# ),
path(
"principal_subscription/edit/<int:pk>/",
views.PrincipalSubscriptionCreateOrUpdateView.as_view(),
@@ -56,11 +40,6 @@ urlpatterns = [
views.PrincipalSubscriptionDeleteView.as_view(),
name="principal_subscription_delete",
),
path(
"stripe-subscription/",
views.stripe_config,
name="stripe_subscription",
),
path(
"create-checkout-session/",
views.create_checkout_session,

View File

@@ -8,26 +8,6 @@ API_KEY = settings.GOOGLE_MAPS_API_KEY
gmaps = googlemaps.Client(key=API_KEY)
def get_active_subscription_id_for_principal(principal):
# Filter subscriptions for the principal that are active and not cancelled
active_subscriptions = PrincipalSubscription.objects.filter(
principal=principal,
status=SubscriptionStatus.ACTIVE,
is_paid=True,
cancelled=False,
deleted=False,
active=True,
end_date__gte=now().date(), # Ensure the subscription hasn't expired
).order_by(
"-end_date"
) # Order by end_date to get the most recent active subscription
if active_subscriptions.exists():
# Return the ID of the most recent active subscription
return active_subscriptions.first().id
return None
def get_location_info(latitude, longitude):
reverse_geocode_result = gmaps.reverse_geocode((latitude, longitude))

View File

@@ -12,7 +12,6 @@ from django.contrib.auth import get_user_model
from goodtimes.services import StripeService
from manage_coupons.models import Coupon
from manage_subscriptions.forms import (
StripeProductForm,
SubscriptionForm,
PrincipalSubscriptionForm,
)
@@ -23,8 +22,6 @@ from manage_wallets.models import (
TransactionType,
)
from .models import (
Plan,
StripeProduct,
Subscription,
PrincipalSubscription,
SubscriptionStatus,
@@ -179,129 +176,6 @@ class SubscriptionDeleteView(LoginRequiredMixin, generic.View):
return redirect(self.success_url)
class StripeProductCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
# Initialize the action as ACTION_CREATE (can change based on logic)
action = resource_action.ACTION_CREATE # Default action
template_name = "manage_subscriptions/product_add.html"
model = StripeProduct
form_class = StripeProductForm
success_url = reverse_lazy("manage_subscriptions:stripe_product_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, 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)
success, message = self.handle_stripe_product(form)
if not success:
messages.error(self.request, message)
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)
def handle_stripe_product(self, form):
try:
stripe.api_key = settings.STRIPE_SECRET_KEY
stripe_product = stripe.Product.create(
name=form.cleaned_data.get("title"),
description=form.cleaned_data.get("description"),
)
# Save Stripe Product ID to the form instance
form.instance.product_id = stripe_product.id
return True, "" # Success
except stripe.error.StripeError as e:
return False, f"Stripe error: {str(e)}"
except Exception as e:
return False, f"An error occurred: {str(e)}"
class StripeProductView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
action = resource_action.ACTION_READ
model = StripeProduct
template_name = "manage_subscriptions/product_list.html"
context_object_name = "product_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 PlanView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
action = resource_action.ACTION_READ
model = Plan
template_name = "manage_subscriptions/plan_list.html"
context_object_name = "plan_obj"
def get_queryset(self):
return super().get_queryset().filter(deleted=False)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_name"] = self.page_name
return context
class PrincipalSubscriptionCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
@@ -474,11 +348,9 @@ class ActiveSubscriptionView(generic.View):
today = timezone.now().date()
if request.user.is_authenticated:
latest_subscription = PrincipalSubscription.objects.filter(
principal=request.user,
is_paid=True,
end_date__gte=today,
).order_by('-end_date').last()
latest_subscription = PrincipalSubscription.get_active_princial_subscription(request.user)
print(f"latest subscription reodr is {latest_subscription}")
if not latest_subscription:
return HttpResponseRedirect(reverse("manage_subscriptions:stripe"))
@@ -549,13 +421,6 @@ class CancelAutoSubscriptionView(LoginRequiredMixin, generic.View):
# return redirect("manage_subscriptions:subscription_cancel_fails")
@csrf_exempt
def stripe_config(request):
if request.method == "GET":
stripe_config = {"publicKey": settings.STRIPE_PUBLISH_KEY}
return JsonResponse(stripe_config, safe=False)
@csrf_exempt
@require_POST
def validate_coupon(request):

View File

@@ -154,7 +154,7 @@
</a>
</li>
{% endif %}
{% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_COUPONS %}
{% comment %} {% 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">
@@ -164,7 +164,7 @@
</div>
</a>
</li>
{% endif %}
{% endif %} {% endcomment %}
{% 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"

View File

@@ -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>{{operation}} {{page_name}}</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" novalidate>
{% 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 &amp; 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 %}

View File

@@ -1,107 +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>Manage Plans</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_subscriptions:subscription_list' %}">Subscriptions</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;"> Days </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 plan_obj %}
<tr role="row">
<td class="checkbox-column text-center sorting_1"> {{data_obj.id}}</td>
<td>{{data_obj.title}}</td>
<td>{{data_obj.days}}</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>
</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 %}

View File

@@ -53,16 +53,17 @@
</div>
<!-- Cancellation Button -->
{% if principal_subscription_obj.auto_renew and not principal_subscription_obj.cancelled %}
{% if principal_subscription_obj.auto_renew and not principal_subscription_obj.cancelled_date_time %}
<div class="col-md-12 mb-4">
<div class="card shadow-sm bg-dark text-light border-gold">
<div class="card-body text-center">
<h5 class="text-gold">Cancel Subscription</h5>
<form method="POST" action="{% url 'manage_subscriptions:cancel_subscription' %}">
<a class="btn btn-primary" href="{% url 'manage_subscriptions:cancel_subscription' subscription_id=principal_subscription_obj.id %}">Cancel Subscription</a>
{% comment %} <form method="POST" action="{% url 'manage_subscriptions:cancel_subscription' %}">
{% csrf_token %}
<input type="hidden" name="subscription_id" value="{{ principal_subscription_obj.id }}">
<button type="submit" class="btn btn-outline-gold">Cancel Subscription</button>
</form>
</form> {% endcomment %}
</div>
</div>
</div>

View File

@@ -1,49 +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 Product</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" novalidate>
{% 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>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@@ -1,104 +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>Manage Products</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_subscriptions:stripe_product_add' %}">Add Products</a> -->
<!-- <a class="btn btn-primary mb-2" href="{% url 'manage_subscriptions:plan_list' %}">Plans</a>
<a class="btn btn-primary mb-2" href="{% url 'manage_subscriptions:principal_subscriptions_list' %}">Principal Subscription</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;"> Stripe Product ID </th>
<th class="sorting" tabindex="7" aria-controls="style-3"
style="width: 79.7969px;">Active</th>
</tr>
</thead>
<tbody>
{% for data_obj in product_obj %}
<tr role="row">
<td class="checkbox-column text-center sorting_1"> {{data_obj.id}}</td>
<td>{{data_obj.title}}</td>
<td>{{data_obj.product_id}}</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>
</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 %}

View File

@@ -15,17 +15,7 @@
<h3>Manage Subscriptions</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_subscriptions:stripe_product_list' %}">Products</a>
<a class="btn btn-primary mb-2" href="{% url 'manage_subscriptions:subscription_add' %}">Add Subscriptions</a>
<a class="btn btn-primary mb-2" href="{% url 'manage_subscriptions:stripe_product_add' %}">Add Stripe Product</a>
<!-- <a class="btn btn-primary mb-2" href="{% url 'manage_subscriptions:plan_list' %}">Plans</a>
<a class="btn btn-primary mb-2" href="{% url 'manage_subscriptions:principal_subscriptions_list' %}">Principal Subscription</a> -->
</div>
</div>

View File

@@ -116,7 +116,7 @@
<h2 class="card-title text-gold">404</h2>
</div>
<div class="card-body">
<h5 class="text-gold">An error occurred. Please try again later.</h5>
<h5 class="text-gold">Something went wrong. Please try again later.</h5>
</div>
</div>
</div>