Add support for onetimeprice and productordering

This commit is contained in:
Nico Schottelius 2022-01-30 13:00:58 +01:00
parent f08269640d
commit ba9bf94d8e
10 changed files with 184 additions and 47 deletions

View file

@ -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)

View 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'),
),
]

View file

@ -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'),
),
]

View file

@ -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'),
),
]

View file

@ -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

View 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)

View file

@ -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>

View file

@ -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 %}
-->

View file

@ -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']
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'])
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

View file

@ -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')