forked from uncloud/uncloud
Add support for onetimeprice and productordering
This commit is contained in:
parent
f08269640d
commit
ba9bf94d8e
10 changed files with 184 additions and 47 deletions
|
@ -1,13 +1,27 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
class ProductOrderForm(forms.Form):
|
class ProductOneTimeOrderForm(forms.Form):
|
||||||
|
"""
|
||||||
|
For products that only contain onetimeresoures
|
||||||
|
"""
|
||||||
|
|
||||||
product = forms.SlugField(required=True, disabled=True)
|
product = forms.SlugField(required=True, disabled=True)
|
||||||
timeframe = forms.SlugField(required=True, disabled=True)
|
|
||||||
|
|
||||||
def __init__(self, resources, *args, **kwargs):
|
def __init__(self, resources, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
for res in resources:
|
for res in resources:
|
||||||
print(res)
|
print(res)
|
||||||
field_name = f"{res.name}"
|
field_name = f"{res.slug}"
|
||||||
#field_name = f"resource_{res.slug}"
|
self.fields[field_name] = forms.FloatField(required=True, label=res.name)
|
||||||
self.fields[field_name] = forms.FloatField(required=True)
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
print("Cleaning form myself ...")
|
||||||
|
|
||||||
|
|
||||||
|
class ProductOrderForm(ProductOneTimeOrderForm):
|
||||||
|
"""
|
||||||
|
For recurring products (might also have OneTime items
|
||||||
|
"""
|
||||||
|
|
||||||
|
timeframe = forms.SlugField(required=False, disabled=True)
|
||||||
|
|
18
uncloud_v3/app/migrations/0002_resource_onetime_price.py
Normal file
18
uncloud_v3/app/migrations/0002_resource_onetime_price.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.0 on 2022-01-30 09:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='resource',
|
||||||
|
name='onetime_price',
|
||||||
|
field=models.ManyToManyField(blank=True, to='app.OneTimePrice'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.0 on 2022-01-30 10:06
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0002_resource_onetime_price'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='resource',
|
||||||
|
name='onetime_price',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='resource',
|
||||||
|
name='onetime_price',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='app.onetimeprice'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.0 on 2022-01-30 10:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('app', '0003_remove_resource_onetime_price_resource_onetime_price'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='onetimeprice',
|
||||||
|
options={'ordering': ('value',)},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='productorder',
|
||||||
|
name='timeframe',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='app.timeframe'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -41,6 +41,9 @@ class OneTimePrice(models.Model):
|
||||||
value = models.FloatField()
|
value = models.FloatField()
|
||||||
currency = models.ForeignKey(Currency, on_delete=models.CASCADE)
|
currency = models.ForeignKey(Currency, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('value',)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.value} {self.currency.short_name}"
|
return f"{self.value} {self.currency.short_name}"
|
||||||
|
|
||||||
|
@ -61,6 +64,8 @@ class Resource(models.Model):
|
||||||
step_size = models.FloatField(default=1) # step size
|
step_size = models.FloatField(default=1) # step size
|
||||||
|
|
||||||
price_per_time = models.ManyToManyField(PricePerTime, blank=True)
|
price_per_time = models.ManyToManyField(PricePerTime, blank=True)
|
||||||
|
#onetime_price = models.ManyToManyField(OneTimePrice, blank=True)
|
||||||
|
onetime_price = models.ForeignKey(OneTimePrice, null=True, on_delete=models.CASCADE)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.minimum_units:
|
if self.minimum_units:
|
||||||
|
@ -74,7 +79,8 @@ class Resource(models.Model):
|
||||||
|
|
||||||
pricing = ", ".join([str(x) for x in self.price_per_time.all()])
|
pricing = ", ".join([str(x) for x in self.price_per_time.all()])
|
||||||
|
|
||||||
return f"{self.slug}: {minimum}-{maximum} (+/-){self.step_size} {self.unit} ({pricing})"
|
#return f"{self.slug}: {minimum}-{maximum} (+/-){self.step_size} {self.unit} ({pricing})"
|
||||||
|
return f"{self.name} ({self.slug})"
|
||||||
|
|
||||||
|
|
||||||
class Product(models.Model):
|
class Product(models.Model):
|
||||||
|
@ -88,6 +94,16 @@ class Product(models.Model):
|
||||||
resources = models.ManyToManyField(Resource, blank=True) # List of REQUIRED resources
|
resources = models.ManyToManyField(Resource, blank=True) # List of REQUIRED resources
|
||||||
timeframes = models.ManyToManyField(TimeFrame, blank=True) # List of POSSIBLE timeframes
|
timeframes = models.ManyToManyField(TimeFrame, blank=True) # List of POSSIBLE timeframes
|
||||||
|
|
||||||
|
def has_one_time_price(self):
|
||||||
|
has_otp = False
|
||||||
|
|
||||||
|
for res in self.resources.all():
|
||||||
|
if res.onetime_price:
|
||||||
|
has_otp = True
|
||||||
|
break
|
||||||
|
|
||||||
|
return has_otp
|
||||||
|
|
||||||
def valid_timeframes(self):
|
def valid_timeframes(self):
|
||||||
"""
|
"""
|
||||||
Return all timeframes that have all resources configured
|
Return all timeframes that have all resources configured
|
||||||
|
@ -123,17 +139,25 @@ class ResourceOrder(models.Model):
|
||||||
value = models.FloatField()
|
value = models.FloatField()
|
||||||
resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
|
resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.value} x {self.resource}"
|
||||||
|
|
||||||
|
|
||||||
class ProductOrder(models.Model):
|
class ProductOrder(models.Model):
|
||||||
"""
|
"""
|
||||||
Describes a product a user bought
|
Describes a product a user bought
|
||||||
"""
|
"""
|
||||||
product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
||||||
timeframe = models.ForeignKey(TimeFrame, on_delete=models.CASCADE)
|
timeframe = models.ForeignKey(TimeFrame, null=True, blank=True, on_delete=models.CASCADE)
|
||||||
resources = models.ManyToManyField(ResourceOrder)
|
resources = models.ManyToManyField(ResourceOrder)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Order of {self.product} / {self.timeframe}"
|
if self.timeframe:
|
||||||
|
txt = f"Order {self.id}: {self.product} for {self.timeframe}"
|
||||||
|
else:
|
||||||
|
txt = f"Order {self.id}: {self.product}"
|
||||||
|
|
||||||
|
return txt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
29
uncloud_v3/app/services.py
Normal file
29
uncloud_v3/app/services.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
def order_product(product, timeframe, formdata):
|
||||||
|
"""
|
||||||
|
Order a product with given parameters
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(formdata)
|
||||||
|
po = ProductOrder(product=product, timeframe=timeframe)
|
||||||
|
po.save()
|
||||||
|
|
||||||
|
for res, value in formdata.items():
|
||||||
|
print(f"{res}={value}")
|
||||||
|
|
||||||
|
# skip fixed fields
|
||||||
|
if res == 'product' or res == 'timeframe':
|
||||||
|
continue
|
||||||
|
|
||||||
|
resource = get_object_or_404(Resource, slug=res)
|
||||||
|
ro = ResourceOrder.objects.create(value=value, resource=resource)
|
||||||
|
po.resources.add(ro)
|
||||||
|
|
||||||
|
# Ordering without a timeframe
|
||||||
|
# if not timeframe:
|
||||||
|
# product = models.ForeignKey(Product, on_delete=models.CASCADE)
|
||||||
|
# timeframe = models.ForeignKey(TimeFrame, null=True, on_delete=models.CASCADE)
|
||||||
|
# resources = models.ManyToManyField(ResourceOrder)
|
|
@ -7,5 +7,14 @@
|
||||||
<li><a href="{% url 'product-order-tf' object.slug tf.slug %}">Buy
|
<li><a href="{% url 'product-order-tf' object.slug tf.slug %}">Buy
|
||||||
{{ object.name }}
|
{{ object.name }}
|
||||||
for {{ tf }}</a>
|
for {{ tf }}</a>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if not timeframes %}
|
||||||
|
{% if has_one_time_price %}
|
||||||
|
<li><a href="{% url 'product-order-onetime' object.slug %}">Buy
|
||||||
|
{{ object.name }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
<h2>Ordering a {{ product }} instance for {{ timeframe }}</h2>
|
<h2>Order {{ product }}</h2>
|
||||||
|
|
||||||
|
{% if timeframe %}
|
||||||
|
<p>Timeframe: {{ timeframe }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<form method="post" >
|
<form method="post" >
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -7,30 +11,3 @@
|
||||||
</table>
|
</table>
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="product">Product</label>
|
|
||||||
<input type="text" class="form-control"
|
|
||||||
id="product" value={{product.slug}} readonly>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="timeframe">Timeframe</label>
|
|
||||||
<input type="text" class="form-control"
|
|
||||||
id="timeframe" value="{{timeframe.slug}}" readonly>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% for res,price,currency in res_price %}
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="{{res.slug}}">{{res.name}} {{res.unit}}
|
|
||||||
{{ price }}{{ currency.short_name }} per {{ timeframe}} per unit
|
|
||||||
{% if res.minimum_units %} (Min: {{res.minimum_units}}) {%endif%}
|
|
||||||
{% if res.maximum_units %} (Max: {{res.maximum_units}}) {%endif%}
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control"
|
|
||||||
id="{{res.slug}}">
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
|
@ -8,11 +8,14 @@ from django.views.generic.detail import DetailView
|
||||||
|
|
||||||
from .models import *
|
from .models import *
|
||||||
from .forms import *
|
from .forms import *
|
||||||
|
from .services import *
|
||||||
|
|
||||||
class ProductOrderView(FormView):
|
class ProductOneTimeOrderView(FormView):
|
||||||
form_class = ProductOrderForm
|
form_class = ProductOneTimeOrderForm
|
||||||
template_name = 'app/productorder_form.html'
|
template_name = 'app/productorder_form.html'
|
||||||
success_url = '/order/matrix/30-days/'
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return "/"
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
|
@ -23,29 +26,44 @@ class ProductOrderView(FormView):
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
|
"""
|
||||||
|
Initial values for the form
|
||||||
|
"""
|
||||||
|
|
||||||
initial = super().get_initial()
|
initial = super().get_initial()
|
||||||
|
|
||||||
initial['product'] = self.kwargs['product']
|
initial['product'] = self.kwargs['product']
|
||||||
|
|
||||||
|
if 'timeframe' in self.kwargs:
|
||||||
initial['timeframe'] = self.kwargs['timeframe']
|
initial['timeframe'] = self.kwargs['timeframe']
|
||||||
return initial
|
return initial
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['product'] = get_object_or_404(Product, slug=self.kwargs['product'])
|
context['product'] = get_object_or_404(Product, slug=self.kwargs['product'])
|
||||||
|
if 'timeframe' in context:
|
||||||
context['timeframe'] = get_object_or_404(TimeFrame, slug=self.kwargs['timeframe'])
|
context['timeframe'] = get_object_or_404(TimeFrame, slug=self.kwargs['timeframe'])
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
print("We got a valid form, let's create the order")
|
product = get_object_or_404(Product, slug=form.cleaned_data['product'])
|
||||||
|
|
||||||
|
print("We got a valid form, let's create the order, listing fields:\n------")
|
||||||
for f in form.fields:
|
for f in form.fields:
|
||||||
print(f)
|
print(f)
|
||||||
print(form.cleaned_data)
|
print(form.cleaned_data)
|
||||||
product = get_object_or_404(Product, slug=form.cleaned_data['product'])
|
|
||||||
tf = get_object_or_404(TimeFrame, slug=form.cleaned_data['timeframe'])
|
if 'timeframe' in form.cleaned_data:
|
||||||
po = ProductOrder(product=product, timeframe=tf)
|
timeframe = get_object_or_404(TimeFrame, slug=form.cleaned_data['timeframe'])
|
||||||
print(po)
|
else:
|
||||||
|
timeframe = None
|
||||||
|
|
||||||
|
order_product(product, timeframe, form.cleaned_data)
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
class ProductOrderView(ProductOneTimeOrderView):
|
||||||
|
form_class = ProductOrderForm
|
||||||
|
|
||||||
class ProductDetailView(DetailView):
|
class ProductDetailView(DetailView):
|
||||||
model = Product
|
model = Product
|
||||||
|
@ -54,6 +72,7 @@ class ProductDetailView(DetailView):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['productorder'] = reverse('product-order', kwargs={'product': self.object.slug })
|
context['productorder'] = reverse('product-order', kwargs={'product': self.object.slug })
|
||||||
context['timeframes'] = context['product'].valid_timeframes()
|
context['timeframes'] = context['product'].valid_timeframes()
|
||||||
|
context['has_one_time_price'] = context['product'].has_one_time_price()
|
||||||
print(context)
|
print(context)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,8 @@ urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('order', appviews.ProductSelectView.as_view()),
|
path('order', appviews.ProductSelectView.as_view()),
|
||||||
path('order/<slug:product>/', appviews.ProductOrderView.as_view(), name='product-order'),
|
path('order/<slug:product>/', appviews.ProductOrderView.as_view(), name='product-order'),
|
||||||
path('order/<slug:product>/<slug:timeframe>/', appviews.ProductOrderView.as_view(), name='product-order-tf'),
|
path('order/recurring/<slug:product>/<slug:timeframe>/', appviews.ProductOrderView.as_view(), name='product-order-tf'),
|
||||||
|
path('order/onetime/<slug:product>/', appviews.ProductOneTimeOrderView.as_view(), name='product-order-onetime'),
|
||||||
path('product/', appviews.ProductListView.as_view()),
|
path('product/', appviews.ProductListView.as_view()),
|
||||||
path('product/<slug:slug>/', appviews.ProductDetailView.as_view(), name='product-detail')
|
path('product/<slug:slug>/', appviews.ProductDetailView.as_view(), name='product-detail')
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue