django: double file upload: js and html - django

I have 2 django models:
class Image(models.Model):
name = models.CharField(max_length=100)
photo = models.ImageField(upload_to='upload/', blank=True)
class Preview(models.Model):
photo = models.ImageField(upload_to='preview', blank=True)
Then I create ImageForm with ModelForm and render it in the html template.
I want to do image preview in the page.
I use https://github.com/blueimp/jQuery-File-Upload/wiki/Basic-plugin
<script src="/static/js/jquery.ui.widget.js"></script>
<script src="/static/js/jquery.iframe-transport.js"></script>
<script src="/static/js/jquery.fileupload.js"></script>
<script>
$(function () {
$('#id_photo').fileupload({
dataType: 'json',
url: '{% url preview %}',
always: function (e, data) {
$('#preview').css({'visibility': 'visible'});
$('#preview').prepend('<img src=/static/'+data.result.url+' />')
}
});
});
</script>
<form method="POST" action="edit/" enctype="multipart/form-data">
<div class="field">
{{ form.photo.errors }}
<label for="id_photo">Photo:</label>
<input type="file" name="photo" id="id_photo" >
</div>
<div id="preview">
</div>
<input type="submit" name="Change">
So with the help of javascipt I send image to server, resize it (in preview view - it process PreviewForm), save on disk and return the url which is inserted into img tag. It works.
After I press Submit button I can't receive image file in my view (edit which process ImageForm) : request.FILES is empty !!!
When I disable "uploading image with js" 'edit-view works fine: request.FILES contain image, and i can save it..
what causes disappearance request.FILES in second POST request ?

The first thing you need to learn is HTTP is stateless protocol. Every request a client does to a server is unique and independent from the previous one.
In your situation, you submit your form with the image that you want to upload and it makes the upload so that you can see the image in request.FILES and display a preview for it.
However, once you make the upload and show a new page, of course request.POST will be empty because you did not uploaded anything, the file is already in your server, waiting to be approved.
The proper solution will be, when preparing the preview, create some hidden input fields which will help you to identify what photo you want to approve (unique ids are very good in this case, also don't expose file paths directly to your client unless you are the only one who's using the app).
Then in the second request, retrieve those hidden fields, locate the image and do what you are going to do; in your case, approve and make it public somehow.

Related

Django & AJAX to show DB objects upon user's input submission

I'm pretty new in the Web development world, have been using Django so far.
I've been trying to figure out how to render data back to page after clicking on a submit button, so I see I'll need to use AJAX for that purpose.
I've created a very simple app just to understand the basics of AJAX.
However, googling, I couldn't really find a basic straight-forward implementation so I kinda got lost...
What I'm trying to achieve:
I have a model called Country:
class Country(models.Model):
name = models.CharField(primary_key=True, max_length=35)
continent = models.CharField(max_length=10)
capital = models.CharField(max_length=35)
currency = models.CharField(max_length=10)
And a super simple main page that asks the user to insert some country name.
The idea is to bring to the page all the info from the DB.
So it would look like this:
Main page HTML body:
<body>
<h2><em>Please type a country name:</em></h2><br><br>
<div class="container">
<form id="get_info" method="post">
{{ form }}
{% csrf_token %}
<input id="submit_button" type="submit" name="submit" value="Get info">
</form>
</div>
</body>
views.py:
from django.shortcuts import render
from country_trivia import forms
def main_page(request):
get_info_form = forms.GetInfo()
return render(request, 'country_trivia/index.html', {'form': get_info_form})
forms.py:
from django import forms
class GetInfo(forms.Form):
country_name = forms.CharField(label="")
I've seen some examples using forms, but I'm not even sure if it's needed, as I've seen some other examples that count on 'onclick' even listeners, then "grab" the text in the search field and pass it via AJAX...
How should I build my AJAX object for that simple purpose, and how should I integrate it?
Do I need to use forms at all?
I don't post anything to DB, just query it and print out data...
Thanks!!

Have url with pk or id to redirect to update page in Django error message

