MultiValueDictKeyError at /connect/ - django

I'm trying to have all location functionality on one page, such as
get users location, post it to the database, then filter results based on a user-inputted kilometre radius value.
I am getting MultiValueDictKeyError at /connect/ 'latitude' because the POST request is going to
location = Location(latitude=request.POST['latitude'], longitude=request.POST['longitude'], user = request.user)
when it should be going to
if request.POST['radius']:
radius_km = request.POST.get('radius', 0)
I researched how to stop this happening and saw I could do if request.POST['radius']: to direct the post request to the right function.
This hasn't helped with the error unfortunately.
Am I missing something?
views.py
class ConnectView(View):
template_name = 'connect/home.html'
def get(self, request, *args, **kwargs):
f = ProfileFilter(request.GET, queryset=Profile.objects.exclude(user=request.user))
context = {
'users': User.objects.exclude(username=request.user),
'friends': Friend.objects.filter(current_user=request.user),
'filter': f,
}
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
location = Location(latitude=request.POST['latitude'], longitude=request.POST['longitude'], user = request.user)
location.save()
if request.POST['radius']:
radius_km = request.POST.get('radius', 0)
queryset = User.objects.annotate(
radius_sqr=pow(models.F('loc__latitude') -
request.user.loc.latitude, 2) + pow(models.F('loc__longitude') -
request.user.loc.longitude, 2)
).filter(
radius_sqr__lte=pow(int(radius_km) / 9, 2)
).exclude(username=request.user)
context = {'users': queryset}
return JsonResponse({'message': 'success'})
home.html
<script>
var pos;
var $demo;
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
$demo.text("Geolocation is not supported by this browser.");
}
}
function showPosition(position) {
pos = position;
var { latitude, longitude } = pos.coords;
$('#btn_submit').attr("disabled", null);
}
$(document).ready(function() {
$demo = $("#demo");
$('#btn_submit').on('click', function() {
var data = pos.coords;
data.csrfmiddlewaretoken = $('input[name=csrfmiddlewaretoken]').val();
$.post('', data, function() {
alert("Location Confirmed!");
});
});
});
</script>
<h1>Connect with people.</h1>
<!-- GET window.location IP Address / lat lon coordinates -->
<p id="demo"></p>
<button onclick="getLocation()" class="btn btn-warning" id="confirm">1. Find Location</button>
<button type="submit" id="btn_submit" name="btn_submit" class="btn btn-success" disabled>2. Submit Location </button>
<!-- filter by profile attributes -->
<form method="GET">
{{ filter.form }}
<button type="submit" class="small">Search.</button>
</form>
<!-- enter radius to filter by location-->
<form method="POST">
{% csrf_token %}
<input type="number" name="radius">
<input type="submit" value="filter by kilometers">
</form>

In your post method, you have added request.POST['latitude'], which you might not be passing with the post method. And your if.. condition comes after that. Therefore Django gives you error, because it doesn't find any parameter with this name, and will stop the further execution.
So, on post request if you are not passing latitude longitude parameters try writting it as:
request.POST.get('latitude', None)
or,
put your if.. condition before accessing latitude and longitutde parameters.

request.POST is just a python dictionary (almost, it can have multiple values for the same key, hence the class name MultiValueDict). So just treat it as a normal dictionary object:
dict[key] raises a KeyError if the key doesn't exist
dict.get(key) returns the value for key or None if it doesn't exist.
dict.get(key, default) returns the value for key of default if the key doesn't exist.
request.POST['latitude'], request.POST['longitude'] and request.POST['radius'] will throw this exception if these keys aren't in the POST parameters. Always use request.POST.get(param_name) when checking what is being submitted.
In your case, you should also add an if 'latitude' in request.POST and 'longitude' in request.POST clause before doing anything with latitude and longitude.

Related

How to re-display formset & Select2 field with selected value on form error in Django

