[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