Profile Pictures Disappeared. Mystery here - GAE (Python) - python

Ok, first I want to state that this is Google App Engine via Python.
Any who,
These are my handlers / routing where the problem is occuring. Please read below for context and specifics:
class GetImage(MainHandler):
def get(self):
img = db.get(self.request.get("entity_id"))
self.response.out.write(img.image)
class Profile(MainHandler):
def get(self, profile_name):
current_user = str(self.user.name)
profile_name = current_user
if self.user:
key='ag5kZXZ-c3VpdGVnYW1lcnINCxIHSW1hZ2VkYhgxDA'
imgs = db.GqlQuery("select * from Imagedb WHERE name =:1", current_user)
for img in imgs:
key = img.key() # this is the key
self.render('profile.html', profile_name = self.user.name, current_user = self.user.name, profile_image = key ,username = self.user.name, email = self.user.email, first_name = self.user.first_name, last_name = self.user.last_name, country = self.user.country, prov_state = self.user.prov_state, city_town = self.user.city_town)
else:
self.redirect('/register')
class Change_Profile_Image(MainHandler):
def get(self):
if self.user:
self.render('change_profile_image.html', username = self.user.name, firstname=self.user.first_name, current_user = self.user.name)
else:
self.render('change_profile_image.html')
def post(self):
imagedb = Imagedb(name = self.user.name)
imageupl = images.resize(self.request.get("img"), 200, 200)
imagedb.image = db.Blob(imageupl)
imagedb.put()
self.redirect('/profile/'+self.user.name)
app = webapp2.WSGIApplication([('/', MainPage),
('/register', Register),
('/article', ArticlePage),
('/profile/([^/]+)', Profile),
('/login', Login),
('/logout', Logout),
('/welcome', Unit3Welcome),
('/games', Games),
('/forum', Forum),
('/media', Media),
('/rank', Rank),
('/review', Reviews),
('/events', Events),
('/alreadyloggedin', AlreadyLoggedIn),
('/change_profile_image', Change_Profile_Image),
('/img', GetImage)],
debug=True)
Alright so here is where stuff gets loopy. If I change the Profile class to take -- get(self) and remove my reg expression from the routing for the profile class, my images work perfectly. As soon as I route to unique profiles, i.e. pass profile_name into the Profile handler and map the URL to that profile, I lose all functionality of my GetImage handler. When I look at the source code, nothing has changed. The image is still being passed into the template as per usual.
Does anyone have any idea as to what is going on here? I would really appreciate it. Thank you very much in advance. Hopefully my knowledge will catch up to you guys and I'll be answering questions soon :p.

It's difficult to answer your question without seeing a (simplified) version of your template.
There are also a couple of weird elements in your code that make it hard to tell what's going on. It's hard to format this as a comment, so I'm putting it as an answer, just so you can at least see it.
In your get request in your profile handler, you try to get the current user before you check that the current user exists. You also just throw away the profile_name element completely when you assign profile_name to current_user, so you'll never get a profile image for anything but the current user on a profile page.
You take in profile_name here, but never use it:
def get(self, profile_name):
current_user = str(self.user.name)
profile_name = current_user
You loop over imgs but replace the key each time, which means that if you return more than 1 image you can't tell that this has occurred and you overwrite anything but the last image in the query. One thing you should do is add a check to see if imgs is even truthy, so you can tell if you got any results whatsoever, that might (though I can't imagine how) explain why your image handler is failing.
Finally, you might check your source to see which image url is actually being requested in the template.

Related

Django Rest Framework gives 302 in Unit tests when force_login() on detail view?

