From ba9bf94d8e5e119d992f3bfe4830a7e2fd3f1248 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sun, 30 Jan 2022 13:00:58 +0100 Subject: [PATCH] Add support for onetimeprice and productordering --- uncloud_v3/app/forms.py | 24 +++++++++--- .../migrations/0002_resource_onetime_price.py | 18 +++++++++ ...ce_onetime_price_resource_onetime_price.py | 23 +++++++++++ ...004_alter_onetimeprice_options_and_more.py | 23 +++++++++++ uncloud_v3/app/models.py | 30 ++++++++++++-- uncloud_v3/app/services.py | 29 ++++++++++++++ .../app/templates/app/product_detail.html | 9 +++++ .../app/templates/app/productorder_form.html | 33 +++------------- uncloud_v3/app/views.py | 39 ++++++++++++++----- uncloud_v3/uncloud/urls.py | 3 +- 10 files changed, 184 insertions(+), 47 deletions(-) create mode 100644 uncloud_v3/app/migrations/0002_resource_onetime_price.py create mode 100644 uncloud_v3/app/migrations/0003_remove_resource_onetime_price_resource_onetime_price.py create mode 100644 uncloud_v3/app/migrations/0004_alter_onetimeprice_options_and_more.py create mode 100644 uncloud_v3/app/services.py diff --git a/uncloud_v3/app/forms.py b/uncloud_v3/app/forms.py index 0e094f2..af42715 100644 --- a/uncloud_v3/app/forms.py +++ b/uncloud_v3/app/forms.py @@ -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) diff --git a/uncloud_v3/app/migrations/0002_resource_onetime_price.py b/uncloud_v3/app/migrations/0002_resource_onetime_price.py new file mode 100644 index 0000000..5350a89 --- /dev/null +++ b/uncloud_v3/app/migrations/0002_resource_onetime_price.py @@ -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'), + ), + ] diff --git a/uncloud_v3/app/migrations/0003_remove_resource_onetime_price_resource_onetime_price.py b/uncloud_v3/app/migrations/0003_remove_resource_onetime_price_resource_onetime_price.py new file mode 100644 index 0000000..ccdca59 --- /dev/null +++ b/uncloud_v3/app/migrations/0003_remove_resource_onetime_price_resource_onetime_price.py @@ -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'), + ), + ] diff --git a/uncloud_v3/app/migrations/0004_alter_onetimeprice_options_and_more.py b/uncloud_v3/app/migrations/0004_alter_onetimeprice_options_and_more.py new file mode 100644 index 0000000..7efe960 --- /dev/null +++ b/uncloud_v3/app/migrations/0004_alter_onetimeprice_options_and_more.py @@ -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'), + ), + ] diff --git a/uncloud_v3/app/models.py b/uncloud_v3/app/models.py index 0db9fbd..3ae8a56 100644 --- a/uncloud_v3/app/models.py +++ b/uncloud_v3/app/models.py @@ -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 diff --git a/uncloud_v3/app/services.py b/uncloud_v3/app/services.py new file mode 100644 index 0000000..ce98c66 --- /dev/null +++ b/uncloud_v3/app/services.py @@ -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) diff --git a/uncloud_v3/app/templates/app/product_detail.html b/uncloud_v3/app/templates/app/product_detail.html index d003851..5fd672b 100644 --- a/uncloud_v3/app/templates/app/product_detail.html +++ b/uncloud_v3/app/templates/app/product_detail.html @@ -7,5 +7,14 @@
  • Buy {{ object.name }} for {{ tf }} +
  • {% endfor %} + + {% if not timeframes %} + {% if has_one_time_price %} +
  • Buy + {{ object.name }} +
  • + {% endif %} + {% endif %} diff --git a/uncloud_v3/app/templates/app/productorder_form.html b/uncloud_v3/app/templates/app/productorder_form.html index 85a1cda..0235c78 100644 --- a/uncloud_v3/app/templates/app/productorder_form.html +++ b/uncloud_v3/app/templates/app/productorder_form.html @@ -1,4 +1,8 @@ -

    Ordering a {{ product }} instance for {{ timeframe }}

    +

    Order {{ product }}

    + +{% if timeframe %} +

    Timeframe: {{ timeframe }}

    +{% endif %}
    {% csrf_token %} @@ -7,30 +11,3 @@
    - - - diff --git a/uncloud_v3/app/views.py b/uncloud_v3/app/views.py index f137e54..f7dc32c 100644 --- a/uncloud_v3/app/views.py +++ b/uncloud_v3/app/views.py @@ -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 diff --git a/uncloud_v3/uncloud/urls.py b/uncloud_v3/uncloud/urls.py index fb09101..57a5f66 100644 --- a/uncloud_v3/uncloud/urls.py +++ b/uncloud_v3/uncloud/urls.py @@ -21,7 +21,8 @@ urlpatterns = [ path('admin/', admin.site.urls), path('order', appviews.ProductSelectView.as_view()), path('order//', appviews.ProductOrderView.as_view(), name='product-order'), - path('order///', appviews.ProductOrderView.as_view(), name='product-order-tf'), + path('order/recurring///', appviews.ProductOrderView.as_view(), name='product-order-tf'), + path('order/onetime//', appviews.ProductOneTimeOrderView.as_view(), name='product-order-onetime'), path('product/', appviews.ProductListView.as_view()), path('product//', appviews.ProductDetailView.as_view(), name='product-detail')