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
|
||||
|
||||
class ProductOrderForm(forms.Form):
|
||||
class ProductOneTimeOrderForm(forms.Form):
|
||||
"""
|
||||
For products that only contain onetimeresoures
|
||||
"""
|
||||
|
||||
product = forms.SlugField(required=True, disabled=True)
|
||||
timeframe = forms.SlugField(required=True, disabled=True)
|
||||
|
||||
def __init__(self, resources, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for res in resources:
|
||||
print(res)
|
||||
field_name = f"{res.name}"
|
||||
#field_name = f"resource_{res.slug}"
|
||||
self.fields[field_name] = forms.FloatField(required=True)
|
||||
field_name = f"{res.slug}"
|
||||
self.fields[field_name] = forms.FloatField(required=True, label=res.name)
|
||||
|
||||
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()
|
||||
currency = models.ForeignKey(Currency, on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
ordering = ('value',)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.value} {self.currency.short_name}"
|
||||
|
||||
|
@ -61,6 +64,8 @@ class Resource(models.Model):
|
|||
step_size = models.FloatField(default=1) # step size
|
||||
|
||||
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):
|
||||
if self.minimum_units:
|
||||
|
@ -74,7 +79,8 @@ class Resource(models.Model):
|
|||
|
||||
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):
|
||||
|
@ -88,6 +94,16 @@ class Product(models.Model):
|
|||
resources = models.ManyToManyField(Resource, blank=True) # List of REQUIRED resources
|
||||
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):
|
||||
"""
|
||||
Return all timeframes that have all resources configured
|
||||
|
@ -123,17 +139,25 @@ class ResourceOrder(models.Model):
|
|||
value = models.FloatField()
|
||||
resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.value} x {self.resource}"
|
||||
|
||||
|
||||
class ProductOrder(models.Model):
|
||||
"""
|
||||
Describes a product a user bought
|
||||
"""
|
||||
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)
|
||||
|
||||
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
|
||||
{{ object.name }}
|
||||
for {{ tf }}</a>
|
||||
</li>
|
||||
{% 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>
|
||||
|
|
|
@ -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" >
|
||||
{% csrf_token %}
|
||||
|
@ -7,30 +11,3 @@
|
|||
</table>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</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 .forms import *
|
||||
from .services import *
|
||||
|
||||
class ProductOrderView(FormView):
|
||||
form_class = ProductOrderForm
|
||||
class ProductOneTimeOrderView(FormView):
|
||||
form_class = ProductOneTimeOrderForm
|
||||
template_name = 'app/productorder_form.html'
|
||||
success_url = '/order/matrix/30-days/'
|
||||
|
||||
def get_success_url(self):
|
||||
return "/"
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
|
@ -23,29 +26,44 @@ class ProductOrderView(FormView):
|
|||
return kwargs
|
||||
|
||||
def get_initial(self):
|
||||
"""
|
||||
Initial values for the form
|
||||
"""
|
||||
|
||||
initial = super().get_initial()
|
||||
|
||||
initial['product'] = self.kwargs['product']
|
||||
initial['timeframe'] = self.kwargs['timeframe']
|
||||
|
||||
if 'timeframe' in self.kwargs:
|
||||
initial['timeframe'] = self.kwargs['timeframe']
|
||||
return initial
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['product'] = get_object_or_404(Product, slug=self.kwargs['product'])
|
||||
context['timeframe'] = get_object_or_404(TimeFrame, slug=self.kwargs['timeframe'])
|
||||
if 'timeframe' in context:
|
||||
context['timeframe'] = get_object_or_404(TimeFrame, slug=self.kwargs['timeframe'])
|
||||
return context
|
||||
|
||||
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:
|
||||
print(f)
|
||||
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'])
|
||||
po = ProductOrder(product=product, timeframe=tf)
|
||||
print(po)
|
||||
|
||||
if 'timeframe' in form.cleaned_data:
|
||||
timeframe = get_object_or_404(TimeFrame, slug=form.cleaned_data['timeframe'])
|
||||
else:
|
||||
timeframe = None
|
||||
|
||||
order_product(product, timeframe, form.cleaned_data)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
class ProductOrderView(ProductOneTimeOrderView):
|
||||
form_class = ProductOrderForm
|
||||
|
||||
class ProductDetailView(DetailView):
|
||||
model = Product
|
||||
|
@ -54,6 +72,7 @@ class ProductDetailView(DetailView):
|
|||
context = super().get_context_data(**kwargs)
|
||||
context['productorder'] = reverse('product-order', kwargs={'product': self.object.slug })
|
||||
context['timeframes'] = context['product'].valid_timeframes()
|
||||
context['has_one_time_price'] = context['product'].has_one_time_price()
|
||||
print(context)
|
||||
return context
|
||||
|
||||
|
|
|
@ -21,7 +21,8 @@ urlpatterns = [
|
|||
path('admin/', admin.site.urls),
|
||||
path('order', appviews.ProductSelectView.as_view()),
|
||||
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/<slug:slug>/', appviews.ProductDetailView.as_view(), name='product-detail')
|
||||
|
||||
|
|
Loading…
Reference in a new issue