After searching for several days and trying different options, I decided to finally post the issue and question.
I have a template that has a form and 2 different formsets.
One of the formsets uses an intermediate model with a GenericForeignKey that will reference two other models.
For the formset, I am using an inlineformset and adding a CharField which is used with Select2 to make an ajax call to check the two other models. The value returned by the ajax call will be a json/dict with 3 key/value pairs.
The issue I am having is that when the template is submitted and there are errors, how can I redisplay the value that was entered in the Select2 CharField when the template is presented again?
The value is in self.data and is sent back to the template.
However, everything I've tried so far will not redisplay the select2 field with the value selected previously or the values that were submitted.
The submitted values are returned to the template in a json/dict, key/value, format under form.fieldname.value but I am not sure how I can use that to repopulate the select2 field.
I appreciate any suggestions or links. If there is an alternate way to set this up, I am interested to hear.
Thank you.
UPDATE: 2021-03-18
Here is, hopefully all, the relevant bits from the various files.
models.py
class SiteDomain(models.Model):
website = models.ForeignKey(
WebSite,
on_delete=models.CASCADE,
)
domain_model = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
help_text=(
"The model that the website entry is related to. eg: Domain or SubDomain"
),
)
object_id = models.PositiveIntegerField(
help_text="The ID of the model object the entry is related to."
)
content_object = GenericForeignKey("domain_model", "object_id")
content_object.short_description = "Domain Name:"
views.py
class AddWebsite(View):
def get(self, request, *args, **kwargs):
domain_formset = inlineformset_factory(
WebSite,
SiteDomain,
formset=SiteDomainInlineFormSet,
fields=(),
extra=3,
)
forms.py
class SiteDomainInlineFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
self.account = kwargs.pop('account', None)
super(SiteDomainInlineFormSet, self).__init__(*args, **kwargs)
def add_fields(self, form, index):
super().add_fields(form, index)
form.fields["domain_name"] = forms.CharField(
max_length=255,
widget=forms.Select(),
required=False,
)
template
<script type="text/javascript">
function s2search() {
$('.domain-lookup-ajax').select2({
width: 'style',
ajax: {
url: "{% url 'accounts_ajax:website_domain_lookup' %}",
dataType: 'json',
delay: 250,
data: function (params) {
var query = {
term: params.term,
acct_id: '{{ account.id }}',
}
return query;
},
processResults: function (data, params) {
return {
results: data,
};
},
cache: true
},
placeholder: 'Enter at least 2 characters for search.',
minimumInputLength: 2,
});
}
</script>
<form action="" method="post">
{% csrf_token %}
{{ domain_formset.management_form }}
{{ app_formset.management_form }}
{% for form in domain_formset %}
<div class="domainfieldWrapper" id="row_{{ forloop.counter0 }}">
<select id="id_dform-{{ forloop.counter0 }}-domain_name" class="domain_name domain-lookup-ajax" name="dform-{{ forloop.counter0 }}-domain_name"></select>
<button id="id_dform-{{ forloop.counter0 }}-button" class="button" type="button" onclick="clearSelect('id_dform-{{ forloop.counter0 }}-domain_name')">Clear</button>
</div>
{% endfor %}
</form>
The ajax call will return something like:
{"model_id":"74", "domain_id":"177", "name":"alfa.first-example.com"}
A side note:
I also tested the select2 field in the second formset and it does not get repopulated either when the template is reloaded if there are any form errors. Which I kind of expected since it basically uses the same setup except for the value returned by the ajax call which is for a normal ModelChoiceField.
Using a combination of https://select2.org/programmatic-control/add-select-clear-items#preselecting-options-in-an-remotely-sourced-ajax-select2, js and Django template tags I was able to get something that works for me.

view that outputs user input on a different url

