Search code examples
djangomonkeypatchingdjango-authentication

Monkey patched django auth's login, now its tests fail


My app seeks to wrap the django.contrib.auth.views login and logout views with some basic auditing/logging capabilities. I'm following the prescription as described in the django-axes project, and in running on a server and some other tests, it works as expected, transparently without issue.

The code goes like this:

from django.contrib.auth import views as auth_views
from myapp.watchers import watch_login

class WatcherMiddleware(object):
    def __init__(self):
        auth_views.login = watch_login(auth_views.login)

And

def watch_login(func):
    def decorated_login(request, *args, **kwargs):
        #do some stuff
        response = func(request, *args, **kwargs)
        #more stuff
        return response
    return decorated_login

Urls:

#Edit: Added project's urls - just using vanilla django's auth login
(r'^accounts/login/$', 'django.contrib.auth.views.login',{"template_name":settings.LOGIN_TEMPLATE }),

However, in our build workflow, we run into some issues in the django.contrib.auth.tests.views.

Specifically, these are the tests that fail in django.contrib.auth:

ERROR: test_current_site_in_context_after_login (django.contrib.auth.tests.views.LoginTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Python26\lib\site-packages\django\contrib\auth\tests\views.py", line 192, in test_current_site_in_context_after_login
    response = self.client.get(reverse('django.contrib.auth.views.login'))
  File "C:\Python26\lib\site-packages\django\core\urlresolvers.py", line 351, in reverse
    *args, **kwargs)))
  File "C:\Python26\lib\site-packages\django\core\urlresolvers.py", line 297, in reverse
    "arguments '%s' not found." % (lookup_view_s, args, kwargs))
NoReverseMatch: Reverse for 'myapp.watchers.decorated_login' with arguments '()' and keyword arguments '{}' not found.

======================================================================
ERROR: test_security_check (django.contrib.auth.tests.views.LoginTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Python26\lib\site-packages\django\contrib\auth\tests\views.py", line 204, in test_security_check
    login_url = reverse('django.contrib.auth.views.login')
  File "C:\Python26\lib\site-packages\django\core\urlresolvers.py", line 351, in reverse
    *args, **kwargs)))
  File "C:\Python26\lib\site-packages\django\core\urlresolvers.py", line 297, in reverse
    "arguments '%s' not found." % (lookup_view_s, args, kwargs))
NoReverseMatch: Reverse for 'myapp.watchers.decorated_login' with arguments '()' and keyword arguments '{}' not found.

Only these two tests fail with the inclusion of the wrapped login monkey patch.
It seems like the reverse() call in the django auth test behaves differently than how an unpatched function does its thing.

The reason why we're going this route for wrapping logging vs. using django 1.3's new authentication signals is because the logging method provided there only tells you if a wrong attempts happens - it doesn't give you access to the request object to log additional information around that improper request. Patching the authentication form in that case would not have been helpful, hence our need to wrap the login function.

Am I doing something wrong with my wrap of the login function? Or is this as to be expected with tests failing due to other side effects, despite no change in overall functionality?

edit: I'm running python 2.6.4, django 1.2.5


Solution

  • Couldn't you simply wrap it in another view?

    urls:

    url(
        r'^accounts/login/$',
        'accounts.views.login',
        {"template_name":settings.LOGIN_TEMPLATE }
    ),
    

    accounts.views:

    from django.contrib.auth import views as auth_views   
    
    def login(request, *args, **kwars):
        # do some stuff    
        response = auth_views.login(request, *args, **kwars)
        # more stuff
        return response
    

    Like this, django.contrib.auth.tests will be testing the view which they were written for and you can write your own tests for the "more stuff" you need.