Django sidebar form - python

Im trying to build a sidebar form on a website in order to search some stuff, kind of like this http://cl.ly/0e0R1T0G3B1x0c451F22 where a person can search for something given certain parameters in any part of the website, meaning that the form must be displayed everywhere, and thats where I have the problem.
it seems that the view is not passing the form to the sidebar on the website, what can I do to always send the empty form as a sidebar.
Im trying to be as clear as possible, yet I know that It might not be enough, please let me know, Ill clarify.
This is the view
#render_to(template='league/common_fragments/sidebar_fixturesandresults.html')
def results_fixt_search(request):
results_fixt_search_form = results_fixt_SearchForm(request)
return {'results_fixt_search_form': results_fixt_search_form, }
This is the form, note that Im using django-uni-form
class HorizRadioRenderer(forms.RadioSelect.renderer):
""" this overrides widget method to put radio buttons horizontally
instead of vertically.
"""
def render(self):
"""Outputs radios"""
return mark_safe(u'\n'.join([u'%s\n' % w for w in self]))
class results_fixt_SearchForm(forms.Form):
league_search = forms.ChoiceField(choices=[ (league.slug, league.title ) for league in League.objects.all()])
radios = forms.CharField(widget=forms.RadioSelect(renderer=HorizRadioRenderer,
choices=(('table','Table'),('results','Results'), ('fixtures', 'Fixtures'),)), required=True)
# uniForm Helper
helper = FormHelper()
layout = Layout(
Fieldset('',
'league_search', 'radios'
)
)
helper.add_layout(layout)
# Submit button(s)
submit = Submit('submit','Submit')
helper.add_input(submit)
class Meta:
fields = ['search_term', 'radios']
def __init__(self, request, *args, **kw):
super(results_fixt_SearchForm, self).__init__(*args, **kw)
self.request = request
this is the HTML template
<form action="{% url results_fixt_search %}" method="get" enctype="multipart/form-data" id="results_fixt_search" class="select-form">
{% with results_fixt_search_form.helper as helper %}
{% uni_form results_fixt_search_form helper %}
{% endwith %}
</form>
And this the URL
url(r'^(?i)results_fixt/search/$', 'results_fixt_search', {}, name='results_fixt_search'),

You can either include the form html directly (without using django forms for rendering):
<form action = '{% url search %}' method='GET'>
<label>Query: <input type='search' name='query'></label>
<input type='submit' value='Go'>
</form>
or pass the form instance to all views using context processor:
def search_form_processor(request):
return {'search_form': SearchForm()}
and then render the form:
<form action = '{% url search %}' method='GET'>
{{ search_form.query.label_tag }} {{ search_form.query }}
<input type='submit' value='Go'>
</form>
Form may be processed by its own view at its own url then.

Related

How do I display same form in multiple pages using a single method in django?

I have a table with atrributes, and I'm displaying each attribute as a checkbox in html view. I want to show them in different pages, but I don't want to make different functions for each category. Is there an efficient way to do so? Here is what I tried so far.
def questions(request):
# start session page for the user to test
questions = Attribute.objects.all()
realistic = Attribute.objects.filter(holland_code=1)
investigative = Attribute.objects.filter(holland_code=2)
artistic = Attribute.objects.filter(holland_code=3)
social = Attribute.objects.filter(holland_code=4)
enterprising = Attribute.objects.filter(holland_code=5)
conventional = Attribute.objects.filter(holland_code=6)
left = [realistic, investigative, artistic, social, enterprising, conventional]
for attribute in left:
# get all the values form the form submitted
if request.method == "POST":
# THIS WILL GET ALL THE RECOMMENDAITONS
rAttributes = request.POST.getlist('realistic')
print(rAttributes)
return render(request, "main/questions.html", {"questions": attribute})
context = {
"questions": realistic,
}
return render(request, 'main/questions.html', context)
This is my html template to display the checkboxes
<form action="" method="POST">
{% csrf_token %}
<div class="form-check">
{% for question in realistic %}
<input type="checkbox" class="form-check-input" id="exampleCheck1" name="realistics" value="{{ question.attribute_name }}">
<label class="form-check-label" for="exampleCheck1">{{ question.attribute_name }}</label>
<br>
{% endfor %}
</div>
<input type="submit" class="btn btn-danger" value="Next">
</form>
Just add the form to the base template and extend that template so it'll be available everywhere.
or you could create a class based view and add a post request
class InsterViewNameHere(View):
def post(request, self, id=None, *args, **kwargs):
#form logic and context here
context = {}
return render(request, 'template.html', context)
To not have to repeat the same post function for every view or class-based-view, you could create a Mixin view
class ObjectnameMixin(object):
model = ClassModel
form = formname
def post_form(self):
return form

