GAE Blobstore: upload blob along with other text fields - python

I have a form that includes, between text fields, an element to upload a picture.
I want to store the blob in the blobstore and reference it in my model (ndb.Model) using ndb.BlobKeyProperty().
The method shown in this link uses an upload handler (UploadHandler) which is called from the link created in this way:
upload_url = blobstore.create_upload_url('/upload')
upload_url is the form action in the page created to upload the blob. However, my form includes other fields that are not processed in the UploadHandler post method.
The temporary solution I found was to create a handler for my form that inherits from my BaseHandler AND from BlobstoreUploadHandler:
class EditProfile(blobstore_handlers.BlobstoreUploadHandler, BaseHandler)
def get(self):
params['upload_url'] = blobstore.create_upload_url('/upload_blob1')
... fields ...
def post(self):
upload_blob = self.get_uploads()
blob_key = upload_blob[0].key()
value_field1 = self.request.POST.get('field1')
value_field2 = self.request.POST.get('field2')
value_field3 = self.request.POST.get('field3')
...
This method works, except that I have to define a new handler in main.py for each page that has a blob to be uploaded:
app = webapp2.WSGIApplication([ ('/upload_blob1', handlers.EditProfile),
('/upload_blob2', handlers.EditBlob2Handler),
('/serve/([^/]+)?', handlers.ServeHandler) ],
debug=os.environ['SERVER_SOFTWARE'].startswith('Dev'), config=webapp2_config)
Question: how can I use one single upload handler (for instance: UploadHandler) that is called from different pages to perform the upload blob task? I know this might be very simple for an experienced GAE programmer, but I haven't found a solution around.

Short answer: Yes you can.
The handler is just the code that parse your upload form and then performs action based on the information.
Technically, you can have one form even for different uploads, but it really depends on how different and whether or not you wish to split up the code.
For example, if your form1 uploads "First name" "Last name" "Favorite color" and your form2 uploads "First name" "Last name" "Favorite number", then your /upload handler can simply read the parameter and process them differently.
If (req.getParameter("Favorite_Number") != null) {Do whatever}
Else if (req.getParameter("Favorite_Color") != null) {Do whatever}
It's just a matter of design. Your question of whether or not you CAN use one handler, the answer is yes. However, it's recommended to use different ones if they are functionally different.

Related

How can I redirect to a new page after submitting a form using Jinja template?

