Reputation: 301
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
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