Stuck with django form validation

I'm trying to get validation running on a django form used to retrieve a list of objects in a ListView View. Despite having read django docs and many other questions here, I can't find out what's wrong in this simple test code:
form.html
<form action="list.html" method="get">
{{ form }}
<input type="submit" value="Submit">
</form>
list.html
<ul>
{% for area in object_list %}
<li>{{ area.name }}</li>
{% endfor %}
</ul>
forms.py
from django import forms
class SearchArea(forms.Form):
area = forms.CharField(label='Area code', max_length=6)
def clean_area(self):
area = self.cleaned_data['area'].upper()
if '2' in area:
raise forms.ValidationError("Error!")
return area
views.py
class HomePageView(FormView):
template_name = 'form.html'
form_class = SearchArea
class AreaListView(ListView):
template_name = 'list.html'
model = AreaCentral
def get_queryset(self):
q = self.request.GET.get('area')
return AreaCentral.objects.filter(area__istartswith=q)
When I try to submit something like "2e" I would expect a validation error, instead the form is submitted. Moreover I can see in the GET parameters that 'area' is not even converted to uppercase ('2E' instead of '2e').
The default a FormView will only process the form on POST; the GET is for initially displaying the empty form. So you need to use method="post" in your template form element.
Your action attribute is also suspect; it needs to point to the URL of the form view. If that actually is the URL, note it's not usual to use extensions like ".html" in Django URLs, and I would recommend not doing so.

Getting a form in Django DetailView