I've created an app, and on the CreateView page, the Submit button works fine to create a new S Reference. I also created an error message if the input value matches an existing Reference. I created button in the error message part and tried to link it to update the page to update these reference fields, like primary contact. I tried many options but have not got right code for the argument with pk or id to get individual record update page.
this is the url in error message.
I tried quite few pk, id options, none of them works.
'pk'=self.pk;
{'pk'=self.pk};
object.id
some code as below
models.py
class LNOrder(models.Model):
reference_number = models.CharField(max_length=15,blank=True, null=True, unique=True, error_messages={'unique':"This reference already exists."})
primary_contact = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
urls.py
urlpatterns = [
path('lfcnotifier', LNCreateView.as_view(), name='lnorder_create'),
path('lfcnotifier/<int:pk>', LNDetailView.as_view(), name='lnorder_detail'),
path('lfcnotifier/<int:pk>/update/', LNUpdateView.as_view(), name='lnorder_update'),
]
template
<div class="input-group mb-3">
<div class="input-group-prepend w-225px">
<label class="input-group-text w-100">S Reference</label>
</div>
<input name="reference_number" type="text" class="form-control" placeholder="Enter your S Reference"/>
<button class="btn btn-primary cardshadow " data-toggle="tooltip" title="Click to submit" style="width:200px;" type="submit">submit</button>
{%for field in form %}
{% for error in field.errors %}
{{ error }} Update Request
{% endfor %}
{% endfor %}
Views.py
class LNCreateView(SuccessMessageMixin,LoginRequiredMixin,CreateView):
model = LNOrder
template_name = 'lfcnotifier/lnorder_create.html'
form_class = LNOrderForm
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
I expect when users click on Update Request button, it'll open the update page to edit the individual reference.
but I got message "Could not parse the remainder: '=self.pk' from ''pk'=self.pk'".
I get slightly different messages when I try the above different options.
I would like to have the right code for the URL to update the page when the Update Request button is clicked.
Thanks,
Additional background, I only put some of template code here to save space. They are in form section. If I use the following code
Update Request
instead of
Update Request
it can open the full list page without issue. I can go to update page from full list page without issue. But I want to open update page from here directly other than have one more step.
This is all kinds of confused.
For a start, you can't use a string on the left-hand side of an expression, either in pure Python or in Django templates.
But secondly, you don't have anything called self there. What you do have would be passed from the view; however it's not clear from the code you have posted which actual view this is. It doesn't seem to be that CreateView, because you are linking to the update. But assuming it's actually the LNDetailView, and assuming that that actually is a DetailView, you have access to the current object in the template exactly as object.
So you would do:
{% url 'lnorder_update' pk=object.pk %}
However again, this makes no sense to actually do. You can't submit a form via an a. You need a <form> element with a button.

How to pass data from one view to the next

