introduce service plans
This commit is contained in:
parent
f69f7fb755
commit
70f4a02db9
11 changed files with 362 additions and 20 deletions
|
@ -5,9 +5,34 @@ from .models import (
|
||||||
ConsultingPartner,
|
ConsultingPartner,
|
||||||
Service,
|
Service,
|
||||||
Category,
|
Category,
|
||||||
|
Currency,
|
||||||
|
Plan,
|
||||||
|
PlanPrice,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlanPriceInline(admin.TabularInline):
|
||||||
|
model = PlanPrice
|
||||||
|
extra = 1
|
||||||
|
min_num = 1
|
||||||
|
verbose_name = "Price"
|
||||||
|
verbose_name_plural = "Prices"
|
||||||
|
|
||||||
|
|
||||||
|
class PlanInline(admin.StackedInline):
|
||||||
|
model = Plan
|
||||||
|
extra = 1
|
||||||
|
show_change_link = True
|
||||||
|
fields = ("name", "description", "is_default", "features", "order")
|
||||||
|
classes = ("collapse",)
|
||||||
|
inlines = [PlanPriceInline]
|
||||||
|
|
||||||
|
def get_formset(self, request, obj=None, **kwargs):
|
||||||
|
formset = super().get_formset(request, obj, **kwargs)
|
||||||
|
formset.request = request
|
||||||
|
return formset
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Category)
|
@admin.register(Category)
|
||||||
class CategoryAdmin(admin.ModelAdmin):
|
class CategoryAdmin(admin.ModelAdmin):
|
||||||
list_display = ("name", "slug", "parent", "order")
|
list_display = ("name", "slug", "parent", "order")
|
||||||
|
@ -36,7 +61,6 @@ class ServiceAdmin(admin.ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"name",
|
"name",
|
||||||
"cloud_provider",
|
"cloud_provider",
|
||||||
"price",
|
|
||||||
"logo_preview",
|
"logo_preview",
|
||||||
"category_list",
|
"category_list",
|
||||||
"partner_list",
|
"partner_list",
|
||||||
|
@ -49,6 +73,7 @@ class ServiceAdmin(admin.ModelAdmin):
|
||||||
filter_horizontal = ("categories", "consulting_partners")
|
filter_horizontal = ("categories", "consulting_partners")
|
||||||
search_fields = ("name", "description", "slug")
|
search_fields = ("name", "description", "slug")
|
||||||
prepopulated_fields = {"slug": ("name",)}
|
prepopulated_fields = {"slug": ("name",)}
|
||||||
|
inlines = [PlanInline]
|
||||||
|
|
||||||
def logo_preview(self, obj):
|
def logo_preview(self, obj):
|
||||||
if obj.logo:
|
if obj.logo:
|
||||||
|
@ -66,6 +91,9 @@ class ServiceAdmin(admin.ModelAdmin):
|
||||||
partner_list.short_description = "Consulting Partners"
|
partner_list.short_description = "Consulting Partners"
|
||||||
category_list.short_description = "Categories"
|
category_list.short_description = "Categories"
|
||||||
|
|
||||||
|
class Media:
|
||||||
|
css = {"all": ("admin/css/hide_inline_header.css",)}
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ConsultingPartner)
|
@admin.register(ConsultingPartner)
|
||||||
class ConsultingPartnerAdmin(admin.ModelAdmin):
|
class ConsultingPartnerAdmin(admin.ModelAdmin):
|
||||||
|
@ -79,3 +107,19 @@ class ConsultingPartnerAdmin(admin.ModelAdmin):
|
||||||
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
|
'<img src="{}" style="max-height: 50px;"/>', obj.logo.url
|
||||||
)
|
)
|
||||||
return "No logo"
|
return "No logo"
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Currency)
|
||||||
|
class CurrencyAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("code", "name", "symbol")
|
||||||
|
search_fields = ("code", "name")
|
||||||
|
ordering = ("code",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Plan)
|
||||||
|
class PlanAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("name", "service", "is_default", "order")
|
||||||
|
list_filter = ("service", "is_default")
|
||||||
|
search_fields = ("name", "description")
|
||||||
|
ordering = ("service", "order", "name")
|
||||||
|
inlines = [PlanPriceInline]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from .models import Lead
|
from .models import Lead, Plan, PlanPrice
|
||||||
|
|
||||||
|
|
||||||
class LeadForm(forms.ModelForm):
|
class LeadForm(forms.ModelForm):
|
||||||
|
@ -12,3 +12,38 @@ class LeadForm(forms.ModelForm):
|
||||||
"email": forms.EmailInput(attrs={"class": "form-control"}),
|
"email": forms.EmailInput(attrs={"class": "form-control"}),
|
||||||
"phone": forms.TextInput(attrs={"class": "form-control"}),
|
"phone": forms.TextInput(attrs={"class": "form-control"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PlanForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Plan
|
||||||
|
fields = ("name", "description", "is_default", "features", "order")
|
||||||
|
widgets = {
|
||||||
|
"description": forms.Textarea(attrs={"rows": 3}),
|
||||||
|
"features": forms.Textarea(attrs={"rows": 4}),
|
||||||
|
}
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
# If this is set as default, ensure no other plan for this service is default
|
||||||
|
if cleaned_data.get("is_default"):
|
||||||
|
service = self.instance.service
|
||||||
|
if service:
|
||||||
|
Plan.objects.filter(service=service).exclude(
|
||||||
|
pk=self.instance.pk
|
||||||
|
).update(is_default=False)
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
class PlanPriceForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = PlanPrice
|
||||||
|
fields = ("currency", "price")
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
currency = cleaned_data.get("currency")
|
||||||
|
price = cleaned_data.get("price")
|
||||||
|
if price and price < 0:
|
||||||
|
raise forms.ValidationError("Price cannot be negative")
|
||||||
|
return cleaned_data
|
||||||
|
|
118
hub/services/migrations/0009_currency_plan_planprice.py
Normal file
118
hub/services/migrations/0009_currency_plan_planprice.py
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
# Generated by Django 5.1.5 on 2025-01-28 09:19
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def create_initial_currencies(apps, schema_editor):
|
||||||
|
Currency = apps.get_model("services", "Currency")
|
||||||
|
currencies = [
|
||||||
|
{"code": "USD", "name": "US Dollar", "symbol": "$"},
|
||||||
|
{"code": "CHF", "name": "Swiss Franc", "symbol": "CHF"},
|
||||||
|
{"code": "EUR", "name": "Euro", "symbol": "€"},
|
||||||
|
]
|
||||||
|
for currency_data in currencies:
|
||||||
|
Currency.objects.create(**currency_data)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_initial_currencies(apps, schema_editor):
|
||||||
|
Currency = apps.get_model("services", "Currency")
|
||||||
|
Currency.objects.all().delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("services", "0008_remove_service_countries_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Currency",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("code", models.CharField(max_length=3, unique=True)),
|
||||||
|
("name", models.CharField(max_length=50)),
|
||||||
|
("symbol", models.CharField(max_length=5)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name_plural": "Currencies",
|
||||||
|
"ordering": ["code"],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Plan",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=100)),
|
||||||
|
("description", models.TextField()),
|
||||||
|
("is_default", models.BooleanField(default=False)),
|
||||||
|
("features", models.TextField(blank=True)),
|
||||||
|
("order", models.IntegerField(default=0)),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"service",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="plans",
|
||||||
|
to="services.service",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"ordering": ["order", "name"],
|
||||||
|
"unique_together": {("service", "name")},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="PlanPrice",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("price", models.DecimalField(decimal_places=2, max_digits=10)),
|
||||||
|
(
|
||||||
|
"currency",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to="services.currency",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"plan",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="prices",
|
||||||
|
to="services.plan",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"unique_together": {("plan", "currency")},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RunPython(create_initial_currencies, remove_initial_currencies),
|
||||||
|
]
|
17
hub/services/migrations/0010_remove_service_price.py
Normal file
17
hub/services/migrations/0010_remove_service_price.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 5.1.5 on 2025-01-28 09:28
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("services", "0009_currency_plan_planprice"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="service",
|
||||||
|
name="price",
|
||||||
|
),
|
||||||
|
]
|
|
@ -12,6 +12,63 @@ def validate_image_size(value):
|
||||||
raise ValidationError("Maximum file size is 1MB")
|
raise ValidationError("Maximum file size is 1MB")
|
||||||
|
|
||||||
|
|
||||||
|
class Currency(models.Model):
|
||||||
|
code = models.CharField(max_length=3, unique=True) # ISO 4217 currency code
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
symbol = models.CharField(max_length=5)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "Currencies"
|
||||||
|
ordering = ["code"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.code} ({self.name})"
|
||||||
|
|
||||||
|
|
||||||
|
class Plan(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
description = ProseEditorField()
|
||||||
|
service = models.ForeignKey(
|
||||||
|
"Service", on_delete=models.CASCADE, related_name="plans"
|
||||||
|
)
|
||||||
|
is_default = models.BooleanField(default=False)
|
||||||
|
features = ProseEditorField(blank=True)
|
||||||
|
order = models.IntegerField(default=0)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["order", "name"]
|
||||||
|
unique_together = [["service", "name"]]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.service.name} - {self.name}"
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.is_default:
|
||||||
|
# Ensure only one default plan per service
|
||||||
|
Plan.objects.filter(service=self.service).update(is_default=False)
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
# If this is the only plan, make it default
|
||||||
|
if self.pk and self.service and self.service.plans.count() == 1:
|
||||||
|
self.is_default = True
|
||||||
|
|
||||||
|
|
||||||
|
class PlanPrice(models.Model):
|
||||||
|
plan = models.ForeignKey(Plan, on_delete=models.CASCADE, related_name="prices")
|
||||||
|
currency = models.ForeignKey(Currency, on_delete=models.PROTECT)
|
||||||
|
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = [["plan", "currency"]]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.plan.name} - {self.currency.code} {self.price}"
|
||||||
|
|
||||||
|
|
||||||
class Category(models.Model):
|
class Category(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
|
@ -103,7 +160,6 @@ class Service(models.Model):
|
||||||
ConsultingPartner, related_name="services", blank=True
|
ConsultingPartner, related_name="services", blank=True
|
||||||
)
|
)
|
||||||
categories = models.ManyToManyField(Category, related_name="services")
|
categories = models.ManyToManyField(Category, related_name="services")
|
||||||
price = models.DecimalField(max_digits=10, decimal_places=2)
|
|
||||||
features = ProseEditorField()
|
features = ProseEditorField()
|
||||||
logo = models.ImageField(
|
logo = models.ImageField(
|
||||||
upload_to="service_logos/",
|
upload_to="service_logos/",
|
||||||
|
@ -133,6 +189,9 @@ class Service(models.Model):
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("services:service_detail", kwargs={"slug": self.slug})
|
return reverse("services:service_detail", kwargs={"slug": self.slug})
|
||||||
|
|
||||||
|
def get_default_plan(self):
|
||||||
|
return self.plans.filter(is_default=True).first() or self.plans.first()
|
||||||
|
|
||||||
|
|
||||||
class Lead(models.Model):
|
class Lead(models.Model):
|
||||||
service = models.ForeignKey(Service, on_delete=models.CASCADE)
|
service = models.ForeignKey(Service, on_delete=models.CASCADE)
|
||||||
|
|
7
hub/services/static/admin/css/hide_inline_header.css
Normal file
7
hub/services/static/admin/css/hide_inline_header.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.inline-group .tabular .has_original td:first-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-group .tabular tr:not(.has_original) td:first-child {
|
||||||
|
display: none;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends "admin/change_form.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
{{ block.super }}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'admin/css/hide_inline_header.css' %}" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block admin_change_form_document_ready %}
|
||||||
|
{{ block.super }}
|
||||||
|
<script>
|
||||||
|
django.jQuery(document).ready(function($) {
|
||||||
|
// Initialize collapse state for plan inlines
|
||||||
|
$('.plan-inline').addClass('collapsed');
|
||||||
|
|
||||||
|
// Add custom styling to price inline tables
|
||||||
|
$('.price-inline').find('table').addClass('price-table');
|
||||||
|
|
||||||
|
// Add helper text for default plan selection
|
||||||
|
$('.field-is_default').append('<p class="help">Only one plan can be default per service.</p>');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -5,12 +5,12 @@
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title mb-4">Show Interest in {{ service.name }}</h2>
|
<h2 class="card-title mb-4">Order {{ service.name }}</h2>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h5>Service Details</h5>
|
<h5>Service Details</h5>
|
||||||
<p><strong>Provider:</strong> {{ service.cloud_provider.name }}</p>
|
<p><strong>Provider:</strong> {{ service.cloud_provider.name }}</p>
|
||||||
<p><strong>Price:</strong> ${{ service.price }}</p>
|
<p><strong>Plan:</strong> {{ selected_plan.name }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
|
|
|
@ -33,13 +33,45 @@
|
||||||
<h5>Features</h5>
|
<h5>Features</h5>
|
||||||
<p>{{ service.features|safe }}</p>
|
<p>{{ service.features|safe }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">Service Details</h5>
|
|
||||||
<p><strong>Price:</strong> ${{ service.price }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<h3 class="mb-4">Plans</h3>
|
||||||
|
<div class="row row-cols-1 row-cols-md-3 g-4">
|
||||||
|
{% for plan in service.plans.all %}
|
||||||
|
<div class="col">
|
||||||
|
<div class="card h-100 {% if plan.is_default %}border-primary{% endif %}">
|
||||||
|
{% if plan.is_default %}
|
||||||
|
<div class="card-header text-primary">
|
||||||
|
Recommended
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{ plan.name }}</h5>
|
||||||
|
<div class="card-text mb-3">{{ plan.description|safe }}</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
{% for price in plan.prices.all %}
|
||||||
|
<div class="mb-1">
|
||||||
|
<strong>{{ price.currency.symbol }}</strong> {{ price.price }}
|
||||||
|
<small class="text-muted">{{ price.currency.code }}</small>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if plan.features %}
|
||||||
|
<div class="rich-text-content">
|
||||||
|
{{ plan.features|safe }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<a href="{% url 'services:create_lead' service.slug %}?plan={{ plan.id }}"
|
||||||
|
class="btn btn-primary mt-3">Order</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,17 +12,12 @@
|
||||||
<p>We have received your inquiry and our team will contact you shortly.</p>
|
<p>We have received your inquiry and our team will contact you shortly.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<h5>Service Details</h5>
|
|
||||||
<p><strong>Provider:</strong> {{ service.cloud_provider.name }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<p class="text-muted">A confirmation email will be sent to your provided email address.</p>
|
<p class="text-muted">A confirmation email will be sent to your provided email address.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<a href="{% url 'services:service_detail' service.id %}" class="btn btn-primary me-2">
|
<a href="{% url 'services:service_detail' service.slug %}" class="btn btn-primary me-2">
|
||||||
Back to Service Details
|
Back to Service Details
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'services:service_list' %}" class="btn btn-secondary">
|
<a href="{% url 'services:service_list' %}" class="btn btn-secondary">
|
||||||
|
|
|
@ -9,6 +9,7 @@ from .models import (
|
||||||
CloudProvider,
|
CloudProvider,
|
||||||
ConsultingPartner,
|
ConsultingPartner,
|
||||||
Category,
|
Category,
|
||||||
|
Plan,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .forms import LeadForm
|
from .forms import LeadForm
|
||||||
|
@ -86,12 +87,21 @@ def thank_you(request, slug):
|
||||||
|
|
||||||
def create_lead(request, slug):
|
def create_lead(request, slug):
|
||||||
service = get_object_or_404(Service, slug=slug)
|
service = get_object_or_404(Service, slug=slug)
|
||||||
|
selected_plan = None
|
||||||
|
|
||||||
|
if request.GET.get("plan"):
|
||||||
|
selected_plan = get_object_or_404(
|
||||||
|
Plan, id=request.GET.get("plan"), service=service
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
selected_plan = service.get_default_plan()
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = LeadForm(request.POST)
|
form = LeadForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
lead = form.save(commit=False)
|
lead = form.save(commit=False)
|
||||||
lead.service = service
|
lead.service = service
|
||||||
|
lead.plan = selected_plan
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info(f"Attempting to create lead for service: {service.name}")
|
logger.info(f"Attempting to create lead for service: {service.name}")
|
||||||
|
@ -115,5 +125,7 @@ def create_lead(request, slug):
|
||||||
form = LeadForm()
|
form = LeadForm()
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request, "services/lead_form.html", {"form": form, "service": service}
|
request,
|
||||||
|
"services/lead_form.html",
|
||||||
|
{"form": form, "service": service, "selected_plan": selected_plan},
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue