I'm trying to implement an 'advanced search', but I'll keep it a bit simpler for the sake of this question.
How it works so far:
#app.route('/advanced_search', methods=['GET', 'POST'])
def advanced_search():
form = AdvancedProductSearch() # this is a SelectMultipleField
countries = ['Canada', 'France', 'Mexico', 'Nigeria']
form.categories.choices = [(c,c) for c in countries]
if form.validate_on_submit():
countrydata = form.countries.data
res = models.Product.query.filter(models.Product.country.in_(countrydata)).all()
# Now I have my search results, what do I do with them?
return render_template('advanced_search.html', title='Advanced Search', form=form)
My plan was to have to have another view: advanced_search_results, which should be used for rendering the results, but I don't know how it should work.
#app.route('/advanced_search_results', methods=['GET', 'POST'])
def advanced_search_results(query):
#what to do?
return render_template('advanced_search_results.html', title='Search Results')
I can think of two ways of proceeding:
1) Somehow pass all the rows of data in res to the advanced_search_results() view, but I don't see how I can do that.
2) Use a redirect to pass the desired search query to advanced_search_results() and do the db search there. I'm also not sure how to go about this.
Simply return the necessary view:
res = models.Product.query.filter(models.Product.country.in_(countrydata)).all()
# Now I have my search results, let's send them back
return render_template('advanced_search_results.html', title='Results',
results=res)
Then, in your view you can loop over the results as you would for any other list of models:
{% for result in results %}
{{ result.name }}<br>
{{ result.country }}<br>
<hr noshade>
{% endfor %}
Alternatively, if you want to host it at another URL, just change the action of the form on the advanced-search page to point at advanced-search-results:
<form action="/advanced_search_results" method="post">
{# etc ... #}
</form>
then you can validate the search and either display the results or redirect the user with an error message:
#app.route('/advanced_search_results', methods=['POST'])
def advanced_search_results():
form = AdvancedProductSearch()
countries = ['Canada', 'France', 'Mexico', 'Nigeria']
form.categories.choices = [(c,c) for c in countries]
if not form.validate_on_submit():
flash("There were errors with your submission")
return redirect(url_for('advanced_search'))
countrydata = form.countries.data
res = models.Product.query.filter(models.Product.country.in_(countrydata)).all()
return render_template('advanced_search_results.html',
title='Search Results',
results=res)
This makes your advanced_search method simpler:
#app.route('/advanced_search', methods=['GET'])
def advanced_search():
form = AdvancedProductSearch() # this is a SelectMultipleField
countries = ['Canada', 'France', 'Mexico', 'Nigeria']
form.categories.choices = [(c,c) for c in countries]
return render_template('advanced_search.html', title='Advanced Search', form=form)
Related
It's been days I am trying to split up a simple django form with some charfield and dropdowns to sub forms using django-formtools. In the first step form I left 2 dropdowns field and in the second step form, 2 char fields and one image file. Once I change dropdowns and go to the second form then after step back, the values and image of the second step disappear so I need to refill the second step of form before submitting(and values of dropdown in the first step saved in the storage after changing the step):
Here is the view.py code:
FORMS = [('step_first', step_first_form),
('step_second', step_second_form)]
TEMPLATES = {'step_first': 'myapp/step_first.html',
'step_second': 'myapp/step_second.html'}
class NewWizard(NamedUrlSessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'photos'))
def done(self, form_list, **kwargs):
.....
def get_form(self, step=None, data=None, files=None):
# maintaining the files in session when changing steps
if self.steps.current == 'step_first':
step_files = self.storage.get_step_files(self.steps.current)
else:
step_files = self.storage.current_step_files
if step_files and files:
for key, value in step_files.items():
if files in key and files[key] is not None:
step_files[key] = files[key]
elif files:
step_files = files
return super(NewWizard, self).get_form(step, data,step_files)
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
and my second template:
<form id="dropdownForm" method="POST" action="" enctype="multipart/form-data">{% csrf_token %}
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{ form }}
{% endfor %}
{% else %}
<div class="content-section mt-4 text-center">
<div class="row justify-content-center">
{% if wizard.steps.prev %}
<button class="btn btn-sm btn-outline-primary" name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" formnovalidate>{% trans "prev step" %}</button>
{% endif %}
<div class="form-group ml-2">
<input class="btn btn-sm btn-outline-primary" type="submit" value="{% trans 'submit' %}"/>
</div>
</div>
</div>
{% endif %}
in url.py:
create_wizard = login_required(NewWizard.as_view(FORMS, url_name='step_first', done_step_name='finished'))
urlpatterns = [
re_path('myapp/new/create-(?P<step>.+)', create_wizard, name='step_first'),
]
I guess the prev step does not submit the form!
Any assistance you can provide would be greatly appreciated.
The default behavior of form wizard is that if you go back and come back to the current form, you will lose all the data(non-file) and files. The reason is that the prev button is associated with render_goto_step method. In the doc about render_goto_step, it says:
This method is called when the step should be changed to something else than the next step. By default, this method just stores the requested step goto_step in the storage and then renders the new step. If you want to store the entered data of the current step before rendering the next step, you can overwrite this method.
The following will solve part of your problems.
class NewWizard(NamedUrlSessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'photos'))
def done(self, form_list, form_dict, **kwargs):
return render(self.request, 'simpletest/done.html', {
'form_data':[form.cleaned_data for form in form_list]
})
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def render_goto_step(self, goto_step, **kwargs):
print('under render_goto_step')
print(self.storage.current_step)
form1 = self.get_form(self.storage.current_step, data=self.request.POST,files=self.request.FILES)
if form1.is_valid:
print("form.is valid")
self.storage.set_step_data(self.storage.current_step, self.process_step(form1))
print('after set_step_data')
self.storage.set_step_files(self.storage.current_step, self.process_step_files(form1))
print('after set_step_files')
else:
print('under form.errors')
print(form1.errors)
######### this is from render_goto_step method
self.storage.current_step = goto_step
form = self.get_form(
data=self.storage.get_step_data(self.steps.current),
files=self.storage.get_step_files(self.steps.current))
return redirect(self.get_step_url(goto_step))
Unfortunately, it cannot solve the image preview problem. I am not entirely sure about it, but this seems not related to the render_goto_step function per se because even the ones saved by postmethod to the session storage cannot be rendered. For example, if you add an image in form2, hit submit, and go to form3, and hit prev, you will see that image in form2 is gone, although the value(title) is there.
It seems that django and form wizard just do not render these files because they are dictionaries not files themselves. They are either <UploadedFile> or <InMemoryUploadedFile>objects.
What to do about image preview?
1. I was able to solve this problem by saving the file data into Model. Override post method and render_goto_step method to make sure that the image is saved to model both when you hit submit (post) and hitprev, first--- render_goto_step.
In addition, in order to render the image in your template, override get_context_data method and pass mypostinstance.
Please note that: in the following code, I simplified the save to modelportion by just save to pk=1 object. You have to change it accordingly.
class NewWizard(NamedUrlSessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'photos'))
def done(self, form_list, form_dict, **kwargs):
return render(self.request, 'simpletest/done.html', {
'form_data':[form.cleaned_data for form in form_list]
})
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def render_goto_step(self, goto_step, **kwargs):
print('under render_goto_step')
print(self.storage.current_step)
form1 = self.get_form(self.storage.current_step, data=self.request.POST,files=self.request.FILES)
if form1.is_valid:
print("form.is valid")
self.storage.set_step_data(self.storage.current_step, self.process_step(form1))
print('after set_step_data')
self.storage.set_step_files(self.storage.current_step, self.process_step_files(form1))
print('after set_step_files')
############ check if it is step_second, save file to model.
if self.steps.current =='step_second':
print('under render_goto_step step_second')
if 'imagefile' in self.request.FILES.keys():
f = self.request.FILES['imagefile']
print(f)
if f:
mypost = MyPost.objects.get(pk=1)
mypost.image = f
mypost.save()
print('saved')
else:
print('under form.errors')
print(form1.errors)
######### this is from render_goto_step method
self.storage.current_step = goto_step
form = self.get_form(
data=self.storage.get_step_data(self.steps.current),
files=self.storage.get_step_files(self.steps.current))
return redirect(self.get_step_url(goto_step))
def post(self, *args, **kwargs):
wizard_goto_step = self.request.POST.get('wizard_goto_step', None)
if wizard_goto_step and wizard_goto_step in self.get_form_list():
return self.render_goto_step(wizard_goto_step)
print('wizard_goto_step')
print(wizard_goto_step)
print('current')
print(self.steps.current)
# get the form for the current step
form = self.get_form(data=self.request.POST, files=self.request.FILES)
# and try to validate
if form.is_valid():
self.storage.set_step_data(self.steps.current, self.process_step(form))
self.storage.set_step_files(self.steps.current, self.process_step_files(form))
############ check if it is step_second, save file to model.
if self.steps.current =='step_second':
print('under step_second')
f = self.request.FILES['imagefile']
print(f)
if f:
mypost = MyPost.objects.get(pk=1)
mypost.image = f
mypost.save()
print('saved')
return self.render_next_step(form)
else:
# check if the current step is the last step
if self.steps.current == self.steps.last:
# no more steps, render done view
return self.render_done(form, **kwargs)
else:
# proceed to the next step
return self.render_next_step(form)
return self.render(form)
return super(NewWizard, self).post(*args, **kwargs)
def get_context_data(self, form, **kwargs):
context = super().get_context_data(form=form, **kwargs)
mypost = MyPost.objects.get(pk=1)
context.update({'mypost': mypost})
return context
and in your template, use image.url like below:
<div class="preview">%
{% if mypost.image %}
<img id="fileip-preview" src="{{mypost.image.url}}" alt="....">
{% else %}
<img id="fileip-preview" src="" alt="....">
{% endif %}
</div>
2. Another viable way is to first read the file and pass it to the template in views. I did not try to implement this. But this post might provide you an idea about how to implement it.
3. Another seemingly viable way is to use history API and localStorage. The idea is to mimic the browser back and forward buttons. As you can see when you use the browser to go back and come back to current, you can see that all your info is retained. It seemed that so many things that users do can affect history states, such as using back/forward rather than prev, submit; refreshing pages, back/forward from refreshed pages, etc. I tried this approach and felt like it should be a winner, but abandoned it eventually.
I am using Django for develop a website. The website is intended to use to search information stored in a MySQL database.
This is the current basic flow of the web site.
1) index.html - this has a form to select an option
2) according the option, users will redirect to search.html (include a form)
3) once the user provides the criteria, the result will be displayed in reply.html
In my views.py , I have two functions.
from django.shortcuts import render
from website.models import WebsiteRepository
from .forms import SearchForm
from .forms import SelectTypeForm
def Search(request):
if request.method == 'POST':
#do something
return render(request, 'reply.html', {'env_dict':env_dict})
else:
#do something
return render(request, 'search.html', context = context)
def index(request):
if request.method =='POST':
#do something
return render(request, 'search.html', context = context)
else:
#do something
return render(request, 'index.html', context= context)
When I go to index.html page, I can select a option and it will direct me to search.html. After, I fill the form there and submit, it wont give me the reply.html page.
I have a feeling that, I could make this work by changing urls.py.
from django.urls import path
from website import views
urlpatterns = [
path('', views.index, name='index'),
#path('search/', view.Search, name ='Search')
]
I tried to google it. But its too much details and Iam kind of lost.
Do any of you guys know how to achieve this?
Thanks
search.html
{% extends "base_generic.html" %}
{% block content %}
<h3>Welcome to search information Repository</h3>
<form method="post">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>
{% endblock %}
index.html
{% block content %}
<h3>Welcome to information Repository</h3>
<form method="post">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>
just for clarify things more, ill add the forms.py too
from django import forms
from .models import WebsiteRepository
class SearchForm(forms.Form):
websiterepository = WebsiteRepository
env_indicators = websiterepository.objects.filter (key_aspect='Environmental').values_list('repo_id','indicator')
indicator = forms.ChoiceField(choices=env_indicators,label = 'Indicator' )
OPTIONS = (('2000','2000'),('2001','2001'),('2002','2002'), ('2003','2003'),('0000','0000'),)
year = forms.ChoiceField(choices=OPTIONS)
class SelectTypeForm(forms.Form):
OPTIONS = (('1', 'Envirnmental Indicators'),('2','Economic Indicators'),('3','Social Indicators'),)
types = forms.ChoiceField(choices=OPTIONS)
Your code is wrong on many points.
First thing first: for a search, you want a GET request, not a POST (POST is for updating the server's state - adding or updating your database mostly). This is the semantically correct method (since you want to GET data), and it will allow a user to bookmark the url.
Second point: you don't want to submit the search form to the index view but to the search view. No need for redirects etc, just use the {% url %} templatetag to fill the action attribute of your form (you of course need to have a 'Search' url in your urls.py):
<form method="get" action="{% url 'Search' %}">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>
if you want to have this form on more than one page (which is often the case for search forms), use an inclusion tag tha will take care of creating an unbound SearchForm and render the template fragment.
Then in your search view, you only want GET requests, and do not use two different templates, this will only lead to useless duplication.
def Search(request):
form = SearchForm(request.GET)
# use the form's data - if any - to get search results
# and put those results (even if empty) in you context
return render(request, 'reply.html', {'env_dict':env_dict})
And finally, your search form is totally broken:
class SearchForm(forms.Form):
# this is totally useless
websiterepository = WebsiteRepository
# this will only be evaluated once at process startup, so you will
# get stale data in production - and probably different data
# per process, in a totally unpredictable way.
# You need to either put this in the form's __init__ or wrap it
# in a callable and pass this callable
env_indicators = websiterepository.objects.filter (key_aspect='Environmental').values_list('repo_id','indicator')
indicator = forms.ChoiceField(choices=env_indicators,label = 'Indicator' )
# are you going to manually add a new year choice every year ???
OPTIONS = (('2000','2000'),('2001','2001'),('2002','2002'), ('2003','2003'),('0000','0000'),)
year = forms.ChoiceField(choices=OPTIONS)
For the "indicators" ChoiceField you want something like:
def get_indicators_choices():
return Websiterepository.objects.filter (key_aspect='Environmental').values_list('repo_id','indicator')
class SearchForm(forms.Form):
# IMPORTANT : we are NOT calling the function here, just
# passing it (python functions are objects) to the field, which
# will call it everytime the form is instanciated, so you don't
# have stale data
indicator = forms.ChoiceField(
choices=get_indicator_choices,
label='Indicator')
As a last note: be consistent with your namings (ie why name one view in all lower (index) and capitalize the other (Search) ? Whichever convention you choose (I strongly suggest respecting pep8 here), at least stick to it for the whole project.
The problem is that code is not redirecting to /search, instead rendering search.html after post from index.html.
Try doing like-
views.py-
#your code
def index(request):
#do something
if request.method == 'POST':
return redirect('Search')
else:
#render index.html
def search(request):
#do something
if request.method == 'POST':
#render reply.html
else:
#render search.html
Another way to achieve this is if you specify action in your form so that form posts on /search.
search.html
<form method="post" action="/search">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>
Here's the goal: when the user submits the form, use one view to send the submitted data to the database, then redirect back to the form, but with the data pre-populated. This is mostly working, but something about my implementation is wrapping extra quotes around the string. For now, I'm just using a super-simple form, btw. I enter Billy, and the pre-pop is: "Billy", if I click submit again, it comes back as: "\"Billy\"", then "\"\\\"Billy\\\"\"", and so on (as far as I have tested, anyways.
relevant views are:
def editUsers(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = usersForm(request.POST)
# check whether it's valid:
# process the data in form.cleaned_data as required
# redirect to a new URL:
name = json.dumps(form.data['user_name'])
request.session['editUserName'] = name
# call out to limboLogic.py to update values
test = name
return redirect('../users')
# if a GET (or any other method) we'll create a blank form
else:
return redirect('../users')
from .forms import *
def users(request):
form = None
if 'editUserName' not in request.session:
# create a blank form
form = usersForm()
else:
# form = equipmentForm(initial='jim') - used to make sure I was branching the if/else correctly
form = usersForm(initial={'user_name':request.session['editUserName']}, auto_id=False) #limboLogic.GetUserInfo(name))
return render(request, 'limboHtml/UserManagement.html', {'form': form})
form is simply:
class usersForm(forms.Form):
user_name = forms.CharField(label='New User\'s name', max_length=100)
and the template is:
{% extends "base.html" %}
{% block content %}
<div class="row">
<p>This is the user management page</p><br>
<form action="/edit/users.html" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="OK">
</form>
<br><p class="bold">This is below the form</p>
</div>
{% endblock %}
thoughts?
I can't quite say what the intracies are here, but the problem involves the fact that I was using a json class. I used this site as a guide and managed to fix the problem. note that the key aspect is inside the second if:
name = form.cleaned_data['user_name'] works fine,
name = json.dumps(form.data['user_name']) does not
the whole function as it now stands:
def editUsers(request):
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = usersForm(request.POST)
# check whether it's valid:
# process the data in form.cleaned_data as required
# redirect to a new URL:
if form.is_valid():
name = form.cleaned_data['user_name']
# name = json.dumps(form.data['user_name'])
request.session['editUserName'] = name
# call out to limboLogic.py to update values
test = name
return redirect('../users')
# if a GET (or any other method) we'll create a blank form
return redirect('../users')
I have created a function for a search form form my database, method works fine, but I, don't know whether I should use queryBooks = request.GET['queryBooks'] or form.cleaned_data.get('queryBooks')
Here is my code.
# views.py
def SearchBook(request):
error = False
message = ''
books = Books.objects.all()
if 'queryBooks' in request.GET:
queryBooks = request.GET['queryBooks']
if not queryBooks:
error = True
message = u'enter book or author title'
else:
books = Books.objects.filter\
(
Q(book__icontains=queryBooks) | Q(Author__name__icontains=queryBooks)
)
contexto = {'Books': books, 'Error': error, 'Message': message}
return render(request, 'list_of_book.html', contexto)
# list_of_book.html
<form action="" method="get">
<input type="text" name="queryBooks">
<input type="submit" value="search">
</form>
# urls.py
url(r'^books/search/$', SearchBook, name='searchBook'),
There is no form in your view, so
form.cleaned_data.get('queryBooks')
Would give you an error.
In general, I recommend that you learn about Django forms, as they take care of rendering the html, and validating the input from the user. For your specific example, fetching the query string from request.GET is probably ok.
Have csrf in search result url. Don't know why is there and how to remove it. Search works nice. Here is URL
/search/?csrfmiddlewaretoken=675d1340034e094866d15a921407e3fc&q=testing
here is view:
def search(request):
query = request.GET.get('q', '')
rezult = []
if query:
qset1 = (
Q(title__icontains=query)
)
result = Product.objects.filter(qset1).distinct()
if result.count() == 1:
return HttpResponseRedirect(result[0].get_absolute_url())
return render_to_response('search/search.html',{'query': query, 'result': result, },context_instance=RequestContext(request))
Thanks
Remove {% csrf_token %} from your form in the template, you don't need it since you're making a GET request.
you added {% csrf_token %} in your form. if you dont need csrf remove this from your form and add csrf_exempt.
look at this sample of django:
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def my_view(request):
return HttpResponse('Hello world')
I would assume that you've added the {% csrf_token %} within one of the search form's input element. That would cause the token to be submitted along with the form.
Check your search form template.