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)
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.
Related
I have a form which has more than 10 fields. Now i want a particular field, lets say "requirements". This can be more than one requirement, i can deal with that using rich text editor as ask my user to input all the requirements as a ordered list. But for better user experience i am asking this !
I am gonna keep a button under "requirements" field, so that user can click on this button to get a new field. By this, i want all fields to be combined in a dict like
requirements = {'field1','extrafield1'} and etc
How to perform this ? I cant user formset ( as am just adding dynamic field not whole form )
How to deal this with django forms ?
I had a similar problem and came up with following solution (it's based on this answer mainly, but I was able to avoid any javascript):
form contains hidden field to store count of requirement fields; by default form has one such field, but pressing button "add_requirement" increases this count in TestView.post(). On __init__ form adds new fields with proper indexes, if needed, and on save() it goes through all requirement indexes to collect values from respective fields.
TestView.post() works differently for form's two buttons: "add_requirement" just increases fields count and renders form again with added field; default "submit" button saves valid form and redirects to success_url or re-displays invalid one.
forms.py
class SimpleForm(forms.Form):
requirements_count = forms.CharField(widget=forms.HiddenInput(), initial=1)
stable_field = forms.CharField(label='Stable field', required=False)
other_stable_field = forms.CharField(label='Other field', required=False)
requirements_1 = forms.CharField(label='Requirements', required=False)
def __init__(self, *args, **kwargs):
super(SimpleForm, self).__init__(*args, **kwargs)
if self.is_bound:
requirements_count = int(self.data.get('requirements_count', 1))
else:
requirements_count = int(self.initial.get('requirements_count', 1))
if requirements_count > 1:
for index in range(2, requirements_count + 1):
self.fields.update(
{'requirements_' + str(index):
forms.CharField(label='Requirements', required=False)}
)
def save(self, *args, **kwargs):
form = self.cleaned_data
requirements_count = int(form.get('requirements_count', 1))
requirements_list = []
for index in range(1, requirements_count + 1):
requirements = form.get('requirements_' + str(index), '')
if requirements:
requirements_list.append(requirements)
views.py
class TestView(FormView):
template_name = 'testtemplate.html'
form_class = SimpleForm
form = None
success_url = reverse_lazy('home')
def post(self, request, *args, **kwargs):
request_POST = request.POST
requirements_count = int(request_POST.get('requirements_count', 1))
if 'add_requirement' in request_POST:
new_initial = get_initial(request_POST)
new_initial['requirements_count'] = requirements_count + 1
self.form = SimpleForm(initial=new_initial)
context = self.get_context_data(form=self.form)
response = render(request, self.template_name, context)
else:
self.form = SimpleForm(data=request_POST)
context = self.get_context_data(form=self.form)
if self.form.is_valid():
response = self.form_valid(self.form)
else:
response = render(request, self.template_name, context)
return response
def form_valid(self, form):
form.save()
return super().form_valid(self.form)
# helper function
def get_initial(previous_data):
new_initial = {}
for key, value in previous_data.items():
if value != '' and key != 'csrfmiddlewaretoken':
new_initial.update({key: value})
return new_initial
testtemplate.html
<form action="" method="post">
{% csrf_token %}
<table border="1">
{{ form.as_table }}
</table>
<input type="submit">
<input type="submit" name="add_requirement" value="Add another requirement">
{{ form.non_field_errors }}
</form>
I'm in no way Django expert and it may be not the best solution, but it works for me and is relatively simple.
I'm trying to make a site where users can have multiple avatars and pick the one they want to use when entering a room. (It will be a dice-rolling app.) I've been stuck for three days trying to figure out how to pass a queryset into a form to use as the options for a select widget that's on an IntegerField. Right now, I think I have successfully passed the information into the form, but nothing is rendering in the widget. (I've even got a print statement in the form to prove to myself that the information has made its way in.)
model.py
class EnterSWroom(models.Model):
room_number = models.IntegerField(blank=False)
passcode = models.CharField(max_length=100, blank=True, null=True)
default_avatar = models.IntegerField(default=0)
forms.py
class Enter_SW_Room(forms.ModelForm):
class Meta:
model = EnterSWroom
widgets = {'default_avatar': forms.Select(choices=[])}
fields = ('room_number', 'passcode', 'default_avatar')
def __init__(self, *args, **kwargs):
imported_list = kwargs.pop('avatar_list')
super().__init__(*args, **kwargs)
self.fields['default_avatar'].choices = imported_list
print(self.fields['default_avatar'].choices) #this is just for debugging; see example below
views.py
class DockingBay(FormMixin, TemplateView):
form_class = Enter_SW_Room
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
# get tuples for avatar choices
# get user first name (if exists), else use username
if self.request.user.userprofile.user_first_name:
user_name = self.request.user.userprofile.user_first_name
else:
user_name = self.request.user.username
# instantiate avatars list, start with user name
my_avatars = Avatar.objects.filter(user_id=self.request.user.id)
my_avatars_choices = [(0, user_name)]
for avatar in my_avatars:
if avatar.deleted != 1:
my_avatars_choices.append((avatar.id, avatar.avatar_name))
kwargs['avatar_list'] = my_avatars_choices
return kwargs
def get(self, request, *args, **kwargs):
kwargs = self.get_form_kwargs()
# cut for brevity
form = Enter_SW_Room(**kwargs)
args = {'form': form, # other args that were cut from this example}
return render(request, self.template_name, args)
...
Example of a print: [(0, 'Eric')]
But, even though the print suggests that I've successfully gotten the queryset to the form, the selection widget displays nothing.
Thanks in advance!
edit to add snippet of html template:
<form method="post">
{% csrf_token %}
<table>
<col width="240">
<col width="120">
<tr>
<td><strong>Room ID:</strong>
<br>This will be a number.</td>
<td>{{form.room_number}} <br>
<strong>{{ form.room_number.errors }}</strong>
</td>
</tr>
<tr>
<td><strong>Passcode:</strong>
<br>If the room is open, or you've been in it before, leave this blank.</td>
<td>{{form.passcode}} <br>
<strong>{{ form.passcode.errors }}</strong>
</td>
</tr>
<tr>
<td><strong>Avatar:</strong>
<br>Select your avatar.</td>
<td>{{form.default_avatar}} <br>
<strong>{{ form.default_avatar.errors }}</strong>
</td>
</tr>
</table>
<button type="submit">Enter Room</button>
</form>
Oh, I'm an idiot. Forgot to define the field in the form.
added:
default_avatar = forms.ChoiceField()
to the form. Now it works. Arrrrrgh!
You can directly set your choice fields in your form init like this and filter by the current user.
class Enter_SW_Room(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(Enter_SW_Room, self).__init__(*args, **kwargs)
self.fields['default_avatar'] = forms.ModelChoiceField(
queryset=Avatar.objects.filter(user=user).exclude(deleted=1)
)
class Meta:
model = EnterSWroom
......
When initialising the form, pass the user.
form = Enter_SW_Room(self.request.user)
I want to be able to vary the placeholder like so:
<input placeholder=" {{ input.placeholder }}">
Where input is a model with the "placeholder" field. The placeholder field will vary since I'll be using a formset, and each placeholder will vary.
Here's my modelForm
class Value(forms.ModelForm):
class Meta:
model = ValueModel
fields = ['value_text']
widgets = {
'value_text': forms.TextInput(attrs={'class': 'form-control'})
and my modelformset
values_formset = modelformset_factory(model=ValueModel, extra=0, form=Value)
I've tried
class Value(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(Value, self).__init__(*args, **kwargs)
self.fields['value_text'].widget.attrs['placeholder'] = self.fields['placeholder']
class Meta:
model = ValueModel
fields = ['value_text', 'placeholder']
widgets = {
'value_text': forms.TextInput(attrs={'class': 'form-control'})
And other attempts at trying to modify the self.fields with no success.
Edit: The relevant part of my views.py:
def page_view(request, values_id):
values_form = values_formset(queryset=ValueModel.objects.filter(
values_id=values_id))
context = {'value': values_form}
return render(request, 'view.html', context)
My template view:
{{ value.management_form }}
{% for form in value %}
{{ form.id }}
{{ form.value_text }}
{% endfor %}
self.fields['placeholder'] refers to a form field object, not a value; you couldn't use it as a placeholder. But it seems like what you want is to use the value of the model instance.
def __init__(self, *args, **kwargs):
super(Value, self).__init__(*args, **kwargs)
self.fields['value_text'].widget.attrs['placeholder'] = self.instance.placeholder
I have a formset, but I can't figure out how to prepopulate it with data from the database.
Truth be told, I have a model Library and a model Book that has a foreign key to Library. I want someone to be able to edit all of the book in one library (at a time).
html
{{ book_form.management_form }}
{% for form in book_form.forms %}
{{ form.as_p }}
{% endfor %}
views.py
class LibraryUpdateView(LoginRequiredMixin, UpdateView):
model = Library
form_class = LibraryChangeForm
template_name = 'libraries/library_update.html'
def get_context_data(self, *args, **kwargs):
context = super(LibraryUpdateView, self).get_context_data(*args, **kwargs)
if self.request.POST:
context['book_form'] = BookFormSet(self.request.POST)
else:
context['book_form'] = BookFormSet()
return context
forms
class LibraryChangeForm(forms.ModelForm):
class Meta:
exclude = ['updated']
BookFormSet = inlineformset_factory(Library, Book, exclude = () )
How do I prepopulate the html page to show the data in the formset. In my loop there are 3 loops for {{form.as_p}}. When I run print(BookFormsSet()) I see the following
<input type="hidden" name="perdateinfo_set-TOTAL_FORMS" value="3"...
So it looks like it is generating three blank forms.
When I run print(PerDateInfoFormSet(qs,qs2)) for qs = Library.objects.filter(library_id='slkdfj') and qs2=Book.objects.filter(library_id='slkdfj') It tells me too many values to unpack (expected 2)
Can I pass in a query set? I imagine that I will be passing in a qs.first() to get the library and then multiple (qs2) query sets to get all of the books in the library.
You need to pass instance argument to the formset:
def get_context_data(self, *args, **kwargs):
context = super(LibraryUpdateView, self).get_context_data(*args, **kwargs)
if self.request.POST:
context['book_form'] = BookFormSet(self.request.POST, instance=self.object)
else:
context['book_form'] = BookFormSet(instance=self.object)
return context
I'm having a problem getting my view to update a manytomany field. It returns this after the form is submitted.
Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "/home/footbook/Ubuntu One/webapps/fb/poc/../poc/activity/views.py" in activity_save_page
44. group_names = form.cleaned_data['groups'].split()
Exception Type: AttributeError at /activity_save/
Exception Value: 'QuerySet' object has no attribute 'split'
Here are the files.
Models.py
class Group (models.Model):
group_nm = models.CharField(max_length=64)
group_desc = models.CharField(max_length=250)
created = models.DateTimeField(auto_now_add=True)
active_yn = models.BooleanField(default=True)
def __unicode__(self):
return self.group_nm
class Activity(models.Model):
activity_nm = models.CharField(max_length=60)
activity_desc = models.CharField(max_length=250)
startdt = models.DateField()
enddt = models.DateField()
crdt = models.DateTimeField(auto_now_add=True,editable=False)
groups = models.ManyToManyField(Group)
upddt = models.DateTimeField(editable=False)
def save(self, *args, **kwargs):
if not self.id:
self.crdt = datetime.date.today()
self.upddt = datetime.datetime.today()
super(Activity, self).save(*args, **kwargs)
def __unicode__(self):
return self.name
forms.py
def make_custom_datefield(f):
formfield = f.formfield()
if isinstance(f, models.DateField):
formfield.widget.format = '%m/%d/%Y'
formfield.widget.attrs.update({'class':'datePicker', 'readonly':'true'})
return formfield
class ActivitySaveForm(forms.ModelForm):
formfield_callback = make_custom_datefield
def __init__(self, *args, **kwargs):
super(ActivitySaveForm, self).__init__(*args, **kwargs)
self.fields['activity_nm'].label = "Activity Name"
self.fields['activity_desc'].label = "Describe It"
self.fields['startdt'].label = "Start Date"
self.fields['enddt'].label = "End Date"
self.fields['groups'].label ="Group"
class Meta:
model = Activity
views.py
def activity_save_page(request):
if request.method == 'POST':
form = ActivitySaveForm(request.POST)
if form.is_valid():
act, created = Activity.objects.get_or_create(
activity_nm = form.cleaned_data['activity_nm']
)
act.activity_desc = form.cleaned_data['activity_desc']
if not created:
act.group_set.clear()
group_names = form.cleaned_data['groups'].split()
for group_name in group_names:
group, dummy = Group.objects.get_or_create(group_nm=group_name)
act.group_set.add(group)
act.save()
return HttpResponseRedirect('/activity/')
else:
form = ActivitySaveForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('activity_save.html', variables)
I think it would work if I wasn't using the modelform, but I need it to implement this datepicker. Since it's a manytomany field, I want to split them when they are entered into the database, but my queryset fails. I've tried changing this a bunch of different ways, but I'm stuck. I've seen a lot of similar questions, but they either had foreign keys or no modelform.
Thanks.
EDIT:
activity_save.html
{% extends "base.html" %}
{% block title %}Save Activity{% endblock %}
{% block head %}Save Activty{% endblock %}
<input class="datePicker" readonly="true" type="text" id="id_startdt" />
<input class="datePicker" readonly="true" type="text" id="id_enddt" />
{% block content %}
<form action="{% url activity.views.activity_save_page act_id%}" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="save it" />
</form>
{% endblock %}
Exactly as the error describes: a QuerySet does not have a split method. You cannot call my_qs.split().
form.cleaned_data['groups'] returns cleaned data; it has already taken care of the form string-to-python-object conversion for you, which in the case of a ManyToManyField is ultimately represented by a QuerySet in python.
A date field returns date objects, IntegerField an integer, CharFields a string, etc. in the same way via form cleaning.
If you want a list of group_names, you'd need to explicitly iterate through the objects in the QuerySet and pull their group_nm attribute.
group_names = [x.group_nm for x in form.cleaned_data['groups']]
I'm not sure you need to do all that in your view. You can directly save the form in the view without manually creating the objects and manipulating them.
Also, you need to get the id of activity so that you can update existing activity instance.
Update the urls.py to have these urls to have act_id:
url(r'^activity_app/save/(?P<act_id>\d+)/$', 'activity_app.views.activity_save_page'),
url(r'^activity_app/save/$', 'activity_app.views.activity_save_page'),
I would change the view to:
def activity_save_page(request, act_id=None):
act_inst = None
try:
if act_id:
act_inst = Activity.objects.get(id=act_id)
except Exception:
pass
if request.method == 'POST':
form = ActivitySaveForm(request.POST, instance=act_inst)
if form.is_valid():
return HttpResponseRedirect('/activity/')
else:
form = ActivitySaveForm(instance=act_inst)
variables = RequestContext(request, {
'form': form
})
return render_to_response('activity_save.html', variables)