I'm using Django Rest Framework to serve an API. I've got a couple tests which work great. To do a post the user needs to be logged in and I also do some checks for the detail view for a logged in user. I do this as follows:
class DeviceTestCase(APITestCase):
USERNAME = "username"
EMAIL = 'a#b.com'
PASSWORD = "password"
def setUp(self):
self.sa_group, _ = Group.objects.get_or_create(name=settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME)
self.authorized_user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASSWORD)
self.sa_group.user_set.add(self.authorized_user)
def test_post(self):
device = DeviceFactory.build()
url = reverse('device-list')
self.client.force_login(self.authorized_user)
response = self.client.post(url, data={'some': 'test', 'data': 'here'}, format='json')
self.client.logout()
self.assertEqual(status.HTTP_201_CREATED, response.status_code)
# And some more tests here
def test_detail_logged_in(self):
device = DeviceFactory.create()
url = reverse('device-detail', kwargs={'pk': device.pk})
self.client.force_login(self.authorized_user)
response = self.client.get(url)
self.client.logout()
self.assertEqual(status.HTTP_200_OK, response.status_code, 'Wrong response code for {}'.format(url))
# And some more tests here
The first test works great. It posts the new record and all checks pass. The second test fails though. It gives an error saying
AssertionError: 200 != 302 : Wrong response code for /sa/devices/1/
It turns out the list view redirects the user to the login screen. Why does the first test log the user in perfectly, but does the second test redirect the user to the login screen? Am I missing something?
Here is the view:
class APIAuthGroup(InAuthGroup):
"""
A permission to allow all GETS, but only allow a POST if a user is logged in,
and is a member of the slimme apparaten role inside keycloak.
"""
allowed_group_names = [settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME]
def has_permission(self, request, view):
return request.method in SAFE_METHODS \
or super(APIAuthGroup, self).has_permission(request, view)
class DevicesViewSet(DatapuntViewSetWritable):
"""
A view that will return the devices and makes it possible to post new ones
"""
queryset = Device.objects.all().order_by('id')
serializer_class = DeviceSerializer
serializer_detail_class = DeviceSerializer
http_method_names = ['post', 'list', 'get']
permission_classes = [APIAuthGroup]
Here is why you are getting this error.
Dependent Libraries
I did some searching by Class Names to find which libraries you were using so that I can re-create the problem on my machine. The library causing the problem is the one called keycloak_idc. This library installs another library mozilla_django_oidc which would turn out to be the reason you are getting this.
Why This Library Is Causing The Problem
Inside the README file of this library, it gives you instructions on how to set it up. These are found in this file. Inside these instructions, it instructed you to add the AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDS = [
'keycloak_oidc.auth.OIDCAuthenticationBackend',
...
]
When you add this authentication backend, all your requests pass through a Middleware defined inside the SessionRefresh class defined inside mozilla_django_oidc/middleware.py. Inside this class, the method process_request() is always called.
The first thing this method does is call the is_refreshable_url() method which always returns False if the request method was POST. Otherwise (when the request method is GET), it will return True.
Now the body of this if condition was as follows.
if not self.is_refreshable_url(request):
LOGGER.debug('request is not refreshable')
return
# lots of stuff in here
return HttpResponseRedirect(redirect_url)
Since this is a middleware, if the request was POST and the return was None, Django would just proceed with actually doing your request. However when the request is GET and the line return HttpResponseRedirect(redirect_url) is triggered instead, Django will not even proceed with calling your view and will return the 302 response immediately.
The Solution
After a couple of hours debugging this, I do not the exact logic behind this middleware or what exactly are you trying to do to provide a concrete solution since this all started based off guess-work but a naive fix can be that you remove the AUTHENTICATION_BACKENDS from your settings file. While I feel that this is not acceptable, maybe you can try using another library that accomplishes what you're trying to do or find an alternative way to do it. Also, maybe you can contact the author and see what they think.
So i guess you have tested this and you get still the same result:
class APIAuthGroup(InAuthGroup):
def has_permission(self, request, view):
return True
Why do you use DeviceFactory.build() in the first test and DeviceFactory.create() in the second?
Maybe a merge of the two can help you:
def test_get(self):
device = DeviceFactory.build()
url = reverse('device-list')
response = self.client.get(url)
self.assertEqual(status.HTTP_200_OK, response.status_code)
Is this a problem with the setUp() method? From what I see, you may be setting self.authorize_user to a user that was already created on the first test.
Instead, I would create the user on each test, making sure that the user doesn't exist already, like so:
user_exists = User.objects.filter(username=self.USERNAME, email=self.EMAIL).exists()
if not user_exists:
self.authorize_user = User.objects.create_user....
That would explain why your first test did pass, why your second didn't, and why #anupam-chaplot's answer didn't reproduce the error.
Your reasoning and code looks ok.
However you are not giving the full code, there must be error you are not seeing.
Suspicious fact
It isn't be default 302 when you are not logged in.
(#login_required, etc redirects but your code doesn't have it)
Your APIAuthGroup permission does allow GET requests for non-logged-in user ( return request.method in SAFE_METHODS), and you are using GET requests (self.client.get(url))
So it means you are not hitting the endpoint that you think you are hitting (your get request is not hitting the DevicesViewSet method)
Or it could be the case you have some global permission / redirect related setting in your settings.py which could be DRF related..
eg :
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
Guess
url = reverse('device-detail', kwargs={'pk': device.pk})
might not point to the url you are thinking..
maybe there's another url (/sa/devices/1/) that overrides the viewset's url. (You might have a django view based url)
And I didn't address why you are getting redirected after force_login.
If it's indeed login related redirect, all I can think of is self.authorized_user.refresh_from_db() or refreshing the request ..
I guess some loggin related property (such as session, or request.user) might point to old instance .. (I have no evidence or fact this can happen, but just a hunch) and you better off not logging out/in for every test case)
You should make a seperate settings file for testing and add to the test command --settings=project_name.test_settings, that's how I was told to do.

