Dynamic URL's inside Google's AppEngine - python

Good Afternoon,
I'm currently trying to build something incredibly simple inside of the Google AppEngine. The goal is to build a simple photo sharing application that will connect back to my iPhone application. It's all a learning experience for both Python and Objective-C.
(I've been a PHP programmer for quite some time).
The goal, create URL's that look like the following:
/img/{{ model.key.id }}
The problem is that it seems no matter how I do the python script, I either end up with an error or simply get nothing to display on my template page that's wrapped in the FOR statement.
My App.yaml File:
application: randomwebappname
version: 1
runtime: python
api_version: 1
handlers:
- url: /media
static_dir: media
- url: /b/.*
script: beta.py
login: required
- url: /.*
script: main.py
My Model (inside beta.py):
class Photo(db.Model):
author = db.StringProperty()
title = db.StringProperty()
slugline = db.StringProperty()
content = db.StringProperty(multiline=True)
coordinates = db.StringProperty()
avatar = db.BlobProperty()
date = db.DateTimeProperty(auto_now_add=True)
My Class For Viewing The Image Page:
class ViewPage(webapp.RequestHandler):
def get(self, id):
template_values = {
'image': image,
}
path = os.path.join(os.path.dirname(__file__), 'templates/view.html')
self.response.out.write(template.render(path, template_values))
I tried all of the following in my class, but they all end in failure. I tried them with key, key.name, and key.id in the URL:
photos = db.Query(Photo).filter('key', slug).fetch(limit=1)
photos = Photo.get_by_key_name(id)
photos = Photo.get_by_key_name(key)
key = db.Key.from_path('Photo', id)
photos = db.GqlQuery("SELECT * FROM Photo WHERE __key__ = :key", key=key)
photos = db.get(photo_key)
photos = self.request.get("id")
My URL's:
application = webapp.WSGIApplication([
('/b/', HomePage),
('/b/upload', UploadPage),
('/b/add', MainPage),
('/b/img', Image),
('/b/img/([-\w]+)', ViewPage),
('/b/submit', Submission)
], debug=True)
The Template Query:
{% for photo in photos %}
<img alt="" title="" src="img?img_id={{ photo.key }}" alt="main image" />
{% endfor %}
This seems like it would be something incredibly simple and I know I'm missing something, but I'm just not sure where it's at. I would write this in PHP, but I like the concept of AppEngine and I've said above, it's a good Python learning experience.
As a side note, this application does work on the homepage of the site. I simply have a GQL query and it outputs the images fine, it just fails when I try to go to the /img/id pages.
Any advice guys (and gals)? Thanks in advance!
UPDATE #1:
As per Jonathan's request, the following is the Image class:
class Image (webapp.RequestHandler):
def get(self):
photo = db.get(self.request.get("img_id"))
if photo.avatar:
self.response.headers['Content-Type'] = "image/png"
self.response.out.write(photo.avatar)
else:
self.response.out.write("No image")
Also, after posting this, I realized that this was part of the problem as I was trying to do /img as the actual image and /img/ to display the view page. I've since changed this and now have a working model. But it's based upon the Key and not key.id:
URL:
('/b/i', ViewPage)
New ViewPage Class:
class ViewPage(webapp.RequestHandler):
def get(self):
image = db.get(self.request.get("id"))
template_values = {
'image': image,
}
path = os.path.join(os.path.dirname(__file__), 'templates/view.html')
self.response.out.write(template.render(path, template_values))
So... to get to the View page (which includes comments and such), you now have to go to the following URL:
/b/i?img_id={{ image.key }}
Atleast the page is working now, but I would much prefer to get the page to look like the following as stated above:
/b/img/{{ image.key.id }}
Update #2: ViewPage class updated as well as URL's:
class ViewPageV2(webapp.RequestHandler):
def get(self, id):
images = [ db.get(id) ]
template_values = {
'image': image,
}
path = os.path.join(os.path.dirname(__file__), 'templates/view.html')
self.response.out.write(template.render(path, template_values))
New URL:
('/b/image/([-\w]+)', ViewPageV2),
The following are two screenshots with one being proof of id "1" existing and the error that is coming with it currently.
alt text http://img215.imageshack.us/img215/9123/screenshot20091130at937.png
alt text http://img215.imageshack.us/img215/2207/screenshot20091130at938.png
Again, Thanks All!

A simple way to grab them all:
photos = Photo.gql('ORDER BY __key__')
For more, see Queries on Keys in the App Engine docs.
Are you storing your photos with predefined keys?
photo = Photo(key_name="xzy123")
photo.put()
Then you can retrieve it in your ViewPage:
photos = [ Photo(key_name="%s" % id) ]
See Getting an Entity Using a Key.
Finally, to retrieve a photo based on its appengine-assigned key and assuming this key is present in the URL (e.g., http://host/b/img/ahByY..., so change your template to generate URLs of this form), use
class ViewPage(webapp.RequestHandler):
def get(self, id):
photos = [ db.get(id) ]
...

Related

Integrating Wagtail pages into existing Django project

I build a website using Django and I need to add functionality to edit every peace of every page blocks on a site. I found Wagtail and read the docs about how to integrate it and thought of the way to integrate it via django views, like this (main is an app name):
main/pages.py:
...
class IndexPage(Page):
header_title = models.CharField(...)
header_subtitle = models.CharField(...)
...
main/views.py:
...
def view_index(request):
pages = Page.objects.all()
page = pages.filter(...) # somehow filter index page
return render(
request,
'index.html',
context={'page': page.specific},
content_type='text/html')
...
Is it good way to use Wagtail pages and integrate it into existing Django views this way? Could any surprises be found along the way?
Finally decided to use a StreamField. StreamField could be used to build a Page, like this:
main/pages.py:
class IndexPage(Page):
content = StreamField([
('title', blocks.CharBlock()),
('subtitle', blocks.CharBlock()),
...
])
IndexPage is a Model in Django terms (so we have a simple DB table) & content is stored like a list of JSON's. This two things gave our team an understanding to use it the way we want. To use it in a Django view this code could be used:
main/views.py:
...
def index_view(request):
page = IndexPage.objects.all().first()
return {
'content': unify(page.content),
}
unify - a special method to create a dict from content, because a content is an array where a title live inside content[0], subtitle - inside content[1], etc... Indices could be randomly rearranged, that's why unify is important to use. After unify have applied we can use a content in a template via attribute {{ content.title }}, {{ content.subtitle }}, etc...

Django + NGINX Protecting Media Links using X-Accel-redirect not working

I am trying to get the image links delivered via Django Views to undergo additional security checks so that only the person with the correct permission can access the link.
Currently, this is the setup: an User X uploads a photo from his account. The files get saved in media/images/ folder. And the django CreateView redirects to 'view/<id_of_image_in_db>' --> Which triggers Django Detailview function .
class PhotosHDView(DetailView):
model = MYPhotos
template_name = 'detail.html'
context_object_name = 'photos'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['userName'] = self.request.user
return context
The detail.html gets the photos object and renders in card.
{% block content %}
<div class="container mx-auto">
<div class="card ">
<img src="{{photos.image.url}}" class="card-img-top" alt="...">
</div>
</div>
{% endblock %}
When the page is rendered "{{photos.image.url}}" will be /media/images/<image_name>
This all works fine in production with NGINX with the default method where the images are served by NGINX without communicating with Django server.
Working NGINX Config:
location /media/ {
autoindex off;
alias /home/ubuntu/photoApp/media/;
}
Now if you right-click the image and get the image URL rendered in detail view and go to an incog window and paste the link, the image will be displayed irrespective of who is accessing it. Because there is no communication from NGINX with django whether proper permission exists.
Tried Solutions:
Based on many answers, I tried using X-Accel-redirect and been failing miserably to get it to work.
**Added in urls.py**
re_path(r'^media/$',login_required(views.sid), name='SecureImages' ),
** sid to function when the path is true **
def sid(request):
print(' Triggered SID FUNCTION', request)
response = HttpResponse()
# Showing cat image irrespective of image requested--> Simple Test
# Once it works, get the image requested from request and send the redirect
response['X-Accel-Redirect'] ='media/images/cat.jpeg'
return request
** NGINX Config Changes **
location /media/ {
autoindex off;
internal; #<-- Added this
alias /home/ubuntu/photoApp/media/;
}
After these changes, none of the images are shown even when accessed by the proper User.
What am I doing wrong ?? Please suggest a solution for this.
Sample GitHub project is here: https://github.com/IamVNIE/django-photoapp-project
Finally Got it to Work..
1 - Make All the views that render images to include ID tag so that it becomes easier to lookup for access
class PhotosHDView(DetailView):
model = MYPhotos
template_name = 'detail.html'
context_object_name = 'photos'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
x = context[self.context_object_name]
context['safeURLs'] = x.image.url.replace('/images','/images/'+str(x.id))
context['userName'] = self.request.user
return context
2 - Urls.py Modification - So that all /media/* urls go through django and not nginx
url(r'^media/(?P<path>.*)', login_required(views.media_access), name='media'),
3 - In views.media_access check for proper access
def media_access(request, path):
#--> Check For Access Code
if access_granted:
response = HttpResponse()
del response['Content-Type']
response['X-Accel-Redirect'] = '/protected/' + path.replace(str(id)+'/','')
#/protected/ will go through Nginx
# Remove the ID that we tagged while rendering the object
print('Final Response', response['X-Accel-Redirect'])
return response
else:
return HttpResponseForbidden('Not authorized to access this media.')
4 - Final nail in the coffin - NGINX Update
location ^~ /protected/ {
internal;
alias /home/ubuntu/photoApp/media/;
}
location / {
# Other Proxy Stuff
proxy_buffering off;
}
Basically made all /media/ requests to go django --> Checks Access --> django tags /media/ as /protected/ --> NGINX picks up /protected/ and serves the file from media folder.
The only drawback is all the ListView and DetailView have to be tagged with object ID and later on stripped away.
I felt without this tag, we would have to make query to search the DB for the file name to lookup proper owner, which would make the system inefficient.
Let me know if this is the correct way.
I have updated the project in github with working code: https://github.com/IamVNIE/django-photoapp-project

Display a picture via Django saved on mysql database

I want to display a picture with Django, which is already saved on a mysql database. In all Methods I found people used models.Imagefield(upload_to='path'), but I think this will save the file into this directory and people from other computers won´t have access later.
So the question is, how do I access the database directly and display the picture without saving it in between?
I already managed to do this in python, but I am not quite sure how to implement the code into the models.py.
Something like this was my approach :
class mysqlpicture(models.Model):
#mysqlpic=something?
def openpic():
connection = pymysql.connect(user='root', passwd='***',
host='localhost',
database='db')
cursor = connection.cursor()
sql1='select * from Pictures'
cursor.execute(sql1)
data=cursor.fetchall()
mysqlpic=io.BytesIO(data[1][0])#one means second element of column zero
#img=Image.open(mysqlpic)
#img.show()
cursor.close()
return mysqlpic
and then I tried in the views.py to give mysqlpicdirectly to the httpresponse like this:
def MysqlView(request):
query_results = mysqlpicture.objects.all()
template = loader.get_template('polls/pic.html')
context = {
'query_results': query_results,
}
return HttpResponse(template.render(context, request))
using a template pic.htmlto insert a picture like:
{% for n in query_results %}
<mysqlpic src="{{ n.mysqlpic.url }}" />
{% endfor %}
Has someone an idea how to to this in the right way?
Yes, using ImageField does allow people from other computers to access it later. It inherits all attributes and methods from FileField which uploads the file into the server in the path specified in upload_to and also saves this path into your database to keep its reference. All of that happens when you call the save() method in your model instance.
Then, you can create the url to build a link by calling your_instance.image_field.url in your template. Also remember to have a look at MEDIA_ROOT and MEDIA_URL which respectively tells Django the server root path for saving your files and the url that handles the media served from MEDIA_ROOT.
You can use BinaryField for this purposes.
But as stated in the django documentation:
Although you might think about storing files in the database, consider that it is bad design in 99% of the cases.
from django.db import models
class MysqlPicture(models.Model):
mysqlpic = models.BinaryField()
# ...
And then to display in the template you can convert binary data to base64:
from base64 import b64encode
def mysql_view(request):
query_results = MysqlPicture.objects.all()
template = loader.get_template('polls/pic.html')
context = {
'images': [b64encode(obj.mysqlpic) for obj in query_results],
}
return HttpResponse(template.render(context, request))
Template:
{% for image in images %}
<img src="data:;base64,{{ image }}">
{% endfor %}

Wagail one page with different content

I am new to Wagtail and python, so could apprectiate some help with my problem.
I have a web app (wagtail web site + rest api backend).
On my website I have 2 pages:
HomePage with list of accessible objects (e.g. photos)
PhotoPage with a detailed information on photo
What I want to achive:
When I click on photo on homepage I am redirected to the photopage
I fill the photopage with information I got from backend
And the photopage url is smth like this http://example.com/photo?id=12345
So, I want to
have 1 model for photopage
fill photopage based on a requested url (i.e. from homepage I redirect user to example.com/photo?id=12345 and it is filled with information on photo with id=12345)
I guess there should be some middleware to parse requested url to get the id and fill the page with info from API. Is there any standard solution to this issue?
Page objects (and any classes that inherit from Page) have a get_context method that can be used to add context pre-rendering of your templates.
from django.shortcuts import get_object_or_404
class PhotoPage(Page):
# your model definition ...
def get_context(self, request):
context = super(PhotoPage, self).get_context(request)
photo_pk = request.GET.get('id',None)
photo = get_object_or_404(YourPhotoModel,pk=photo_pk) # if no matching photo, return 404. You can do whatever you like instead :)
context['photo'] = photo
return context
Now in your photo template you can access your Photo model instance directly...
{{ photo.some_attribute }}
{{ photo.some_other_attribute }}

Uploading files in django admin, download through public site

I am running a Django website and I want to be able to upload a file through my admin panel and then have visitors to the main site be able to download it. I am running my site using Django-nonrel, Django FileTransfers and Google App Engine. I believe the upload functionality is working correctly as I am able to see the file in my App Engine Blob Storage. What I can't seem to figure out is how to present a download link to the specified file on the public website. I have pasted the relevant classes below:
I have an app called Calendar, that has the following model:
class CalendarEvent (models.Model):
start = models.DateTimeField(auto_now=False, auto_now_add=False)
end = models.DateTimeField(auto_now=False, auto_now_add=False)
title = models.CharField(max_length=500)
description = models.TextField()
file = models.FileField(upload_to='uploads/%Y/%m/%d/%H/%M/%S/')
Here is the view:
def calendar(request):
events = CalendarEvent.objects.exclude(start__lt=datetime.datetime.now()).order_by('start')
return render_to_response('home/calendar.html',{'events': events},context_instance=RequestContext(request))
def download_handler(request, pk):
upload = get_object_or_404(CalendarEvent, pk=pk)
return serve_file(request, upload.file, save_as=True)
Here is my admin:
class calendarAdmin(admin.ModelAdmin):
list_display = ('title','start','end')
admin.site.register(CalendarEvent, calendarAdmin)
Finally, here is the relevant part of my template:
{% for e in events %}
{% url Calendar.views.download_handler pk=e.pk as fallback_url %}
Download
{% endfor %}
{% firstof e.file|public_download_url fallback_url %} is just returning blank, i'm not sure where I am going wrong.
The GAE blob store does not support public download according to the documentation here, so if you use the default backend for public download urls, it returns None. So my guess is that e.file|public_download_url always return None. You could verify that.
Then I think your template is wrong. You're trying to access e.views.download_handler where it should be Calendar.views.download_handler if your app is named Calendar.
I think the sample on the django-filetransfers page is error prone because the variable used in the template loop has the same name as the sample app: "upload".
If this doesn't fix it, could you post your urls.py from Calendar app. It could be that the template's url method is not able to resolve the url for Calendar.views.download_handler if there is no mapping in urlpatterns.
You should have something like
urlpatterns = patterns('Calendar.views',
...
(r'^download/(?P<pk>.+)$', 'download_handler'),
...
)
in this file.
I don't see anything special, eg
Download File
should work, or just use e.file.url directly?
I haven't deployed on Google App Engine myself, but this appears to be what django-filetransfers was designed for:
http://www.allbuttonspressed.com/projects/django-filetransfers#handling-downloads
edit: I believe I've answered this in the other question you posted, then: Trouble downlaoding file using Django FileTransfers
I think easiest way is to write a view since this file blob cannot be retrieved directly to write a function which is such:
def file_transfer(req, calendar_event_id):
try:
ce = CalendarEvent.objects.get(calendar_event_id)
except CalendarEvent.DoesNotExist:
raise Http404()
file = ce.file (Write Google appengine specfic routine to pull file)
return HttpResponse(file, media_type='file content type')
Hook it up on urls.py

Categories