i created a view + form that creates two widgets + a button for a user. One to select a choice and another to type something in. Now i want to redirect the user after the clicking the button to another webpage displaying his input. (Generally i want to know how to access the userinput and further use it).
This is my form:
class Eingabefeld(forms.Form):
eingabefeld = forms.CharField(label="Flight Number",max_length=20)
a = Auswahlmoeglichkeiten.objects.all()
flughafenname = forms.ModelChoiceField(label="Target Airport",queryset=a,empty_label="-------")
source = forms.CharField(
max_length=50,
widget=forms.HiddenInput(),
required=False
)
This is my views.py:
def get_eingabe(request):
log = logging.getLogger(__name__)
if request.method =="POST":
eingabe = Eingabefeld(request.POST)
log.warn(eingabe)
if eingabe.is_valid():
return HttpResponseRedirect("answerrequest")
else:
eingabe = Eingabefeld()
return render(request, "app_one/labels.html", {"eingabe": eingabe})
def answerrequestseite(request):
return render(request, "app_one/answerrequest.html")
and this is my html ( the included html in this one is just for layout):
<head>
<title>Home</title>
</head>
<form method="post" novalidate>
{% csrf_token %}
{% include "app_one/bootstrap_layout2.html" with form=eingabe %}
<div class="row">
<div class="col-sm-5"></div>
<div class="col-sm-2">
<button type="submit" class="btn btn-primary btn-block">Let's Go!</button>
</div>
<div class="col-sm-5"></div>
</div>
</form>
So basically when opening my webpage "get_eingabe" gets called, and the template gets rendered, now when clicking the button the input is validated and after successfull validation a different URL is opened which will trigger the method "answerrequestseite". Now how do i pass the userinput (eingabefeld and flughafenname) into the other method which will render the template for the second URL?
I read alot about using "request.GET" but i am not quite sure where exactly to place it and how.
After if eingabe.is_valid(): create some variable containing the values you want.
then in you redirect you need to pass those values as get argument like:
your_url/?id=123
Then you can retrieve your variable in your views.py via
request.GET.get('id')
But in your case, you don't want to pass simple id, you want to pass user_input.
One way will be to sanitize this input to make it url compatible.
Otherwise the more flexible solution is to store the values in the session.
Session (via cookie)
# views.py
# Set the session variable
request.session['you_variable_name_here'] = 'the value'
# Retrieve the session variable
var = request.session.get['you_variable_name_here']
https://docs.djangoproject.com/en/2.2/topics/http/sessions/
For your exemple in the first view:
if eingabe.is_valid():
eingabefeld = eingabe.cleaned_data.get('eingabefeld')
flughafenname = eingabe.cleaned_data.get('flughafenname')
request.session['eingabefeld'] = eingabefeld
request.session['flughafenname'] = flughafenname.pk
return HttpResponseRedirect("answerrequest")
In the second view:
def answerrequestseite(request):
eingabefeld = request.session.get('eingabefeld')
flughafenname_pk = request.session.get('flughafenname')
flughafenname = YourFlughafennameModel.objects.get(pk=flughafenname_pk)
return render(request, "app_one/answerrequest.html",{'eingabefeld':eingabefeld,'flughafenname':flughafenname})

Form fields missing in Django, just button visable

New to Django and having problem seeing form fields displayed. What I see is just the submit button. If pressed, the form is finally presented, but with the format for a form that had bad data (typical 'this field is required' error for each box, red box, etc).
The form works fine after entering data and again pressing submit (stores entries in my db). I have a number of forms on the same page that have the same behavior.
Example of one form:
#model
class dbPara(models.Model): #parameters
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
username = models.CharField(max_length=10)
turns = models.FloatField(default=27)
units = models.FloatField(default=5)
rise = models.FloatField(default=2.9)
rescutL = models.FloatField(default=0.0833333333)
rescutH = models.FloatField(default=0.333333333)
LorR = models.CharField(max_length=1, default='R')
def __str__(self):
return self.timestamp, self.username, self.turns, self.units, self.rise, self.rescutL, self.rescutH, self.LorR
#form
class ParaForm(ModelForm):
class Meta:
model = dbPara
widgets = {'username': forms.HiddenInput()}
fields =['username', 'turns', 'units', 'rise', 'rescutL', 'rescutH', 'LorR']
#view
def importParameters(request):
if request.method == 'GET':
form = ParaForm()
else:
form = ParaForm(request.POST)
if form.is_valid():
entry=dbPara(username = request.POST.get('username'),
turns = request.POST.get('turns'),
units = request.POST.get('units'),
rise = request.POST.get('rise'),
rescutL = request.POST.get('rescutL'),
rescutH = request.POST.get('rescutH'),
LorR = request.POST.get('LorR')
)
entry.save()
return render(request, 'main.html',
{'ParaHTML' : form })
#url
urlpatterns = patterns('Inputs.views',
url(r'^importParameters/$', 'importParameters', name='urlParameters'),
)
#main.html
<div class='col-lg-3'>
<h4>Set Rosetta Parameters</h4>
<action="{% url "urlParameters" %}" method="post">{% csrf_token %}
{{ ParaHTML|crispy }}
<input type="hidden" name = "username" value = "{{ user.get_username }}">
<input type="submit" class="btn btn-primary" value="Set">
</form>
</div>
Appreciate any advice (better simple than 'most correct but complicated')
Could it be due to using default in the model? Would that not 'fill in the form' and result in 'POST' at the initial visit to the page, resulting in just the button? Thoughts?
One Suggesestion here ....
if Using request.POST.get('anything') simply then it Will raise error if particular string not find as in example('anything') string...
Because request.POST.get('anything') will return None if 'anything' is not in request.POST.
Additionally, .get allows you to provide an additional parameter of a default value which is returned if the key is not in the dictionary.
e.g: Corrected will be request.POST.get('anything', 'mydefaultvalue')