I am currently trying to show a custom form where I alter the form in the corresponding view
in the .html
<form action="{% url 'systems_system_update' system.id %}" id="system_update_form" method="post" class="form">
{% csrf_token %}
{% buttons %}
<button type="submit" class="btn btn-primary">Update {{ system.name }}</button>
{% endbuttons %}
{% bootstrap_form system_update_form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Update {{ system.name }}</button>
{% endbuttons %}
</form>
So I'm trying to show system_update_form where it is defined in the view
in SystemDetailView
class SystemDetailView(DetailView):
"""Detail view for Systems"""
form_class = SystemForm
model = System
template_name = 'systems/system_detail.html'
def get_form(self, form_class):
form = super(SystemDetailView, self).get_form(form_class)
form.fields['primary_purpose_business_use'].label = "Primary purpose/business use"
form.fields['secondary_purpose_business_uses'].label = "Secondary purpose/business uses"
return form
def get_context_data(self, **kwargs):
context = super(SystemDetailView, self).get_context_data(**kwargs)
context.update({
'system_update_form': self.form_class(instance=self.get_object()),
'object_name': self.object.name,
'user_can_edit': self.request.user.has_perm(
'services.change_system'),
'user_can_delete': self.request.user.has_perm(
'services.delete_system'),
'object_events': self.object.events.all(),
})
return context
So, I'm updating the context and setting 'system_update_form' to the form and I'm trying to update the form by using get_form, but I don't think DetailView has the get_form method for overriding.
Updating it in the forms is not an option because SystemForm is used in many different places and needs to be altered for this view specifically
DetailView does not have a get_form() method as it does not uses a form, thereby your get_form() method is not being called.
Instead of that, you can manually instantiate the form in your get_form() method and call this method when generating the context.
Also, in your code, you are passing instance to the form by calling self.get_object(). This will lead to another query for getting the object as Django has already fetched the object before. Instead of doing that, you can directly pass object using self.object.
class SystemDetailView(DetailView):
def get_form(self):
form = self.form_class(instance=self.object) # instantiate the form
# modify the form fields
form.fields['primary_purpose_business_use'].label = "Primary purpose/business use"
form.fields['secondary_purpose_business_uses'].label = "Secondary purpose/business uses"
return form
def get_context_data(self, **kwargs):
context = super(SystemDetailView, self).get_context_data(**kwargs)
context.update({
'system_update_form': self.get_form(), # get the form instance
'object_name': self.object.name,
'user_can_edit': self.request.user.has_perm(
'services.change_system'),
'user_can_delete': self.request.user.has_perm(
'services.delete_system'),
'object_events': self.object.events.all(),
})
return context

Django form redirect after POST is unreliable and needed a success_url

I am totally stuck to understand this behaviour and found a workaround I don't really like. Can anyone help enlighten me please? The context is I have a bootstrap styled form to create new records (inheriting from the generic.CreateView)
url.py:
url(r'^$', home, name='home'),
url(r'^main/$', views.MainView.as_view(), name='MainView'),
url(r'^topic/(?P<pk>[0-9]+)/$', catalogue_views.TopicView.as_view(), name='TopicView'),
url(r'^resource/(?P<pk>[0-9]+)/$', catalogue_views.DetailView.as_view(), name='ResourceDetail'),
url(r'^contribute/$', catalogue_views.ContributeView.as_view(success_url="/main/"), name='Contribute'),
views.py:
class ContributeView(generic.CreateView):
template_name = "openeye/contribute.html"
form_class = ContributeForm
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ContributeView, self).dispatch(*args, **kwargs)
class MainView(generic.ListView):
template_name = "openeye/main.html"
context_object_name = 'topic_list'
# TODO Make this only active topic areas?
def get_queryset(self):
return TopicArea.objects.all().order_by('name')
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(MainView, self).dispatch(*args, **kwargs)
forms.py:
class ContributeForm(forms.ModelForm):
class Meta:
model = CatalogueItem
fields = ['title', 'topic_area', 'description', 'link', 'what_learn', 'how_apply', 'level', 'relevant_to', 'discovered_by']
ROLE_CHOICES = [[x.id, x.job] for x in JobType.objects.all()]
title = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'To sell this resource to others'}), max_length=80, required=True)
description = forms.CharField(widget=forms.Textarea(attrs={'rows': 2, 'placeholder': 'Clear, e.g. format, duration, activities...'}))
link = forms.CharField(widget=forms.URLInput(attrs={'placeholder': 'If required, link to resource http://...'}), required=False)
what_learn = forms.CharField(widget=forms.Textarea(attrs={'rows': 3, 'placeholder':"This is important,."}), label='What will you learn?')
how_apply = forms.CharField(widget=forms.Textarea(attrs={'rows': 3, 'placeholder':"How could this be put into action afterwards?"}), label='How could you apply this?')
relevant_to = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=ROLE_CHOICES)
and a template with a form:
<div class="container">
<div class="entry-form row">
<div class="col-md-10 col-md-offset-1 col-sm-10 col-sm-offset-1 col-xs-10 col-xs-offset-1">
<form action="{% url 'MainView' %}" method="post" class="form">
<input type="hidden" name="next" value="{{ next }}">
{% bootstrap_form form %}
<button class="btn btn-primary btn-lg" type="submit">Submit Suggestion</button>
{% csrf_token %}
</form>
</div>
</div>
The form works perfectly and the data is saved nicely into the database. The problem is afterwards, the browser goes to the correct URL /main/ but the SCREEN IS BLANK. The server shows HTTP 405 0, and if I refresh the page it works.
If I alter the template so the action="{% url 'Contribute' %}" to return to the same form, I get HTTP 500 and a Django message about 'No URL to redirect to'. So two different errors determined by the re-direct location. In both case if I just click in browser url field and hit return it works.
I am sure this worked initially and then broke but I solved it as follows. Hard code in the success_url using it's path
url(r'^contribute/$', catalogue_views.ContributeView.as_view(success_url="/main/"), name='Contribute'),
Removing any action link in the template:
<form action="" method="post" class="form">
Is this the correct approach? Why, despite going to the correct URLs, do the pages not load or give errors with my original approach? I'd love to understand this.
Are you sure your data is actually saved on the server? From what you posted, it seems very unlikely. Here is the normal process followed by Django:
GET on form view (ContributeView)
→ returns an empty form
POST on form view (ContributeView)
→ if invalid, go back to step 1. If valid return a 302 Redirect to success_url.
GET on success_url
So normally, in your template, the form action should be empty, so the form gets posted back to the view that generated it. And the ContributeView should have a success url that redirects to wherever you want to send the user after:
from django.core.urlresolvers import reverse_lazy
class ContributeView(generic.CreateView):
# other stuff
success_url = reverse_lazy('MainView')
The behavior you get, with the 405, is because the browser, attempts to send the form directly to MainView which, not being a form view, tells the browser it does not know how to handle a POST method.

