Python/Django: automatically log when exceptions occur, including request info - python

I have created a function log_error(request, traceback), which I call in my exceptions. This writes error information to the database. Now, before I open up every one of my views and add this in an exception handler, is there a way to automatically have all exceptions raise to a function, which then calls this?
I have seen this Python error logging, which says to write your own version of sys.excepthook. This function is automatically called when there is an exception. I tried this, but my_excepthook was not called even though I copy-pasted the solution into views.py and raised an error. However, I didn't try too hard because it's not getting all the information that I need, anyway. I also need request so I can log information abut the user, url, etc.
Maybe that's asking too much?
(I'm using Django, but this does not seem like a Django-specific thing) Edit: yes, it is.

J.F Sebastian's suggestion worked. This is a Django solution.
In settings.py MIDDLEWARE_CLASSES:
(I added it as the last one, not sure if this is right or will cause errors down the line. Works for now.)
'myapp.middleware.ExceptionMiddleware',
In myapp.middleware.py:
import traceback
class ExceptionMiddleware(object):
def process_exception(self, request, exception):
log_error(traceback, request)
That's it. log_error is my function and writes to the database. It also appears from the documentation https://docs.djangoproject.com/en/dev/howto/error-reporting/ that I can get the local variables as well as the request attributes.

Related

Django: Context processors for error pages (or request in simple_tag)

In my Django project, there are several django apps. I want to write custom error pages, and I want them to contain correct links to the application that the errors happened in, for example, if a 500-error happened in my app a, I want the error page contain a link to /a/index.html, and if a server error happened in app b, I want the page to contain the link to /b/index.html. And I want to create only one copy of each of the error page files, which means I need to get the name of the app from within the template.
To that end, I have written a custom context processor that adds the app_name var to the templates. I tested it on my normal pages, but when I went on to test it on the error pages, turns out that the context processor isn't firing.
Similarly, I have written a template tag app_aware_url which takes the name of the url pattern and tries to resolve it, but again, turns out that for the error pages the simple_tag(takes_context=True) receives a context that does not contain the request (which is needed for telling which app I am in).
Is there a way round it, or is there a better solution to my problem altogether?
(Django is 1.11)
The correct way to do this seems to be a custom error handler. The documentation on this is a bit... Light? So I had to do some experimentation to get it to work.
It turns out that that's only possible to do globally, in your base urls.py, so you can't have a custom one for a specific app, (which is sad), but you can work out the app from the request.
In your base urls.py:
from .views import ErrorHandler
handler500 = lambda request: ErrorHandler.as_view()(request)
This will now defer to views.ErrorHandler on every 500 that happens:
class ErrorHandler(TemplateView):
template_name = '500.html'
def get_app_name(self):
module = self.request.resolver_match.func.__module__
app, *path = module.split('.')
return app
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['app_name'] = self.get_app_name()
return context
Here, .get_app_name() is doing the magic. It inspects the request's resolver_match - it's path through the urls machinery, and determines which app it was in based on that.
From here, the sky's the limit. I simply subclassed TemplateView for easiness' sake, but you can defer to django.views.defaults.server_error() if you want. Whatever your heart desires.
If you wanna get more hands-on, I've thrown together a repo, (commit b638f8 at the time of writing), which you can feel free to check out and much around with.
For those who may step on the same rakes:
What I ended up doing is passing the "app_name" variable from the error page views to the context. Thus the variable is in the context for non-error pages, because it is set by the context processor, and it is there for error pages because it is passed manually. Doesn't look like a good solution though.

Getting ImageField file path post_delete

I'm making a little piece of software with Django and JS that will handle image uploads. So far, so good. I'm getting nice little images via AJAX from dropzone.js. They are saved on the file system and have an ImageField in my Photo model to keep track of what is stored and where.
I even stabbed dropzone.js to nicely ask my dev server to delete the database entries and the files themselves. I find that the latter part is lacking a bit. So I started writing a function that catches a post_delete signal from my Photo model and has the task of handling the deletion from the file system. The problem is, I can't seem to find a way to get my hands on the file path that's stored in database.
If I've understood correctly, the following should work:
from django.db import models
from django.db.models.signals import post_delete
from django.dispatch import receiver
class Photo(models.Model):
imageFile = models.ImageField(upload_to=generateImageFileNameAndPath)
#receiver(post_delete, sender=Photo)
def cleanupImageFiles(sender, **kwargs):
print("Cleanup called")
p = kwargs['instance']
path = p.imageFile.name
print(path)
But when I try to output path to the console, there's nothing.
Sorry about the upperCasing instead of using under_scores as seems to be Python convention. I personally find the underscore convention a bit annoying and am having a wrestling match inside my head over whether to follow the convention or just go my own way. For now, I've done the latter.
edit: I can't seem to make it work with p.imageFile.url either as suggested here.
editedit: I also tried with pre_delete signal thinking that maybe post_delete the data has already been blown to smithereens, which would be dumb, but who knows :)
edit3: calling imageFile.path, doesn't cut it either. It just produces
[27/Nov/2016 22:29:08] "POST /correcturl/upload/ HTTP/1.1" 200
Cleanup called
[27/Nov/2016 22:29:15] "DELETE /correcturl/upload/ HTTP/1.1" 500 37
on the console window. The HTTP error 500 just comes from the view not being able to handle the delete call because of this code not working properly. That's what I use as status message to the frontend at this point.
It might be worth noting, that if I do
print(p)
the output on the console is
Photo object
If you need the path of the image, try:
path = p.imageField.path
P.S.: Yes, you should follow the convention. Otherwise it will be hard for others to read your code if you share it with somebody, or contribute to an open source project, or hire programmers in your company, etc.
I knew I had to have done some stupid and finally had time to get back to debugging.
In my view I'd done
deletable = Photo(id=id)
instead of
deletable = Photo.objects.get(id=id)
thus ending up with a new photo object with just the id field filled in. Because Photo.save() was never called this didn't end up in my DB and no errors were thrown. Because of this, the bug flew stealthily under my radar.
Thus, when finally calling
deletable.delete()
it only removed the uncomplete instance I had just created. Although, it also deleted the proper entry from the DB. This is what threw me off and made me mostly look elsewhere for the problem thinking I had the correct database object in my hands.
Where this behavior came from remains unclear to me. Does delete() actually check the database for the id (which it in this case would've found) instead of just handling the instance in question? I guess taking a look at django.db.models.Model.delete() could shed some light on this.

Django: how can I automatically generate and cache my urls.py?

My project's URLs are automatically generated in urls.py using a for loop (the URLs look like AppName/ViewName). According to the docs, urls.py is loaded upon every request. This appears to be slowing my site down since it requires a bunch of introspection code, so I want to generate the URLs less frequently. I could of course manually run a script to re-generate urls.py (or a file imported by urls.py) as needed, but would prefer if it happened automatically as part of project validation/startup (like the server starting up or the database being synced). I'm open-sourcing this project, and many people will be running it on their own servers, so I want to do this in a robust way. Any recommendations?
The docs do not say what you claim they do (or rather, you're reading too much into a phrase which only means "loads that Python module (if it has not already been loaded)".
Generally, the only things that happen on every request are running the middleware and the specific view code associated with that request. Even then, nothing is reloaded on every request. URLs, like all Python code, is only loaded when a new process is started, and when that happens depends on your server setup. Your problem is elsewhere: you should profile your application carefully to find out exactly where.
For example you can look for django-json-rpc where author has realized url-generating via decorators. There are main controller which receive all requests and urls dict {'pattern': method}. urls dict filled automatically by decorators like #jsonrpc_method which receive a function and put them to urls.
I think it must run faster than the for and I believe that this approach will be able to apply for django.urls

How to get the permission attached to the called view in Pyramid?

In my forbidden view, I want to redirect all members to their dashboard if they visit a page for guests, and I want to redirect all guests to the login page if they visit a page for members. This is easy enough.
However, there are some cases where I need to throw an HTTPForbidden error which is not the cause of a failed permission and simply display the reason to the user. How can I determine whether an HTTPForbidden is a result of a failed permission or some other reason? I suppose I could do some weird stuff with pyramid.security.has_permission (haven't tried it yet), but there has to be an easier way.
An old question from 2011 where it was stated that this was on the to do list How to check what permission failed in authorization in pyramid (pylons 2)?
This is not exactly what you asked for but it may still help you.
The right answer to your question was already given: you'd better raise a specific exception that is more appropriate than HTTPForbidden.
But if you really want to change the behavior of your forbidden view depending on the missing permission that triggered the HTTPForbidden exception, you need to grab its name.
The name of the missing permission can be found inside the HTTPForbidden exception object in HTTPForbidden.result.permission.
But first, the HTTPForbidden exception needs to be passed as a context of your forbidden view.
For instance, here is how I use this in my webapps to inform the user why he cannot access a particular feature so that he can
ask a manager to re-configure ACL accordingly if appropriate.
#forbidden_view_config(renderer='/forbidden.mako')
def forbidden(context, request):
return { 'required_permission': context.result.permission }
It works with pyramid 1.4.
I couldn't find anything in the documentation so I hacked this by debugging pyramid.
This is more a workaround than a clean solution.
I haven't tested it, but what I'd try to do is to raise something else than HTTPForbidden in the places where you do this manually. You can even subclass HTTPForbidden:
class WeDontLikeYourFace(HTTPForbidden):
pass
def my_view(context, request):
if request['face'] != 'beautyful':
raise WeDontLikeYourFace("go away")
Then you can register a custom view for your custom exception, or do some if/else stuff in the common view method registered for HTTPForbidden.
You can also add custom fields to your subclass to pass any information you need.

Suddenly all HttpResponseRedirect( reverse() ) are giving syntax errors

This one has me scratching my head. I have an app with views that do form processing (logins/signup) and then return various HttpResponseRedirect()s based on the input. All of those redirects contain reverse() lookups with the appropriate functions listed as strings. And every function has a corresponding urlpattern in urls.py.
Everything was working fine until this morning.
Now, whenever I submit a form, Django gives me a syntax error for a non-existent line:
SyntaxError at /logout/
invalid syntax (views.py, line 399)
(That file only has 354 lines)
When I scroll down to look at the traceback, the line that's highlighted is always one with a return HttpResponseRedirect( reverse('app.views.func') ).
Because of these bewildering error messages, I'm not even sure that the problem is really with the HttpResponseRedirect( reverse() )s. I haven't touched any of that code in a few days, so I'm not sure why it would suddenly start throwing out weird errors like that.
Any help debugging this would be much appreciated!
I finally figured it out after consulting the docs for the reverse() function.
When you call reverse(), django first imports your project's URLConf files, which in turn imports every single view module that is declared in your URLconf. My issue was that I was working on a new, totally unrelated view that had a syntax error (on line 399!).
So even though I wasn't viewing a page that was doing anything with the new view, my old view was still getting tripped up with the syntax error because of how reverse() works.
From the docs:
Make sure your views are all correct. As part of working out which URL
names map to which patterns, the reverse() function has to import all
of your URLconf files and examine the name of each view. This involves
importing each view function. If there are any errors whilst importing
any of your view functions, it will cause reverse() to raise an error,
even if that view function is not the one you are trying to reverse.
Make sure that any views you reference in your URLconf files exist and
can be imported correctly. Do not include lines that reference views
you haven't written yet, because those views will not be importable.

Categories

Resources