Display a Django form on a page

I am trying to display a comment form on a page. So far I have created a link and I want that each time that link is clicked it displays the form on the same page as where the link is but my problem here is that the link redirects me to another page, which I don't want.
urls.py
url(r'^all/$', 'posts.views.articles'),
url(r'^get/(?P<post_id>\d+)/$', 'posts.views.article'),
url(r'^articles/$', 'posts.views.create'),
url(r'^like/(?P<post_id>\d+)/$', 'posts.views.like_article'),
url(r'^article/(?P<post_id>\d+)/$', 'posts.views.add_comment'),
views.py
def articles(request):
args = {}
args.update(csrf(request))
args ['posts'] = post.objects.filter(user = request.user)
args ['full_name'] = User.objects.get(username = request.user.username)
args ['form'] = PostForm()
return render_to_response('articles.html', args)
def article(request, post_id=1):
return render(request, 'article.html',
{'post': post.objects.get(id=post_id) })
def add_comment(request, post_id):
a = post.objects.get(id=post_id)
if request.method == "POST":
f = CommentForm(request.POST)
if f.is_valid():
c = f.save(commit=False)
c.pub_date = timezone.now()
c.article = a
c.save()
messages.success(request, "You Comment was added")
return HttpResponseRedirect('/posts/get/%s' % post_id)
else:
f = CommentForm()
args = {}
args.update(csrf(request))
args['post'] = a
args['form'] = f
return render_to_response('article.html', args)
#return HttpResponseRedirect('/posts/all')
article.html
<h2>Comments</h2>
{% for c in post.comment_set.all %}
<p>{{c.name}} : {{c.body}}</p>
{% endfor %}
<form action="/posts/article/{{post.id}}/" method="post">{% csrf_token %}
<ul>
{{form.as_ul}}
</ul>
<input type="submit" name="submit" value="Post Comment">
</form>
{% endblock %}
As from your question you want submit a comment in your article and when you submit the comment you want to redirect it to the same article page... If you are willing to do this then here is example:
First create a comment submit form either using model form or just form:
class CommentCreationForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('comment_body',) # Set your field for comment
Now pass this form as context in ur article view. Like you did above.
def articles(request):
args = {}
args.update(csrf(request))
args ['posts'] = post.objects.filter(user = request.user)
args ['full_name'] = User.objects.get(username = request.user.username)
args ['comment_form'] = CommentCreationForm
return render_to_response('articles.html', args)
Your article.html
<h2>Comments</h2>
{% for c in post.comment_set.all %}
<p>{{c.name}} : {{c.body}}</p>
{% endfor %}
<form action=""{% url "comment_submit" post.id %}"" method="get">{% csrf_token %}
<ul>
{{form.as_ul}}
</ul>
<input type="submit" name="submit" value="Post Comment">
</form>
{% endblock %}
Catch the url with url(r'^comment/(?P<pk>\d+)/',CommentSubmitView, name="comment_submit"), and write a view.
def AnswerSubmitView(request, pk):
post = Post.objects.get(id=pk) # Get what you have set for your article
comment_text = request.GET.get('comment_body',False)
com = Comment()
post = post # or anything that you have named for your article..
com.comment_body = comment_text
com.save()
return HttpResponseRedirect('/post/%s' % pk) # Your url for your article I guess...
Enjoy...
Use an ajax call to fetch the form from the server without refreshing the page. This requires jQuery. Replace the placeholder selectors I've used with whatever you need for your app. I'd recommend wrapping all of article.html in a div and give that an id tag (and refer to this tag where I use '#form-id' selector below), so you know when the form is already displayed and you can access the entire chunk.
Also note that I'm not entirely sure how to get the html from render_to_response. Just figure out what kind of object is sent back to the ajax caller and how to get the html from that object. Shouldn't be hard.
Adapt and add the following to the bottom of the template containing the link to add the form
<script>
var showForm = function(url) {
$.ajax({
type: 'GET',
dataType: 'json',
url: url,
success: function(data, status, xhr) {
// Not positive if this is how things work with render_to_response
// I usually use render_to_string for this and just return pure HTML
$('#div-to-display-form-in').append(data);
},
error: function(error) {
// Handle error
}
});
}
$(document).ready(function() {
$('#link-to-show-form').click(function(event) {
event.preventDefault();
// The conditionals check if form is already showing
// If form already showing and link clicked again, form is removed
if ($('#form-id').length === 0) {
showForm($(this).attr('href'));
} else {
$('#form-id').remove();
}
});
});
</script>

