[WIP] added offer request
rodzic
c7aa69448c
commit
10b9527261
|
@ -2,9 +2,9 @@ from django import forms
|
||||||
from phonenumber_field.formfields import PhoneNumberField
|
from phonenumber_field.formfields import PhoneNumberField
|
||||||
|
|
||||||
from store.models import (
|
from store.models import (
|
||||||
|
ProductTemplate,
|
||||||
ProductCategoryParamValue,
|
ProductCategoryParamValue,
|
||||||
ProductCategoryParam,
|
Product
|
||||||
ProductCategory
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,39 +40,27 @@ class CustomerDataForm(forms.Form):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProductCategoryParamFormset(forms.BaseModelFormSet):
|
class ButtonToggleSelect(forms.RadioSelect):
|
||||||
...
|
template_name = "store/forms/button_toggle_select.html"
|
||||||
|
|
||||||
|
|
||||||
class ProductCategoryParamValueForm(forms.ModelForm):
|
class ProductTemplateConfigForm(forms.Form):
|
||||||
class Meta:
|
|
||||||
model = ProductCategoryParamValue
|
|
||||||
fields = ("key", "value")
|
|
||||||
|
|
||||||
key = forms.CharField(required=True)
|
def _create_dynamic_fields(self, template: ProductTemplate):
|
||||||
value = forms.ModelChoiceField(
|
category_params = template.category.category_params.all()
|
||||||
queryset=ProductCategoryParamValue.objects.none(),
|
for param in category_params:
|
||||||
widget=forms.RadioSelect(attrs={"class": "btn-check", "type": "radio", "autocomplete": "off"}),
|
self.fields[param.key] = forms.ModelChoiceField(
|
||||||
required=True
|
queryset=ProductCategoryParamValue.objects.filter(param=param),
|
||||||
)
|
widget=ButtonToggleSelect(attrs={"class": "btn-group btn-group-toggle"}),
|
||||||
|
)
|
||||||
|
|
||||||
def _get_instace(self, key: str):
|
def __init__(
|
||||||
return ProductCategoryParam.objects.get(key=key)
|
self, template: ProductTemplate, *args, **kwargs
|
||||||
|
):
|
||||||
def __init__(self, *args, **kwargs):
|
self.template = template
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
key = self.initial.get("key")
|
self._create_dynamic_fields(template)
|
||||||
if not key:
|
|
||||||
return
|
|
||||||
self.cat_param = self._get_instace(key)
|
|
||||||
self.fields["value"].choices = [
|
|
||||||
(param_value.pk, param_value.value) for param_value in self.cat_param.param_values.all()
|
|
||||||
]
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def get_product(self):
|
||||||
param_value = ProductCategoryParamValue.objects.get(
|
params = list(self.cleaned_data.values())
|
||||||
param__key=str(self.cleaned_data["key"]),
|
return Product.objects.get_or_create_by_params(template=self.template, params=params)
|
||||||
value=str(self.cleaned_data["value"])
|
|
||||||
)
|
|
||||||
print(param_value or "DUPSKo")
|
|
||||||
return param_value
|
|
||||||
|
|
|
@ -172,8 +172,7 @@ class ProductManager(models.Manager):
|
||||||
|
|
||||||
def get_or_create_by_params(self, params: list[ProductCategoryParamValue], template: ProductTemplate):
|
def get_or_create_by_params(self, params: list[ProductCategoryParamValue], template: ProductTemplate):
|
||||||
products = self.filter(template=template)
|
products = self.filter(template=template)
|
||||||
# TODO - other price
|
|
||||||
price_proposal = products.first().price if products.first() else 4.0
|
|
||||||
for param in params:
|
for param in params:
|
||||||
products = products.filter(params__pk=param.pk)
|
products = products.filter(params__pk=param.pk)
|
||||||
|
|
||||||
|
@ -186,7 +185,7 @@ class ProductManager(models.Manager):
|
||||||
product = self.create(
|
product = self.create(
|
||||||
name=f"{template.title} - AUTOGENERATED",
|
name=f"{template.title} - AUTOGENERATED",
|
||||||
template=template,
|
template=template,
|
||||||
price=price_proposal,
|
price=0,
|
||||||
available=False
|
available=False
|
||||||
)
|
)
|
||||||
for param in params:
|
for param in params:
|
||||||
|
@ -220,6 +219,8 @@ class Product(ClusterableModel):
|
||||||
try:
|
try:
|
||||||
return self.product_images.get(is_main=True)
|
return self.product_images.get(is_main=True)
|
||||||
except ProductImage.DoesNotExist:
|
except ProductImage.DoesNotExist:
|
||||||
|
if main_image := self.template.main_image:
|
||||||
|
return main_image
|
||||||
return self.product_images.first()
|
return self.product_images.first()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,21 +23,11 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<form action="" method="POST" class="mt-5">
|
<form action="" method="POST" class="mt-5">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{{ formset.management_form }}
|
|
||||||
<div class="row mt-5">
|
<div class="row mt-5">
|
||||||
{% for form in formset.forms %}
|
{% for field in form %}
|
||||||
<div class="col-6 text-center">
|
<div class="col-6 text-center">
|
||||||
<h3>{{form.cat_param}}</h3>
|
<h3>{{field.label}}</h3>
|
||||||
{% if form.value.field.choices %}
|
{{field}}
|
||||||
<input type="hidden" name="{{form.prefix}}-key" value="{{form.key.value}}">
|
|
||||||
{% for value, label in form.value.field.choices %}
|
|
||||||
<div class="btn-group btn-group-toggle" role="group">
|
|
||||||
<input type="radio" class="btn-check" name="{{form.prefix}}-value" id="{{value}}" autocomplete="off" value="{{value}}" required>
|
|
||||||
<label class="btn btn-outline-primary" for="{{value}}">{{label}}</label>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% if forloop.counter|divisibleby:"2" %} </div><div class="row mt-5">{% endif %}
|
{% if forloop.counter|divisibleby:"2" %} </div><div class="row mt-5">{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="h-100">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
<img src="{{variant.main_image.image.url}}"
|
||||||
|
class="img-fluid img-thumbnail h-80" alt="Responsive image">
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="card mb-2 py-5">
|
||||||
|
<div class="card-body">
|
||||||
|
{% for value in params_values %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<p class="text-muted mb-0">{{value}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if not variant.available %}
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12 card alert-danger text-center">
|
||||||
|
Niestety skonfigurowany przez Ciebie wariant produktu nie jest jeszcze dostępny.
|
||||||
|
Jeżeli jesteś zainteresowany tą konfiguracją złóż zapytanie ofertowe.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-3 ">
|
||||||
|
{% if variant.available %}
|
||||||
|
<h3>Cena: {{variant.price}} zł</h3>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-9 text-end">
|
||||||
|
<a class="btn btn-outline-primary btn-lg" href="{% url 'product-configure' variant.template.pk %}">Wróć do konfiguratora</a>
|
||||||
|
{% if variant.available %}
|
||||||
|
<button class="btn btn-outline-success btn-lg">Zamów produkt</button>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-outline-success btn-lg">Złóż zapytanie ofertowe</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
{% with id=widget.attrs.id %}
|
||||||
|
<div class="btn-group btn-group-toggle" role="group">
|
||||||
|
{% for group, options, index in widget.optgroups %}
|
||||||
|
{% for option in options %}
|
||||||
|
<input type="radio" class="btn-check" name="{{option.name}}" id="{{id}}_{{option.index}}" autocomplete="off"
|
||||||
|
value="{{option.value}}" required>
|
||||||
|
<label class="btn btn-outline-primary" for="{{id}}_{{option.index}}">{{option.label}}</label>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
<!--
|
||||||
|
{% with id=widget.attrs.id %}
|
||||||
|
<div{% if id %} id="{{ id }}"{% endif %}
|
||||||
|
{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}
|
||||||
|
>
|
||||||
|
{% for group, options, index in widget.optgroups %}
|
||||||
|
{% if group %}
|
||||||
|
<div><label>{{ group }}</label>{% endif %}{% for option in options %}<div>
|
||||||
|
{% include option.template_name with widget=option %}</div>{% endfor %}{% if group %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
-->
|
|
@ -138,7 +138,7 @@ class ProductTestCase(TestCase):
|
||||||
)
|
)
|
||||||
self.assertIsNotNone(prod)
|
self.assertIsNotNone(prod)
|
||||||
self.assertFalse(prod.available)
|
self.assertFalse(prod.available)
|
||||||
self.assertEqual(prod.price, 4.0)
|
self.assertEqual(prod.price, 0)
|
||||||
|
|
||||||
|
|
||||||
class OrderProductTestCase(TestCase):
|
class OrderProductTestCase(TestCase):
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigureProductViewTestCase(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def test_sdkfsdf(self):
|
||||||
|
...
|
||||||
|
|
|
@ -10,7 +10,7 @@ router.register("cart-action", store_views.CartActionView, "cart-action")
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('product-configure/<int:pk>/', store_views.ConfigureProductView.as_view(), name='product-configure'),
|
path('product-configure/<int:pk>/', store_views.ConfigureProductView.as_view(), name='product-configure'),
|
||||||
path('product-configure/summary/<int:variant_pk>/', store_views.ConfigureProductSummaryView.as_view(), name='product-configure-summary'),
|
path('product-configure/summary/<int:variant_pk>/', store_views.ConfigureProductSummaryView.as_view(), name='configure-product-summary'),
|
||||||
path('cart/', store_views.CartView.as_view(), name='cart'),
|
path('cart/', store_views.CartView.as_view(), name='cart'),
|
||||||
path("order/", store_views.OrderView.as_view(), name="order"),
|
path("order/", store_views.OrderView.as_view(), name="order"),
|
||||||
path("order/confirm/", store_views.OrderConfirmView.as_view(), name="order-confirm")
|
path("order/confirm/", store_views.OrderConfirmView.as_view(), name="order-confirm")
|
||||||
|
|
|
@ -20,8 +20,7 @@ from store.serializers import (
|
||||||
)
|
)
|
||||||
from store.forms import (
|
from store.forms import (
|
||||||
CustomerDataForm,
|
CustomerDataForm,
|
||||||
ProductCategoryParamValueForm,
|
ProductTemplateConfigForm
|
||||||
ProductCategoryParamFormset
|
|
||||||
)
|
)
|
||||||
from store.models import (
|
from store.models import (
|
||||||
Order,
|
Order,
|
||||||
|
@ -98,18 +97,11 @@ class ConfigureProductView(View):
|
||||||
|
|
||||||
def get_context_data(self, pk: int, **kwargs: Any) -> Dict[str, Any]:
|
def get_context_data(self, pk: int, **kwargs: Any) -> Dict[str, Any]:
|
||||||
template = ProductTemplate.objects.get(pk=pk)
|
template = ProductTemplate.objects.get(pk=pk)
|
||||||
category_params = template.category.category_params.all()
|
form = ProductTemplateConfigForm(template=template)
|
||||||
formset_class = modelformset_factory(
|
|
||||||
ProductCategoryParamValue,
|
|
||||||
form=ProductCategoryParamValueForm,
|
|
||||||
formset=ProductCategoryParamFormset
|
|
||||||
)
|
|
||||||
formset = formset_class(queryset=category_params)
|
|
||||||
context = {
|
context = {
|
||||||
"template": template,
|
"template": template,
|
||||||
"available_variants": Product.objects.filter(template__pk=pk),
|
"available_variants": Product.objects.filter(template__pk=pk),
|
||||||
"category_params": category_params,
|
"form": form
|
||||||
"formset": formset
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
@ -121,41 +113,14 @@ class ConfigureProductView(View):
|
||||||
def post(self, request, pk: int, *args, **kwargs):
|
def post(self, request, pk: int, *args, **kwargs):
|
||||||
# first select template
|
# first select template
|
||||||
template = ProductTemplate.objects.get(pk=pk)
|
template = ProductTemplate.objects.get(pk=pk)
|
||||||
category_params = template.category.category_params.all()
|
form = ProductTemplateConfigForm(template=template, data=request.POST)
|
||||||
params_values = []
|
if not form.is_valid():
|
||||||
formset_class = modelformset_factory(
|
|
||||||
ProductCategoryParamValue,
|
|
||||||
form=ProductCategoryParamValueForm,
|
|
||||||
formset=ProductCategoryParamFormset
|
|
||||||
)
|
|
||||||
formset = formset_class(queryset=category_params, data=request.POST)
|
|
||||||
print(request.POST)
|
|
||||||
if not formset.is_valid():
|
|
||||||
print(formset.errors)
|
|
||||||
messages.error(request, "Niepoprawne dane")
|
|
||||||
context = self.get_context_data(pk)
|
context = self.get_context_data(pk)
|
||||||
context["formset"] = formset
|
context["form"] = form
|
||||||
return render(request, self.template_name, context)
|
return render(request, self.template_name, context)
|
||||||
|
|
||||||
for form in formset.forms:
|
product_variant = form.get_product()
|
||||||
if not form.is_valid():
|
return HttpResponseRedirect(reverse("configure-product-summary", args=[product_variant.pk]))
|
||||||
messages.error(request, "Niepoprawne dane")
|
|
||||||
context = self.get_context_data(pk)
|
|
||||||
context["formset"] = formset
|
|
||||||
return render(request, self.template_name, context)
|
|
||||||
params_values.append(form.save())
|
|
||||||
|
|
||||||
product_variant = Product.objects.get_or_create_by_params(
|
|
||||||
template=ProductTemplate.objects.get(pk=pk), params=params_values
|
|
||||||
)
|
|
||||||
if not product_variant:
|
|
||||||
messages.error(request, "Nie udało się utworzyć wariantu produktu")
|
|
||||||
return HttpResponseRedirect(reverse("product-configure", kwargs={"pk": pk}))
|
|
||||||
|
|
||||||
return HttpResponseRedirect(
|
|
||||||
reverse("product-configure-summary", kwargs={"variant_pk": product_variant.pk})
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigureProductSummaryView(View):
|
class ConfigureProductSummaryView(View):
|
||||||
template_name = "store/configure_product_summary.html"
|
template_name = "store/configure_product_summary.html"
|
||||||
|
|
Ładowanie…
Reference in New Issue