Summary: I am trying to build a job site. On index.html the user enters a zip code into a form to see jobs in that zip code, this form is handled with the job_query view. This brings them to another page(search.html) where at first you only see jobs in that specific zip code but I am trying to add a filter that lets the user see jobs within X miles. How can I pass the zip code value entered in the from on index.html to the next page?
index.html:
<h2>Find a Job</h2>
<!--Search Bar-->
<form method = "GET" action = "{% url 'search' %}" >
<div id = "form_grid">
<input name="query" type="text" placeholder="Zip Code">
<button type="submit">Search</button>
</div>
</form>
search.html:
<form method = "GET" action = "{% url 'search' %}" >
<input class="search_bar" name="query" type="text" placeholder="Zip Code">
<button class="search_btn btn btn-outline-success " type="submit">Find Jobs</button>
</form>
<form id="within_miles_form" method = "GET" action = "{% url 'within_miles' %}" >
<input class="search_bar" name="miles" type="text" placeholder="Within X miles of Zip Code">
<button type="submit">Filter</button>
</form>
<!--code to display jobs-->
views.py:
def job_query(request):
if request.method == "GET":
query = request.GET.get('query')
jobs_matching_query = Job.objects.filter(zip_code__iexact = query) | Job.objects.filter(city__iexact=query) | Job.objects.filter(state__iexact=query)
number_of_results = 0
for job in jobs_matching_query:
number_of_results = number_of_results + 1
return render(request, 'core/search.html', {'query': query ,'jobs_matching_query': jobs_matching_query, 'number_of_results': number_of_results})
def within_miles(request):
miles = request.GET['miles']
#how can i use value of the zip code entered?
urls.py:
url(r'^search$', views.job_query, name="search"),
url(r'within_miles', views.within_miles, name="within_miles"),
I think I included all the relevant info but if I am missing something please let me know, thanks in advance for any help.
You can encode the entered ZIP in a URL, pass it through cookies, store it in the session variables, or use a (hidden) input element that forces the browser to pass it through a GET and POST request.
Encode it in the URL
In that case we can rewrite the URL to:
url(r'^within_miles/(?P<zip>[0-9]{5})/$', views.within_miles, name="within_miles"),
So now one can no longer fetch your.domain.com/within_miles, but your.domain.com/within_miles/12345. It makes it easy for a user to "manipulate" the URL, but since the user can probably provide any ZIP, there is probably not much gain to protect that.
In the form, the URL that is generated is thus:
{% url 'within_miles' zip=query %}
(you can use another variable that is more strictly a ZIP code)
You should thus ensure that query is here a five digit string (or otherwise change the expression in the url(..) part such that it allows all possible queries).
Using hidden form elements
We can also encode content in hidden form elements, for example here we can create an element in the form:
<form id="within_miles_form" method = "GET" action = "{% url 'within_miles' %}" >
<input class="search_bar" name="miles" type="text" placeholder="Within X miles of Zip Code">
<input type="hidden" name="zip_code" value="{{ query }}">
<button type="submit">Filter</button>
</form>
We thus add a form element, fill it with some data, and let the browser submit the value again to the next view. Note that again it is the browser that does this, so a user can inspect the DOM (most browsers allow that, and subsequently edit it).
Using session variables and/or cookies
You can also decide to use session variables (stored at server side, so "secure") or cookies (stored at client side, can be tampered with). A potential problem however is that these are stored in the browser, and changes to the cookies in one tab page, thus can have effect in the other tab page. Furthermore cookies and sessions will "die" after the request, and thus can create a lot of trouble in future views.
You can set a session variable in the view with:
request.session['zip_code'] = query
This will thus store an entry at the server side such that another call can retrieve that value again. The request.session acts like a dictionary that keeps some sort of state per session.
setting and obtaining session variables
In another view, you can thus query the request.session, like:
zip_code = request.session.get('zip_code')
setting and obtaining cookies
We can use a similar approach with cookies. A browser however might reject cookies, or manipulate them, so there are not that much guarantees that there is no tampering with the data (in fact there are none). You can set a cookie with:
response = render(request, 'core/search.html', {'query': query ,'jobs_matching_query': jobs_matching_query, 'number_of_results': number_of_results})
response.set_cookie('zip_code', query)
return response
Before we thus return the result of render(..), we call .set_cookie(..) on the result.
We can - for example in a later view - retrieve the content with:
zip_code = request.COOKIES.get('zip_code')
Improving the job_query view
The job_query view however looks a bit strange: it uses all kinds of "uncommon" code practices. For example the number of elements is calculated by iterating over it, instead of taking the len(..). This also looks basically like a ListView [Django-doc] and we can make the query more elengant by using Q-objects [Django-doc]. The listview then looks like:
def JobListView(ListView):
model = Job
context_object_name = 'jobs_matching_query'
template_name = 'core/search.html'
def get_context_data(self, **kwargs):
kwargs = super(JobListView, self).get_context_data(**kwargs)
kwargs.update(
number_of_results=len(kwargs['object_list'],
query = self.request.GET.get('query')
)
return kwargs
In the view, you then not pass the JobListView, but JobListView.as_view() result as a reference.

Django form post request without rendering

I have django form, then for each field I set initial value. How can I make POST request in my view.py (like after pressing submit button) without rendering the form. I just need to send these initial values as POST request to another url.
You can create the form in HTML and set display:none is style attributee of each field. It will hide the form from front-end and when you click on submit button it will send all data in POST request.
<form method='post'>
<input name='field_name' style='display:none' value='abc'>
<input type='submit' value='submit'>
</form>
Above code will show only submit button on front-end and by clicking on it, it will send value of input field in POST request.

Django: custom content in admin form

For one of my models, I want to show extra content in the change_form. Basically, my model looks like this:
class News(models.Model):
...
class NewsFromSource(models.Model):
news = models.ForeignKey(News)
...
I want to add a 'search' button that, when clicked, triggers a web service request to an external news source, pulls down the available content, and lists all the news pieces contained. The user can then select one of the pieces to "attach" to the News currently edited in the admin interface (i.e. create a new NewsFromSource based on the content downloaded through the web service).
I am done with the web service. What is the best approach to implementing the search-button, list display for the results (I have a view and template that work, need to get those into the form somehow) and the saving part?
What I ended up doing is the following:
1)
I created a view for fetching search results, which boils down to this:
#/myproject/admin/views.py
#never_cache
def news_search(request):
#...query web service
if 'q' in request.POST:
search_term = request.POST['q']
else:
search_term = ''
news = NewsSearch()
news.search(search_term)
return render_to_response( 'news_search_results.html',
{ 'q': search_term,
'news': news.result_list,
'page': page,
'page_left': news.page_left,
'page_right': news.page_right}
)
2) I mapped the view:
#/myapp/urls.py
...
url(r'^myapp/news/search/$', views.news_search),
3) I extended change_form.html for the news model with the following code:
#/myproject/templates/admin/myapp/news/change_form.html
{% extends "admin/change_form.html" %}
{% block after_field_sets %}
...
{% csrf_token %}
<input type="text" name="q" id="news-search-term">
<div id="news-search-results"></div>
...
function submitSearchForm() {
$.post("/myapp/news/search/",
{ 'q': $('#news-search-term').val(),
'csrfmiddlewaretoken': $('input[name=csrfmiddlewaretoken]').val() },
function(data){
$('#news-search-results').html(data);
}
);
}
{{ block.super }}
{% endblock %}
4) I created an html template for displaying the results (news_search_results.html, see 1)
So basically I am sending an AJAX request from the admin page to a custom view to retrieve results from the webservice which then are displayed in a div.
Each element in the results list has a button that sends another request that stores the element with the news id as a ForeignKey.
I have no idea whether this is particularly against Django principles. But it seems to work alright.
Suggestions on doing this in a more "Djangonian" way are welcome.
We'll assume you have a related News model. Add that field to raw_id_fields of the modeladmin we're going to hack, then:
Overload the change_form template for this model, extend admin/change_form.html in admin/yourapp/yourmodel/change_form.html
Add javascript in that template to:
Hide the input and magnifier icon from the news raw id field form raw, you can do that in css too
Add something like a span with a button style in that form row that will open a popup when it is clicked
The popup it should open should be your working view/template with a form to select the news
When the user selects a news, the popup should do an ajax post request to get the news id, and close itself
the value is set to the raw id field input that is hidden, this is pretty tough but fear not someone (disclamer: I) published an article with the whole technical details, also found another one but I didn't test it
It's going to be quite some work. Patience and perseverance will be your best qualities for this mission B)