django url parameters as form default value - django

I have a search form
class PostSearchForm(forms.Form):
keyword = forms.CharField(label=_('Search'))
def __init__(self,*args,**kwargs):
self.request = kwargs.pop('request', None)
super(PostSearchForm,self).__init__(*args,**kwargs)
raise Exception(self.request) # This returns none
categories = Category.objects.filter(status=True)
self.fields['categories'] = forms.ModelChoiceField(queryset=categories, widget=forms.SelectMultiple(attrs={'class': 'some-class'}))
Whenever search is made I have a build a form with default value as what they have searched, so what I tried is get the url parameters in form init and set the value but it was returning None due to some mistake. Can any one tell me where I am wrong
Updated Question
views.py
def get(self,request,category,*args,**kwargs):
search_form = PostSearchForm()
return render(request,self.template_name,{'search_form':search_form})
Template
<form method="get" action="." id="searchForm">
{{ search_form.keyword }} # When searched then the value should be searched term
{{ search_form.categories }}
</form>
URL
http://example.com/?keyword=test
In the form I need to show test as value

Nothing can get into an object unless you pass it there. If you want the request in your object, then pass it:
search_form = PostSearchForm(request=request)
However this will not help you in any way at all and I'm confused about why you thought it would. If you want to pass in initial data for your form, do that:
search_form = PostSearchForm(initial={'categories': categories, 'keyword': keyword})

Related

Django update boolean field with a form

