I'm having problems with creating an access logger for my tastypie restfull app.
I'd like to log several HTTP headers in requests to server and pass them to logger/handler defined in django Settings file. The idea is to log every HTTP request into a access log file.
I've encountered several logging modules (apps) but they all use database, I want something simpler for a basic access log.
I ended up making my own Middleware class in middleware.py inside application root.
Also I placed 'appname.middleware.RequestLoggerMiddleware', inside Settings.py Middleware section.
Here is the code for my access logging middleware class:
import logging
logger = logging.getLogger('access')
class RequestLoggerMiddleware(object):
def process_request(self, request):
... logging logic here...
logger.info('logging message'))
return None
For more info about Middleware components see Django Middleware documentation.
Another possibility is to override of the ModelResource.dispatch() method, in a custom ModelResource object:
class CustomModelResource(ModelResource):
def dispatch(self, request_type, request, **kwargs):
"""
Override for systematic logging.
"""
log_user = request.META['USER']
log_request_type = request_type
log_resource_name = kwargs['resource_name']
log_api_name = kwargs['api_name']
log_response = {}
try:
response = super(CustomModelResource, self).dispatch(request_type, request, **kwargs)
log_response['response_code'] = response.status_code
# Also log what could go wrong
except Exception, e:
log_response['error_type'] = e.__class__.__name__
log_response['error_message'] = e.message
log_response['response_code'] = http.HttpBadRequest.status_code
raise
finally:
# Log all the things
logger.debug('%s asked for %s on %s through api %s: \n%s' % (
log_user,
log_request_type,
log_resource_name,
log_api_name,
log_response,
))
return response
class Meta:
# Other custom stuff
import logging
logger = logging.getLogger('project.app.view')
def my_view(request):
entry = '%s %s for %s' % (request.method, request.get_full_path(), request.META['REMOTE_ADDR'])
logger.info(entry)
See https://docs.djangoproject.com/en/1.4/topics/logging/
Related
In Flask, is there a way to check which error from errorhandler triggered before_request to be executed?
I want to avoid querying the database for users in 500 error pages in case it's a database-related error. Currently, I have a setup like this:
from flask import request, g, app
from my_app import db, generate_my_sitemap
skip_load_user = set()
#app.before_request
def load_user():
g.user = None
if request.endpoint in skip_load_user:
return
if "user_id" in session:
g.user = db.session.get(session["user_id"])
def add_to_skip_load_user(view):
skip_load_user.add(view.__name__)
return view
#app.route('/sitemap.xml')
#add_to_skip_load_user
def my_view_without_auth():
return generate_my_sitemap()
#app.errorhandler(404)
#app.errorhandler(500)
#add_to_skip_load_user
def http_errors(error):
return render_template('http_error.html')
I noticed that in a 404 error handler, endpoint is None, so it's possible to do this:
if not request.endpoint:
return
However, I only want to make an exception for 500 error pages, not 404 error pages. Is there a way to check if before_request was triggered by a 500 errorhandler?
Edit: I did a workaround by not loading user if endpoint is None on before_request, and then calling the function explicitly on error_handler
#app.before_request
def load_user():
...
if request.endpoint is None:
return
...
#app.errorhandler(404)
#app.errorhandler(500)
def http_errors(error):
if getattr(error, 'code', None) == 404:
load_user()
...
I started noticing that the patch method in django rest framework doesn't actually trigger signals, post methods seem to work fine. This is what I have:
#receiver(signals.pre_save, sender=Example)
def populate_time_fields_based_on_state(sender, **kwargs):
example = kwargs.get('instance')
if example.start_datetime is None and example.projected_end_datetime is None and example.state.label == 'Assigned':
example.start_datetime = datetime.datetime.now()
example.projected_end_datetime = example.created_datetime + datetime.timedelta(
days=example.som_field)
example.save()
And I'm testing this via:
client = APIClient()
client.patch(f'/api/example/1/', {'state': 'Assigned'})
Is there a way to tell it to trigger the signal? Do I need to override the update method in my serializer? I'm trying this:
def partial_update(self, request, *args, **kwargs):
response = super().partial_update(request, *args, **kwargs)
instance = self.get_object()
instance.save()
return response
But it's quite hacky
In your app directory there should be an apps.py, see the docs for the format of it.
Generally it ends up looking like the following, as opposed to the example where they wire up the signal manually. Note that I have a "project/apps/" structure here, but just change the module name depending on where the files actually live:
#project/apps/my_app/__init__.py
default_app_config = 'project.apps.my_app.apps.MyAppConfig'
#project/apps/my_app/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "project.apps.my_app"
verbose_name = "MyApp"
def ready(self):
from project.apps.my_app import signals
# ... other init &/or logging
Note: Feel free to delete the line in init.py, and play with the name in the app config. I'm not sure how critical they actually are
I am wondering if the request is actually being made via http. In my app I have a test that looks like
class Authenticate(APITestCase):
def setUp(self):
self.client = APIClient()
self.password_for_admin = '123456'
self.admin = User.objects.create_superuser(username='myname', email='email#email.com', password='123456')
self.token = Token.objects.create(user=self.admin)
def test_authenticate(self):
""" comment """
self.client.credentials(HTTP_AUTHORIZATION='Basic ' + base64.b64encode('{}:{}'.format(self.admin.username, self.password_for_admin)))
response = self.client.post('/api/authenticate/')
print response
And in my view I've got:
#api_view(('POST',))
def authenticate(request, format=None):
""" comment """
import pprint
log.debug(pprint.pprint(request))
try:
"asdlfjl"
except Exception, e:
response = "An error occurred, {}".format(e)
return Response(response)
My settings looks like:
INSTALLED_APPS = (
...
'django.contrib.sessions',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
...
)
The request is being printed out as None in my log file. I need to get the session. I tried request.session (which was None) and that's what led me to this question.
I figured it out. The server does send a request using the testserver domain. This was sort of a misleading question and the code was wrong. The user is already authenticated using the rest basic backend by the time they reach this view method.
Through much research I found out that the user was being authenticated by rest but the login method doesn't get called by the rest backend. Since login doesn't get called from a rest backend the session is never attached to the request. I changed the authenticate method to login and I simply called login by doing this:
...
#api_view(('POST',))
def login(request, format=None):
try:
from django.contrib.auth import login
if request.user and request.user.is_active:
login(request, request.user)
...
response = ...
else:
response = {}
except Exception, e:
response = "An error occurred, {}".format(e)
return Response(response)
I'm trying to do it this way, but it doesn't work.
class MyView(View):
def options(self, request, *args, **kwargs):
"""
Handles responding to requests for the OPTIONS HTTP verb.
"""
response = http.HttpResponse()
if self.kwargs.has_key('xml'):
response['Content-Type'] = 'text/xml; charset=utf-8'
return response
You don't need to write additional code. Use TemplateResponseMixin and set content_type attribute to whatever you need:
class MyView(TemplateResponseMixin):
content_type='application/xml'
...
I think the key point is render_to_response in django.views.generic.base , whose code is this:
def render_to_response(self, context, **response_kwargs):
"""
Returns a response, using the `response_class` for this
view, with a template rendered with the given context.
If any keyword arguments are provided, they will be
passed to the constructor of the response class.
"""
response_kwargs.setdefault('content_type', self.content_type) # key
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
**response_kwargs
)
As for your case, May be you need this code:
class MyView(ListView):
def get(self, request, *args, **kwargs):
context = self.get_context_data()
if self.kwargs.has_key('xml'):
return self.render_to_response(context, content_type="text/xml; charset=utf-8")
return self.render_to_response(context)
I made a middleware class based off of django-cors-headers so I could allow iframe-ing of part of my django app. I keep a middleware.py in my main project directory and save a couple random middleware classes I have made there, like this one here and a ForceResponse Exception for example.
import re
from django import http
from django.conf import settings
class XFrameAllowMiddleware(object):
def process_request(self, request):
"""
If CORS preflight header, then create an
empty body response (200 OK) and return it
Django won't bother calling any other request
view/exception middleware along with the requested view;
it will call any response middlewares
"""
if (self.is_enabled(request) and
request.method == 'OPTIONS' and
"HTTP_ACCESS_CONTROL_REQUEST_METHOD" in request.META):
response = http.HttpResponse()
return response
return None
def process_response(self, request, response):
if self.is_enabled(request):
response['X-Frame-Options'] = 'ALLOWALL'
return response
def is_enabled(self, request):
return re.match(settings.XFRAME_URLS_REGEX, request.path)
Add it to your MIDDLEWARE_CLASSES and configure the regex in your settings:
MIDDLEWARE_CLASSES = (
...
'your_django_app.middleware.XFrameAllowMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
)
XFRAME_URLS_REGEX = r'^/iframe_this_url/.*$'
from the django-cors-headers read.me:
CORS_URLS_REGEX: specify a URL regex for which to enable the sending of CORS headers; Useful when you only want to enable CORS for specific URLs, e. g. for a REST API under /api/.
Example:
CORS_URLS_REGEX = r'^/api/.*$'
Default:
CORS_URLS_REGEX = '^.*$'
I would like to return some JSON responses back instead of just returning a header with an error code. Is there a way in tastypie to handle errors like that?
Figured it out eventually. Here's a good resource to look at, if anyone else needs it. http://gist.github.com/1116962
class YourResource(ModelResource):
def wrap_view(self, view):
"""
Wraps views to return custom error codes instead of generic 500's
"""
#csrf_exempt
def wrapper(request, *args, **kwargs):
try:
callback = getattr(self, view)
response = callback(request, *args, **kwargs)
if request.is_ajax():
patch_cache_control(response, no_cache=True)
# response is a HttpResponse object, so follow Django's instructions
# to change it to your needs before you return it.
# https://docs.djangoproject.com/en/dev/ref/request-response/
return response
except (BadRequest, ApiFieldError), e:
return HttpBadRequest({'code': 666, 'message':e.args[0]})
except ValidationError, e:
# Or do some JSON wrapping around the standard 500
return HttpBadRequest({'code': 777, 'message':', '.join(e.messages)})
except Exception, e:
# Rather than re-raising, we're going to things similar to
# what Django does. The difference is returning a serialized
# error message.
return self._handle_500(request, e)
return wrapper
You could overwrite tastypie's Resource method _handle_500(). The fact that it starts with an underscore indeed indicates that this is a "private" method and shouldn't be overwritten, but I find it a cleaner way than having to overwrite wrap_view() and replicate a lot of logic.
This is the way I use it:
from tastypie import http
from tastypie.resources import ModelResource
from tastypie.exceptions import TastypieError
class MyResource(ModelResource):
class Meta:
queryset = MyModel.objects.all()
fields = ('my', 'fields')
def _handle_500(self, request, exception):
if isinstance(exception, TastypieError):
data = {
'error_message': getattr(
settings,
'TASTYPIE_CANNED_ERROR',
'Sorry, this request could not be processed.'
),
}
return self.error_response(
request,
data,
response_class=http.HttpApplicationError
)
else:
return super(MyResource, self)._handle_500(request, exception)
In this case I catch all Tastypie errors by checking if exception is an instance of TastypieError and return a JSON reponse with the message "Sorry, this request could not be processed.". If it's a different exception, I call the parent _handle_500 using super(), which will create a django error page in development mode or send_admins() in production mode.
If you want to have a specific JSON response for a specific exception, just do the isinstance() check on a specific exception. Here are all the Tastypie exceptions:
https://github.com/toastdriven/django-tastypie/blob/master/tastypie/exceptions.py
Actually I think there should be a better/cleaner way to do this in Tastypie, so I opened a ticket on their github.