Using Django FormPreview the right way

My Goal
I have a django project with a form, and I want to display a preview page before the user submits.
The problem
I can display a preview page using a Django FormPreview, but not all form data is displayed properly. Specifically, if I have a field with choices, the string values of these choices aren't displayed. I'm also having problems applying template filters to date fields. The end result is that some data on the preview page is visible but other data is blank:
However, if I display the same data for posts that have actually been submitted, then everything displays properly:
My Code
models.py:
class Game(models.Model):
# Game Choices
FOOTBALL = 0
BASKETBALL = 1
TENNIS = 2
OTHER = 3
GAME_CHOICES = (
(FOOTBALL, 'Football'),
(BASKETBALL, 'Basketball'),
(TENNIS, 'Tennis'),
(OTHER, 'Other')
)
game_id = models.AutoField(primary_key=True)
location = models.CharField(max_length=200, verbose_name="Location")
game = models.IntegerField(choices=GAME_CHOICES, default=FOOTBALL)
game_date = models.DateField(verbose_name='Game Date')
forms.py
class GameForm(ModelForm):
class Meta:
model = Game
fields = (
'location',
'game',
'game_date'
)
I'm pretty sure that the problem is in my views.py: I'm not sure that I'm processing the POST request the right way to feed all data to the preview page.
views.py
def form_upload(request):
if request.method == 'GET':
form = GameForm()
else:
# A POST request: Handle Form Upload
form = GameForm(request.POST) # Bind data from request.POST into a GameForm
# If data is valid, proceeds to create a new game and redirect the user
if form.is_valid():
game = form.save()
return render(request, 'games/success.html', {})
return render(request, 'games/form_upload.html', {
'form': form,
})
preview.py
class GameFormPreview(FormPreview):
form_template = 'games/form_upload.html'
preview_template = 'games/preview.html'
def done(self, request, cleaned_data):
# Do something with the cleaned_data, then redirect
# to a "success" page.
return HttpResponseRedirect('/games/success')
form_upload.html
...
<form method="post">
{% csrf_token %}
<ul><li>{{ form.as_p }}</li></ul>
<button type="submit">Preview your post</button>
</form>
...
preview.html
{% load humanize %}
...
<h1>Preview your submission</h1>
<div>
<p>Location: {{ form.data.location }}</p>
<p>Game Date: {{ form.data.game_date|date:"l, F d, Y" }}</p>
<p>Game Type: {{ form.data.get_game_display }}</p>
</div>
<div>
<form action="{% url 'form_upload' %}" method="post">
{% csrf_token %}
{% for field in form %}
{{ field.as_hidden }}
{% endfor %}
<input type="hidden" name="{{ stage_field }}" value="2" />
<input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
<!-- Submit button -->
<button type="submit">Submit your post</button>
<!-- Go back button -->
<button type="submit">
<a href="{% url 'form_upload' %}"
onClick="history.go(-1);return false;" >
Go back and edit your post
</a>
</button>
</div>
</form>
</div>
...
Two issues
Essentially, I'm having these two issues:
String values for choices are not displayed. If I use the get_FOO_display() method in my preview.html template, it returns blank. However, if I use this in a page after the post has been submitted, it displays properly.
The humanize date filter doesn't work. If I apply a humanize filter ({{ form.data.game_date|date:"l, F d, Y" }}) in preview.html, it also displays blank. Again, this works for submitted posts.
My question essentially is: what's the right way to use the FormPreview here?
form.data does not have get_FOO_display attributes. When you access {{ form.data.get_game_display }} in the template, it fails silently and doesn't display anything.
The get_FOO_display are methods of the instance, so try this instead.
{{ form.instance.get_game_display }}
Wherever possible you should access data from form.cleaned_data (which is validated and 'cleaned') instead of form.data, which is the raw data submitted to the form.
The filters don't work with form.data.game_date because it's a raw string. They should work with form.cleaned_data.game_date, which has been converted to a python date object.
Finally, you haven't implemented anything in your done method, you've just copied the comment from the docs. You could create a new game using cleaned_data as follows:
def done(self, request, cleaned_data):
game = Game.objects.create(**cleaned_data)
return HttpResponseRedirect('/games/success')

Categories

Resources