django implementing simple vistor log

I'm fair new to Django and I am trying to log visitor for my blog. I'm using generic view for my blog and here is part of code:
#blog/urls.py
urlpatterns = patterns('',
#index
url(r'^(?P<page>\d+)?/?$', PostListView.as_view(
model=Post,
paginate_by=3,
)),
#individual post
url(r'^(?P<pub_date__year>\d{4})/(?P<pub_date__month>\d{1,2})/(?P<slug>[a-zA-Z0-9-]+)/?$',
DetailView.as_view(model=Post,)),
#cat
url(r'^category/(?P<slug>[a-zA-Z0-9]+)/?$', CategoryListView.as_view(
paginate_by=3,
model=Category,
)),
#tag
url(r'^tag/(?P<slug>[a-zA-Z0-9]+)/?$', TagListView.as_view(
paginate_by=3,
model=Tag,
)),
and I wrote a simple model for visitor log:
#tasks/models.py
class Visitor(models.Model):
visit_stamp = models.DateTimeField(auto_now_add=True)
referer = models.CharField(max_length=100, blank=True)
ip = models.IPAddressField(blank=True)
user_agent = models.CharField(max_length=100, blank=True)
page = models.CharField(max_length=100)
and its view:
#tasks/views.py
def log(request, page):
try:
hit = Visitor()
hit.page = page
hit.ip = request.META.get('REMOTE ADDR', '')
hit.last_visit = datetime.now()
hit.referer = request.META.get('HTTP REFERER', '')
hit.user_agent = request.META.get('HTTP_USER_AGENT', '')
hit.save()
except IntegrityError:
pass
def tracking(request, page):
log(request, page)
return render_to_response(page)
My question is how and where can I call this methods so that I can log a user is visiting a specific page. I'd appreciate any advices.
First off, I assume you don't have access to the apache (or whatever host is running your django app) logs and/or you want to eventually add other things and/or you want it available in the database, as otherwise, you can skip a lot of work and just grep the logs.
Anyways, I'd recommend rewriting track to work as a decorator (and adjust log as you need it... note that I believe you can get the URL from the request object versus passing it in as a page value in case you want to know which specific instance was visited). There are also ways you could probably do this with middleware, but this gives you a pretty good mix of simplicity and ability to control which views get logged.
To borrow an example from http://www.djangofoo.com/253/writing-django-decorators
def track(page):
def decorator(func):
def inner_decorator(request, *args, **kwargs):
log(request, page)
return func(request, *args, **kwargs)
return wraps(func)(inner_decorator)
return decorator
And then in your urls (or you can also do #track to decorate function based views)
url(r'^(?P<page>\d+)?/?$', track("index")(PostListView.as_view(
model=Post,
paginate_by=3,
))),
url(r'^someregexp$', track("pagename")(SomeListView.as_view(
model=Post,
paginate_by=3,
))),
Edit: meant to add. Do note that in general, GET requests are supposed to be idempotent; logging is a gray area but the main thing to keep in mind is that some requests may not get logged as you might expect if the page is cached (for Posts this shouldn't be an issue)

Memcached doesn't update on time

class MainHandler(BaseHandler.Handler):
def get(self):
user = users.get_current_user() or "unknown"
posts = memcache.get('posts_%s' % user.user_id())
if not posts:
q = db.Query(P.Post)
q.filter('author =', users.get_current_user()).order("-date")
posts=q.fetch(5)
memcache.set(key='posts_%s:'%user.user_id(),value=posts)
#q=P.Post.all().filter('user =',users.get_current_user())
self.render('index.html', user=user, posts=posts)
def post(self):
user = users.get_current_user() or "unknown"
author = users.get_current_user()
title = self.request.get('title')
content = self.request.get('content')
p = P.Post(author=author, title=title, content=content)
p.put()
res = memcache.get('posts_%s'%users.get_current_user().user_id())
if res:
res+=p
if len(res)>5:
res=res[1:]
else:
res=[p]
memcache.replace("posts_%s"%user.user_id(),value=res)
self.redirect('/')
When the browser redirects to '/' the last added item isn't in the list(it is added only after reloading). This happens only when I am on development server(on GAE it works OK),and I wonder if it can happen on GAE and what's the problem with this code
Any suggestions would be highly appreciated.
UPD:thx,I made keys the same,but the problem still remains
You're not hitting memcache at all here. You're using a different key format in the post and get methods: in get you use "posts_user" whereas in post you use "user:posts", so the key is never found and you fall through to the db query. And, of course, the query is not up to date because of eventual consistency, which is presumably the whole reason you're using memcache in the first place.
Fix your memcache keys and this should work.
maybe the item is not in memcache when you do replace. Why do you use replace in this case? Any reason not to use memcache.set? In the get function, there is still one place where the key is posts_%s: which is different than the others.

GAE + NDB + Blobstore + Google High Performance Image Serving

I'm making an app to upload text and images. I've readed a lot about blobstore and Google High Performance Image Serving and finally I got a way to implement it all together.
What I want to know is if all is well done or can be do in a better way, and also if it is better to save the serving_url in the model or must be calculated every time I want to print the images in the page.
There is a User and a Picture only.
This is the code (summarized, forget about my custom.PageHandler, that only have functions to render the pages easily, and the stuff for check forms values, etc.):
class User(ndb.Model):
""" A User """
username = ndb.StringProperty(required=True)
password = ndb.StringProperty(required=True)
email = ndb.StringProperty(required=True)
class Picture(ndb.Model):
""" All pictures that a User has uploaded """
title = ndb.StringProperty(required=True)
description = ndb.StringProperty(required=True)
blobKey = ndb.BlobKeyProperty(required=True)
servingUrl = ndb.StringProperty()
created = ndb.DateTimeProperty(auto_now_add=True)
user = ndb.KeyProperty(kind=User)
# This class shows the user pics
class List(custom.PageHandler):
def get(self):
# Get the actual user pics
pics = Picture.by_user(self.user.key)
for pic in pics:
pic.servingUrl = images.get_serving_url(pic.blobKey, size=90, crop=True)
self.render_page("myPictures.htm", data=pics)
# Get and post for the send page
class Send(custom.PageHandler, blobstore_handlers.BlobstoreUploadHandler):
def get(self):
uploadUrl = blobstore.create_upload_url('/addPic')
self.render_page("addPicture.htm", form_action=uploadUrl)
def post(self):
# Create a dictionary with the values, we will need in case of error
templateValues = self.template_from_request()
# Test if all data form is valid
testErrors = check_fields(self)
if testErrors[0]:
# No errors, save the object
try:
# Get the file and upload it
uploadFiles = self.get_uploads('picture')
# Get the key returned from blobstore, for the first element
blobInfo = uploadFiles[0]
# Add the key to the template
templateValues['blobKey'] = blobInfo.key()
# Save all
pic = Picture.save(self.user.key, **templateValues)
if pic is None:
logging.error('Picture save error.')
self.redirect("/myPics")
except:
self.render_page("customMessage.htm", custom_msg=_("Problems while uploading the picture."))
else:
# Errors, render the page again, with the values, and showing the errors
templateValues = custom.prepare_errors(templateValues, testErrors[1])
# The session for upload a file must be new every reload page
templateValues['form_action'] = blobstore.create_upload_url('/addPic')
self.render_page("addPicture.htm", **templateValues)
Basically, I list all the pics, showing the image in a jinja2 template with this line:
{% for line in data %}
<tr>
<td class="col-center-data"><img src="{{ line.servingUrl }}"></td>
So in the List class I calculate each serving url and add it temporarily to the Model. I don't know exactly if will be good to save it directly in the Model, because I don't know if the url can change with the time. Will be the url permanent for the image? In that case I can save it instead of calculate, true?
The Send class only shows a form to upload the image and saves the data to the Model. I always generate a new form_action link in the case of re-render the page, because docs talk about it. Is it right?
The code is working, but I want to know which is the better way to do it, in terms of performance and resource-saving.
You're right. You do want to save the get_serving_url() instead of calling it repeatedly. It stays the same.
Note that there's a delete_serving_url() when you're done with the url, like when you delete the blob.

Overwritting Data in App Engine

I have a function that gets an image from a form, and put's it into the database along with the username. So, here is my database:
class Imagedb(db.Model):
name = db.StringProperty(required = True)
image = db.BlobProperty()
And here is the code that writes to the database:
class Change_Profile_Image(MainHandler):
def get(self):
if self.user:
self.render('change_profile_image.html', username = self.user.name, firstname=self.user.first_name)
else:
self.render('change_profile_image.html')
def post(self):
imagedb = Imagedb(name = self.user.name)
imageupl = self.request.get("img")
imagedb.image = db.Blob(imageupl)
imagedb.put()
self.redirect('/profile')
Any who, it works awesome. Except for one thing. What i'm trying to accomplish is only storing ONE profile picture. What ends up happening is this:
Say I am the user admin. Ill upload a display pic, that pic shows in the profile. Ill upload another one, that one shows. Cool, except for the fact that I have 2 objects in my database that have the name = admin attribute. I would like to edit this...
def post(self):
imagedb = Imagedb(name = self.user.name)
imageupl = self.request.get("img")
imagedb.image = db.Blob(imageupl)
imagedb.put()
self.redirect('/profile')
so that I can post images to the database, but if one exists, it is overwritten. Could anyone help me with this please? I'm relatively new to python and app engine.
If something is unclear, please let me know.
You want to set the key of the Imagedb entity to "name". Essentially, you don't need the name field, but you'll instantiate it like
imagedb = Imagedb(key_name = self.user.name)
The key is a required field on all entities. By using your user name as the key it means every time you refere to a given key, it's the same entity.

Categories

Resources