Django: How to upload a file using ajax

I am using django 1.5, python 2.7 and jquery 1.9. I have a form which has precisely 2 fields i.e. title and document. When I press submit I want the users chosen document to be present in the request.FILES as shown in the view.
When I submit the regular form (without ajax), this works fine, but with ajax I do not get the file field in my request. Any suggestions on how to upload a file using ajax.
HTML:
<form enctype="multipart/form-data" action="{% url 'upload_document' %}" method="post" id="uploadForm">
{% csrf_token %}
<ul>
<li>
<div>Title</div>
<input id="title" type="text" maxlength="200"/>
<div class="error"></div>
</li>
<li>
<div>Upload File</div>
<input id="document" type="file" size="15" />
<div class="error"></div>
</li>
</ul>
<input type="submit" value="submit"/></p>
</form>
FORMS.PY:
class UploadForm( forms.Form ):
document = forms.FileField()
title = forms.CharField(max_length = 200)
def clean(self):
cleaned_data = super(UploadForm, self).clean()
return cleaned_data
def save(self, *args, **kwargs):
title = self.cleaned_data['title']
doc = self.cleaned_data['document']
document = Document(title = title, document = doc)
document.save()
return document
SCRIPT:
<script type="text/javascript">
$("#uploadForm").submit(function(event){
event.preventDefault();
$.ajax({
url : "{% url 'upload_document' %}",
type: "POST",
data : {csrfmiddlewaretoken: document.getElementsByName('csrfmiddlewaretoken')[0].value,
title: document.getElementById('title').value,
//document: document: document.getElementById('document'),
},
dataType : "json",
success: function( response ){
if(response == "True"){
// success
}
else {
//append errors
}
}
});
});
</script>
VIEWs.PY
def upload_document(request):
print request.POST
print request.FILES
if request.is_ajax():
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES, user = request.user)
if form.is_valid():
form.save()
return HttpResponse(simplejson.dumps('True'), mimetype = 'application/json' )
else:
errors = form.errors
return HttpResponse(simplejson.dumps(errors), mimetype = 'application/json' )
The answer to that question is not that simple. First of all if you intend to support old browsers then indeed it gets nasty. You have to deal with hidden iframes and some JavaScript tricks. I do advice using some well-known scripts for that like jQuery-File-Upload.
But the world is evolving and new technologies arise including HTML5. There's a new File API which is available in most modern browsers ( IE10+, FireFox3.6+, Chrome13+, see: http://caniuse.com/fileapi ) which can be used for that. First you need some HTML:
<input type="file" id="file-select" />
Then you can bind to (for example) change event:
$('#file-select').change( handleFileSelect );
and finally the handler itself:
var data = {};
function createReaderHandler(name) {
return function(ev) {
data[name] = ev.target.result;
};
}
function handleFileSelect(ev) {
var files = ev.target.files; // FileList object
// Loop through the FileList
for (var i = 0; i < files.length; i++) {
var file = files[i],
name = file.name || file.fileName,
reader = new FileReader();
reader.onload = createReaderHandler(name);
reader.readAsText(file);
}
}
Once the data is loaded into JavaScript memory (note that the operation is asynchronous) you can send it via AJAX like any other data. There are more options: depending on your file you can read it as a binary data using .readAsBinaryString and so on. Google is your friend. :)
Also I think there already are good scripts for uploading files with a fallback to old methods. This one can be interesting (haven't tried it):
http://www.plupload.com/
I think the issue is in the submit button, change it into normal button
ie, <button type='button' id='submit'>submit</button>(by default all buttons in form are submit)
and the ajax as
$('#submit').on('click',function(){
frm = $(this).parents('form')
$.ajax({
type: frm.attr('method'),
dataType:'json',
url: frm.attr('action'),
data: frm.serialize(),
async: false,
success: function (data) {
console.log('success')
},
error: function(data) {
console.log("Something went wrong!");
}
})
All others will be same
Just try it will work