My simple web-application has two models that are linked (one to many).
The first model (Newplate) has a boolean field called plate_complete. This is set to False (0) at the start.
questions:
In a html page, I am trying to build a form and button that when pressed sets the above field to True. At the moment when I click the button the page refreshes but there is no change to the database (plate_complete is still False). How do I do this?
Ideally, once the button is pressed I would also like to re-direct the user to another webpage (readplates.html). This webpage does not require the pk field (but the form does to change the specific record) Hence for now I am just refreshing the extendingplates.html file. How do I do this too ?
My code:
"""Model"""
class NewPlate(models.Model):
plate_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
created_date = models.DateTimeField(default=timezone.now)
plate_complete = models.BooleanField()
"""view"""
def publish_plates(request,plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
newplate.plate_complete = True
newplate.save()
#2nd method
NewPlate.objects.filter(pk=plate_id).update(plate_complete = True)
return HttpResponseRedirect(reverse('tablet:extendplates', args=[plate_id]))
"""URLS"""
path('readplates', views.read_plates, name='readplates'),
path('extendplates/<pk>/', views.show_plates, name='showplates'),
path('extendplates/<pk>/', views.publish_plates, name='publishplates'),
"""HTML"""
<form method="POST" action="{% url 'tablet:publishplates' newplate.plate_id %}">
{% csrf_token %}
<button type="submit" class="button" value='True'>Publish</button></form>
-------Added show plates view:---------
def show_plates(request,pk):
mod = NewPlate.objects.all()
newplate= get_object_or_404(mod, pk=pk)
add2plate= Add2Plate.objects.filter(Add2Plateid=pk)
return render(request, 'tablet/show_plates.html', {'newplate': newplate,'add2plate': add2plate})
Thank you
The problem is two of your urls have the same pattern 'extendplates/<pk>/'. Django uses the first pattern that matches a url. I suppose that one of these view views.show_plates is meant to display the form and the other views.publish_plates is meant to accept the posted form data.
This means that simply both of these views should simply be a single view (to differentiate if the form is submitted we will simply check the requests method):
from django.shortcuts import redirect, render
def show_plates(request, plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
if request.method == "POST":
newplate.plate_complete = True
newplate.save()
return redirect('tablet:extendplates', plate_id)
context = {'newplate': newplate}
return render(request, 'your_template_name.html', context)
Now your url patterns can simply be (Note: Also captured arguments are passed as keyword arguments to the view so they should be consistent for your view and pattern):
urlpatterns = [
...
path('readplates', views.read_plates, name='readplates'),
path('extendplates/<uuid:plate_id>/', views.show_plates, name='showplates'),
...
]
In your form simply forego the action attribute as it is on the same page:
<form method="POST">
{% csrf_token %}
<button type="submit" class="button" value='True'>Publish</button>
</form>
You should avoid changing state on a get request like your view does currently.
Handle the POST request and change the data if the request is valid (ensuring CSRF protection).
def publish_plates(request,plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
if request.method == "POST":
newplate.plate_complete = True
newplate.save(update_fields=['plate_complete']) # a more efficient save
#2nd method
NewPlate.objects.filter(pk=plate_id).update(plate_complete=True)
return HttpResponseRedirect(reverse('tablet:extendplates', args=[plate_id]))
You could also put a hidden input in the form, or make a form in Django to hold the hidden input, which stores the plate_id value and that way you can have a generic URL which will fetch that ID from the POST data.
Now the real problem you've got here, is that you've got 2 URLs which are the same, but with 2 different views.
I'd suggest you change that so that URLs are unique;
path('extendplates/<pk>/', views.show_plates, name='showplates'),
path('publish-plates/<pk>/', views.publish_plates, name='publishplates'),

How to pass value to view from form

Good day.
The challenge is:
Create a form that will change the parameters of the model fields, based on user input.
My logic is this. I tried to create a form for entering changes:
In the lists , I recorded all the ID and field names of the model;
class RefDataForm(forms.Form):
NODE_ID_LIST=[('YE102_4G','YE102_4G'),('AG001_4G','AG001_4G')]
ANRFUNC_PARAM_LIST=[('zzztemporary7','zzztemporary7'),('zzztemporary2','zzztemporary2')]
change_id = forms.CharField(label='Node ID for Change', widget=forms.Select(choices=NODE_ID_LIST))
change_param_name = forms.CharField(label='Parameter name for Change', widget=forms.Select(choices=ANRFUNC_PARAM_LIST))
value = forms.CharField(label='Value')
Next in view.py, I'm trying to create a .update command that should accept changes.
def ref_test(request, template_name ='ref_test.html'):
if request.method == 'POST':
test=RefDataForm(request.POST)
if test.is_valid():
change_id = request.POST['change_id']
change_param_name = request.POST['change_param_name']
change_value = request.POST['value']
update_form = Ran4GRfAnrfunction.objects.filter(pk__in=change_id).update(change_param_name=change_value)
else:
test=RefDataForm()
return render(request, template_name, {'test':test})
My html is :
<form method="post">
{% csrf_token %}
{{ test.change_id }}
{{ test.change_param_name }}
{{ test.value }}
<button type="submit">Search</button>
</form>
However, I get an error
*Ran4GRfAnrfunction has no field named 'change_param_name' *
How do I pass field_name through a form?
In manage.py shell, I tried to do it - and its work.
from dumper.models import *
change_id = ['AG001_4G', 'AG002_4G']
change_value = ('Okay')
change_param_name = ('zzztemporary2')
Ran4GRfAnrfunction.objects.filter (pk__in = change_id) .update (zzztemporary2 = change_value)
How do I pass the value of change_param_name to .update ?
Maybe you've already figured this out since the questions been here for five hours at this point.
I can't exactly test this, but it looks like your problem is right here. This line is telling it to change the change_param_name field - not to change the field matching the name stored in change_param_name.
.update(change_param_name=change_value)
You should be able to fix this by putting the values into a dictionary and unpacking it.
.update(**{change_param_name: change_value})

Creating url out of user's choices from the form. Django gives NoReverseMatch, saying i need at least two arguments and i have none

So basically i have form on my homepage that asks users to choose two cities : where they are now, and where they want to go. I display all the available options with ModelChoiceField() easily, but when i try to use user's choices to make arguments for url, i get NoReverseMatch. I did a little research and found out due to the fact that at the time when page is loaded, user hasn't chosen anything, so there are no arguments. After that, i took different approach - i tried to set /search/ as url for the form. There, i extracted user's choices and tried to redirect back to the main url with these two arguments. Error still persists
Traceback Url :
http://dpaste.com/34E3S2V
Here's my forms.py :
class RouteForm(forms.Form):
location = forms.ModelChoiceField(queryset=Location.objects.all())
destination = forms.ModelChoiceField(queryset=Destination.objects.all())
Here's my template :
<p> From where to where ? </p>
<form action="{% url 'listings:search' %}" method="POST">
{{ form.as_p }}
{% csrf_token %}
<input type="submit" value="Let's go!">
</form>
My urls.py :
urlpatterns = [
path('', views.index, name="index"),
path('<location>/<destination>', views.route, name="route"),
path('search/', views.search, name="search")
]
and views.py :
def index(request):
form = forms.RouteForm()
listings = Listing.objects.all()
context = {"listings" : listings, "form" : form }
def route(request, location, destination):
current_location = Location.objects.get(city=location)
future_destination = Destination.objects.get(city=destination)
context = {"current_location" : current_location, "future_destination" : future_destination}
return render(request, 'listings/route.html', context)
def search(request, location, destination):
chosen_location = Location.objects.get(pk=request.POST['location'])
chosen_destination = Destination.objects.get(pk=request.POST['destination'])
return HttpResponseRedirect(reverse('listings:route', args=[chosen_location, chosen_destination]))
What am i missing?
You need to show the full traceback in your question. Actually, the code you've shown wouldn't give that error; instead you would get a TypeError for the search view.
Nevertheless, you have quite a few things wrong here.
Firstly, you need to decide how you want to represent those fields in the URL. You can't just put a Location object in a URL. Do you want to use numeric IDs, or string slugs? Assuming you want to use slugs, your URL would be:
path('<slug:location>/<slug:destination>', views.route, name="route"),
Secondly, you shouldn't have location and destination as parameters to the search function. They aren't being passed in the URL, but you in the POST data.
Next, you need to actually use the Django form you've defined, and get the values from that form cleaned_data. Using the form - in particular calling its is_valid() method - ensures that the user actually chooses options from the fields. So the search function needs to look like this:
def search(request):
if request.method == 'POST':
form = RouteForm(request.POST)
if form.is_valid():
chosen_location = form.cleaned_data['location']
chosen_destination = form.cleaned_data['destination']
return HttpResponseRedirect(reverse('listings:route', args=[chosen_location.city, chosen_destination.city]))
else:
form = RouteForm()
return render(request, 'search.html', {'form': form})

Pagination and get parameters

Django 1.11.4
I have build a search form with method="get".
Search form has a lot of forms.
Then this input values are transmitted as get parameters in url.
The problem is how to get pagination. The database contains thousands of objects. Pagination is necessary.
This is what the documentation tells us:
https://docs.djangoproject.com/en/1.11/topics/pagination/#using-paginator-in-a-view
It suggests like this:
previous
But this will ruin all the get parameters.
What I have managed to invent is:
previous
This works. But this is goofy. If one switches pages forward and backward, it produces urls ending like this:
page=2&page=3&page=2
I have had a look at how Google managed this problem. In the middle of the url they have start=30. And change this parameter: start=20, start=40. So, they switch.
Could you help me understand how preserve get parameters and switch pages in Django? In an elegant way, of course.
The generic solution is to define a "custom template tag" (a function) which keeps the complete URL but updates the GET parameters you pass to it.
After registration, you can use this function in your templates:
previous
To define and register the custom template tag, include this code in a python file:
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def query_transform(context, **kwargs):
query = context['request'].GET.copy()
for k, v in kwargs.items():
query[k] = v
return query.urlencode()
*Thanks to Ben for the query_transform code. This is an adapation for python 3 from his code.
Why this method is better than reconstructing the URLs manually:
If you later decide that you need additional parameters in your URLs: 1. you don't have to update all the links in your templates. 2. You don't need to pass all the params required for recostructing the URL to the templates.
Typically, to preserve GET parameters you simply re-write them manually. There shouldn't be many cases where having to do this will matter.
&page={{page}}&total={{total}}
You can abstract this away into a template include or a custom template tag.
https://docs.djangoproject.com/en/1.11/howto/custom-template-tags/
Additionally, you could create a string filter that takes URL params as a string as well as a dict of values you want to change. The filter could then parse the params string, update the value, then recombine the string back into URL params.
{{ request.get_full_path | update_param:'page=8' }}
This is what I did and find easier. Many not be better approach though!
param = ""
if search_programs:
qs = qs.filter(title__icontains=search_programs)
param = param + f"&search_programs={search_programs}"
if level:
qs = qs.filter(level__icontains=level)
param = param + f"&level={level}"
if category:
qs = qs.filter(sector__icontains=category)
param = param + f"&category={category}"
paginator = Paginator(qs, 16)
page_number = self.request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {
'page_obj': page_obj,
"param": param
}
return render(self.request, self.template_name, context)
# in html
next
Subclassing
I solved this problem by subclassing Django's Paginator class and adding the stuff I needed there.
You need to override the init method to allow yourself to pass the request and form objects from the view, so that you can retrieve the request.GET parameters and compare them to form.fields. You want to only allow GET parameter names that are predefined in your form. Otherwise you remove them.
Once you have subclassed the Pagination class, you can use it in your views.py files, passing the extra request and form parameters to it. Then you can render your template as suggested in the Django documentation, but now you have access to the paginator.first_params, paginator.last_params, page.previous_params, and page.next_params variables that you can use.
pagination.py
import urllib
from django.core.paginator import Paginator as DjangoPaginator, EmptyPage
class Paginator(DjangoPaginator):
def __init__(self, *args, **kwargs):
request = kwargs.pop('request', None)
form = kwargs.pop('form', None)
super().__init__(*args, **kwargs)
self.params = {key: value for key, value in request.GET.items() if key in form.fields.keys()}
self.first_params = urllib.parse.urlencode({**self.params, 'page': 1})
self.last_params = urllib.parse.urlencode({**self.params, 'page': self.num_pages})
def get_page(self, *args, **kwargs):
page = super().get_page(*args, **kwargs)
try:
page.previous_page_number = page.previous_page_number()
except EmptyPage:
page.previous_page_number = None
try:
page.next_page_number = page.next_page_number()
except EmptyPage:
page.next_page_number = None
page.previous_params = urllib.parse.urlencode({**self.params, 'page': page.previous_page_number})
page.next_params = urllib.parse.urlencode({**self.params, 'page': page.next_page_number})
return page
views.py
from .pagination import Paginator
paginator = Paginator(queryset, per_page, request=request, form=form)
page = paginator.get_page(page_number)
pagination.html
{% if page %}
<nav id="pagination">
{% if page.has_previous %}
« First
Previous
{% endif %}
<span class="current">
Page {{ page.number }} of {{ page.paginator.num_pages }}.
</span>
{% if page.has_next %}
Next
Last »
{% endif %}
</nav>
{% endif %}

Django Reverse for '' with arguments '()' and keyword arguments '{'}' not found Error

I am new to Django and am trying to have a user select from a drop-down of choices, then have their choice pass to my next view class so that the record can be edited. Right now my code passes the name of the disease but not the PK from the database. It seems like a simple problem but I'm not sure how to solve it. I get the following error:
Reverse for 'drui' with arguments '('',)' and keyword arguments '{}' not found.
Code is below:
views.py
def drui_index(request):
diseaseForm = DiseaseForm(request.POST)
if diseaseForm.is_valid():
#the problem is probably in the below line. The code isn't right.
new_disease = diseaseForm.cleaned_data['disease']
url = reverse('drui', kwargs={'someApp_disease_id': new_disease.pk})
return HttpResponseRedirect(url)
else:
diseaseForm = DiseaseForm()
return render_to_response("drui_index.html", {'diseaseForm': diseaseForm}, context_instance=RequestContext(request))
def drui(request, someApp_disease_id):
disease = get_object_or_404(Disease, pk=someApp_disease_id
if request.method == "POST":
diseaseForm = DiseaseForm(request.POST, instance=disease)
indicatorInlineFormSet = IndicatorFormSet(request.POST, request.FILES, instance=disease)
if diseaseForm.is_valid():
new_disease = diseaseForm.save(commit=False)
if indicatorInlineFormSet.is_valid():
new_disease.save()
indicatorInlineFormSet.save()
return HttpResponseRedirect(reverse(valdrui))
else:
diseaseForm = DiseaseForm(instance=disease)
indicatorInlineFormSet = IndicatorFormSet(instance=disease)
return render_to_response("drui.html", {'diseaseForm': diseaseForm, 'indicatorInlineFormSet': indicatorInlineFormSet, 'hide_breadcrumb': hide_breadcrumb},context_instance=RequestContext(request))
forms.py
class DiseaseForm(forms.ModelForm):
disease = forms.ModelChoiceField(queryset=Disease.objects.all())
class Meta:
model = Disease
urls.py
url(r'^drui_index/$', 'someApp.views.drui_index', name='drui_index'),
url(r'^drui/(?P<someApp_disease_id>\d+)/$', 'someApp.views.drui', name='drui')
HTML for drui.html
<form class="disease_form" action="{% url drui someApp_disease_id %}" method="post">{% csrf_token %}
{{ diseaseForm.as_table }}
{{ indicatorInlineFormSet.as_table }}
HTML for drui_index.html
<form class="disease_form" action="{% url drui_index %}" method="post">{% csrf_token %}
{{ diseaseForm.as_table }}
UPDATE
Solved it by adding a .pk in my kwargs. But now I get a Reverse for 'drui' with arguments '('',)' and keyword arguments '{}' not found.
i suppose that 'drui' is another method in the views.py which contain another template you want to redirect to that when diseaseForm.is_valid()
views.py
from django.core import urlresolvers
def drui_index(request):
diseaseForm = DiseaseForm(request.POST)
if diseaseForm.is_valid():
new_disease = diseaseForm.cleaned_data['disease']
url = urlresolvers.reverse('drui', kwargs={'disease_id': new_disease})
return HttpResponseRedirect(url)
else:
diseaseForm = DiseaseForm()
return render_to_response("drui_index.html", {'diseaseForm': diseaseForm}, context_instance=RequestContext(request))
As I've mentioned before, it at least seems that cleaned_data is just returning a CharField, and not a model, and thus you're passing through an empty .pk.
In drui.html you are referring to someApp_disease_id which doesn't exist in the context, this gives you your first error, the one in the original question. To fix this, simply make it available.
I don't know which id is supposed to be stored in this variable but you should have something like this in the drui view
return render_to_response("drui.html",
{'diseaseForm': diseaseForm,
'indicatorInlineFormSet': indicatorInlineFormSet,
'hide_breadcrumb': hide_breadcrumb,
'someApp_disease_id': new_disease.id}, //<<-add this
context_instance=RequestContext(request))
When you added .pk to the drui_index view, you fixed the other issue ("Reverse for 'drui' with arguments '()' and keyword arguments '{'someApp_disease_id': <Disease: someDisease>}' not found.") which allowed you to go to the second error which is the one I have fixed above. So basically you should add the above to drui and leave the .pk in drui_index. That should cure your problems.