I'm working my way through Google's App Engine Guestbook example (to be found here: https://cloud.google.com/appengine/docs/standard/python/getting-started/creating-guestbook)
I'm trying to redirect the output (the Greetings) to another page as opposed to the index.html where they're currently displayed after a user presses the "Sign the Guestbook" button. I created a separate page called greetings.html where I copied the display code from the index.html page. However, I don't know how to modify guestbook.py to make the output go to the new page.
webapp2 has a built-in redirect method:
return redirect('/some-path')
However, I think you would probably rather send the gathered data to the greetings.html template? Under the POST method, you could do:
template_values = {
'guestbook_name': guestbook_name,
# etc.,
}
template = JINJA_ENVIRONMENT.get_template('greetings.html')
self.response.write(template.render(template_values))

Pass variable from jinja2 template to python

Sorry if this is a noob question I am still learning. I have passed a variable from python code to a jinja2 HTML template to set up a URL, like this:
Delete
When this link is pressed it should run a query that deletes the entity with that ID. But when the link is pressed it goes to /delete/1827424298 for example, which results in a 404 error as the request handler doesn't exist.
I need to pass that ID back into my python code so it can run a method to delete the entity with that same ID. How do I go about doing this? Using webapp2 if that is important.
class DeleteRequestHandler(webapp2.RequestHandler):
def get():
template = template_env.get_template('myrequests.html')
context = {
'results': results.key.id()
}
self.response.out.write(template.render(context))
EDIT: I've added my delete handler - it is incomplete as I have yet to add the query to delete the entity. My thinking behind it so far is I can grab the results.key.id() from the jinja2 template and put it into results but I am not sure if this would work.
So I think what you're confused about is how to set up a route handler with a dynamic part to the URL. It's a shame that this is completely skipped over in the webapp2 tutorial, as it's a fundamental part of writing any web application. However, it is covered well in the guide to routing, which you should read.
At its simplest, it's just a matter of putting a regex in the route:
app = webapp2.WSGIApplication([
...
(r'/delete/(\d+)', MyDeleteHandler),
])
which will now route any URL of the form /delete/<number>/ to your deletion handler.
The ID that you pass in the URL will be the first positional argument to the handler method:
class MyDeleteHandler:
def get(self, item_id):
key = ndb.Key(MyModel, item_id) # or whatever

Django - Problems with PDF upload

I got another problem with Django. I want to upload an PDF with an Form in the template, when I click upload in my form, this happens:
Cannot assign "<InMemoryUploadedFile: thebook.pdf (application/pdf)>": "Product.book" must be a "File" instance.
This is the line in my model
book = FilerFileField(null=true,blank=true)
This is the line in my form
book = forms.FileField(label=u"Book Upload")
Django's forms.FileField expects an UploadedFile. Whereby the FilerFileField is actually a subclasses of django.db.models.ForeignKey. Therefor you should use a ChoiceField at your form.
book = forms.ModelChoiceField(queryset=filer.models.File.objects.all())
See also django-filer's usage notes and django's docs on the ModelChoiceField:
http://django-filer.readthedocs.org/en/latest/usage.html
https://docs.djangoproject.com/en/dev/ref/forms/fields/#modelchoicefield

Difficulty having Django find database user

I'm writing a web app which has a page for admin tasks. One of the tasks is that the admin users must be able to edit other users details. Alas, I've fallen at quite a simple roadblock.
I've set up a very simple jQuery AJAX Get request, successfully transferring a string to the server and back. This is just background, but not the issue. The issue lies in retrieving other user's objects.
At the moment, with a username I know exists, this code which is accessed in views.py, produces a 500 Internal Server Error.
#login_required
def user_edit_getuser(request):
# Like before, get the request's context.
context = RequestContext(request)
inputname = request.GET['inputNameSend']
user_obj = User.objects.get(inputname)
return HttpResponse(inputname) #later will return a JSON String
get takes keyword arguments only: the key is the field to look up.
user_obj = User.objects.get(username=inputname)
Also, you should probably deal with the possibility that the GET request has no inputNameSend key.
For JS development, you can usually see the error page in the Chrome dev tools/Firebug console in the Network tab.

Django - Uploaded file type validation

I need to validate the file type of the uploaded file and should allow only pdf, plain test and MS word files. Here is my model and and the form with validation function. But, I'm able to upload files even without the extension.
class Section(models.Model):
content = models.FileField(upload_to="documents")
class SectionForm(forms.ModelForm):
class Meta:
model = Section
FILE_EXT_WHITELIST = ['pdf','text','msword']
def clean_content(self):
content = self.cleaned_data['content']
if content:
file_type = content.content_type.split('/')[0]
print file_type
if len(content.name.split('.')) == 1:
raise forms.ValidationError("File type is not supported.")
if content.name.split('.')[-1] in self.FILE_EXT_WHITELIST:
return content
else:
raise forms.ValidationError("Only '.txt' and '.pdf' files are allowed.")
Here is the view,
def section_update(request, object_id):
section = models.Section.objects.get(pk=object_id)
if 'content' in request.FILES:
if request.FILES['content'].name.split('.')[-1] == "pdf":
content_file = ContentFile(request.FILES['content'].read())
content_type = "pdf"
section.content.save("test"+'.'+content_type , content_file)
section.save()
In my view, I'm just saving the file from the request.FILE. I thought while save() it'll call the clean_content and do content-type validation. I guess, the clean_content is not at all calling for validation.
You approach will not work: As an attacker, I could simply forge the HTML header to send you anything with the mime type text/plain.
The correct solution is to use a tool like file(1) on Unix to examine the content of the file to determine what it is. Note that there is no good way to know whether something is really plain text. If the file is saved in 16 bit Unicode, the "plain text" can even contain 0 bytes.
See this question for options how to do this: How to find the mime type of a file in python?
You can use python-magic
import magic
magic.from_file('/my/file.jpg', mime=True)
# image/jpeg
This is an old question, but for later users main question as mentioned in comments is why field validation not happens, and as described in django documentation field validation execute when you call is_valid(). So must use something sa bellow in view to activate field validation:
section = models.Section.objects.get(pk=object_id)
if request.method == 'POST':
form = SectionForm(request.POST, request.FILES)
if form.is_valid:
do_something_with_form
Form validation happens when the data is cleaned. If you want to customize this process, there are various places to make changes, each one serving a different purpose. Three types of cleaning methods are run during form processing. These are normally executed when you call the is_valid() method on a form

Categories