Justin Boucher
Justin Boucher

Reputation: 301

Django forms min and max price inputs

I want to create a form that shows 2 different textboxes with a minimum and a maximum value of my model's price field, which is a DecimalField. But I'm not sure where to begin. I know I can calculate the min and max value, but I'm not sure how to add that to the placeholder and/or value text. So for right now, I'm just using the view to push the values, but they won't submit in the form. Here is my code so far:

forms.py

class ProductSearchForm(forms.ModelForm):
price_min = forms.DecimalField()
price_max = forms.DecimalField()

def __init__(self, *args, **kwargs):
    super(ProductSearchForm, self).__init__(*args, **kwargs)
    self.fields['length_range'].empty_label = "any size"
    self.fields['hull_type'].empty_label = "any type"
    self.fields['power'].empty_label = "any type"
    self.fields['speed'].empty_label = "any speed"
    self.fields['hull_only_available'].empty_label = None
    self.fields['price_min'].widget.attrs['min'] = kwargs['price']['price__min']
    self.fields['price_max'].widget.attrs['max'] = kwargs['price']['price__max']

class Meta:
    model = Product
    fields = ('length_range', 'hull_type', 'price', 'power', 'speed', 'hull_only_available')

views.py

class IndexView(FormView):
template_name = 'index.html'
form_class = ProductSearchForm
success_url = "search/"

def get_price(self):
    price = getattr(self, "_price", None)
    if price is None:
        price = Product.objects.all().aggregate(Min('price'), Max('price'))
        setattr(self, "_price", price)
    return price

def get_context_data(self, **kwargs):
    context = super(IndexView, self).get_context_data(**kwargs)
    context['length_ranges'] = LengthRange.objects.all().order_by('pk')
    context['hull_types'] = Hull.objects.all().order_by('pk')
    context['power_configs'] = PowerConfiguration.objects.all().order_by('pk')
    context['speed_ranges'] = SpeedRange.objects.all().order_by('pk')
    return context

def get_form_kwargs(self):
    form_kwargs = super(IndexView, self).get_form_kwargs()
    form_kwargs['price'] = self.get_price()
    return form_kwargs

index.html

<form class="nl-form" action="{% url 'boatsales:search' %}" method="get">
                                A boat with a length of
                                {{ form.length_range }}
                                , with hull type of
                                {{ form.hull_type }}
                                with
                                {{ form.power }}
                                power
                                configuration and a top speed between
                                {{ form.speed }}.
                                My budget is from $<input type="text" value="{{ price.price__min|intcomma }}"
                                                         placeholder="{{ price.price__min|intcomma }}"
                                                         data-subline="Our current lowest price is: <em>{{ price__min|intcomma }}</em>"/>
                                to
                                $<input
                                        type="text" value="{{ price.price__max|intcomma }}"
                                        placeholder="{{ price.price__min|intcomma }}"
                                        data-subline="Our current highest price is: <em>{{ price.price__min|intcomma }}</em>"/>.
                                Hull only
                                availability <select>
                                <option value="False" selected>is not</option>
                                <option value="True">is</option>
                            </select> a concern.
                                <div class="container">
                                    <button type="submit"
                                            class="btn-a btn-a_size_large btn-a_color_theme">
                                        Show me the results!
                                    </button>
                                </div>
                            </form>

EDIT Now I'm getting a TypeError: __init__() got an unexpected keyword argument 'price'

Seems to be coming from this line in views.py

context = super(IndexView, self).get_context_data(**kwargs) 

Upvotes: 2

Views: 3186

Answers (1)

Geekfish
Geekfish

Reputation: 2314

Sorry, ignore my previous answer, I completely misunderstood your question.

I think the problem here is that you are creating a form with only the fields that are contained in Product.

However you need a form that contains MORE fields. There is nothing that limits you to only using fields that are in the model.

So the idea would be to ignore price (which is a specific price), and instead add price_min and price_max:

class ProductSearchForm(forms.ModelForm):
    price_min = form.DecimalField()
    price_max = form.DecimalField()

    def __init__(self, *args, **kwargs):
        # Pop the price argument, as the parent form doesn't know about it!
        price = kwargs.pop("price")
        super(ProductSearchForm, self).__init__(*args, **kwargs)
        # ... existing code...
        self.fields['price_min'].widget.attrs['min'] = price['price__min']
        self.fields['price_min'].widget.attrs['max'] = price['price__max']
        self.fields['price_max'].widget.attrs['min'] = price['price__min']
        self.fields['price_max'].widget.attrs['max'] = price['price__max']

You can use these fields in your template as any other regular field.

You can then pass the current price values as you kind of already attempting to do, via get_form_kwargs:

class IndexView(FormView):
    # ... existing code...
    def get_form_kwargs(self):
        form_kwargs = super(IndexView, self).get_form_kwargs()
        form_kwargs['price'] = Product.objects.all().aggregate(Min('price'), Max('price'))
        return form_kwargs

Extra suggestion:

If you want to avoid making a query to Product twice to get the min/max, then you can create a method that caches the result, like:

(in view)

    def get_price(self):
        price = getattr(self, "_price", None)
        if price is None:
            price = Product.objects.all().aggregate(Min('price'), Max('price'))
            setattr(self, "_price", price)
        return price

and then you can call self.get_price() to populate the form kwargs and the template context.

Upvotes: 2

Related Questions