web/ldt/test/client.py
changeset 22 83b28fc0d731
equal deleted inserted replaced
20:20c41a7e2173 22:83b28fc0d731
       
     1 #from django.test.client import Client as DClient
       
     2 from django.conf import settings
       
     3 from django.core.urlresolvers import reverse
       
     4 from django.http import HttpResponse, SimpleCookie
       
     5 from django.test.client import encode_multipart, encode_file, Client, BOUNDARY, \
       
     6     MULTIPART_CONTENT, CONTENT_TYPE_RE
       
     7 from django.utils.encoding import smart_str
       
     8 from django.utils.http import urlencode
       
     9 from ldt.utils import Property
       
    10 from oauth2 import Request, Consumer, Token, SignatureMethod_HMAC_SHA1, \
       
    11     generate_nonce, SignatureMethod_PLAINTEXT
       
    12 from oauth_provider.consts import OUT_OF_BAND
       
    13 from urlparse import urlsplit, urlunsplit, urlparse, urlunparse, parse_qs
       
    14 import httplib2
       
    15 import logging
       
    16 import re
       
    17 try:
       
    18     from cStringIO import StringIO
       
    19 except ImportError:
       
    20     from StringIO import StringIO
       
    21 
       
    22 
       
    23 class WebClient(object):
       
    24     """
       
    25     A class that can act as a client for testing purposes.
       
    26 
       
    27     It allows the user to compose GET and POST requests, and
       
    28     obtain the response that the server gave to those requests.
       
    29     The server Response objects are annotated with the details
       
    30     of the contexts and templates that were rendered during the
       
    31     process of serving the request.
       
    32 
       
    33     Client objects are stateful - they will retain cookie (and
       
    34     thus session) details for the lifetime of the Client instance.
       
    35 
       
    36     This is not intended as a replacement for Twill/Selenium or
       
    37     the like - it is here to allow testing against the
       
    38     contexts and templates produced by a view, rather than the
       
    39     HTML rendered to the end-user.
       
    40     """
       
    41     def __init__(self, **defaults):
       
    42         self.handler = httplib2.Http()
       
    43         #self.defaults = defaults
       
    44         self.cookies = SimpleCookie()
       
    45         #self.exc_info = None
       
    46         #self.errors = StringIO()
       
    47         self.__baseurltuple = ()
       
    48         self.__login_url = None
       
    49     
       
    50     @Property
       
    51     def baseurl():
       
    52         
       
    53         def fget(self):
       
    54             return self.__baseurltuple
       
    55         
       
    56         def fset(self, value):            
       
    57             if isinstance(value, tuple):
       
    58                 self.__baseurltuple = value
       
    59             else:
       
    60                 self.__baseurltuple = urlsplit(unicode(value))
       
    61         
       
    62         return locals()
       
    63 
       
    64     @Property
       
    65     def login_url():
       
    66         
       
    67         def fget(self):
       
    68             return self.__login_url
       
    69         
       
    70         def fset(self, value):            
       
    71             self.__login_url = value
       
    72         
       
    73         return locals()
       
    74     
       
    75 
       
    76     def _mergeurl(self, urltuple):
       
    77         res = ["" for i in range(5)]
       
    78         for i in range(min(len(self.baseurl), len(urltuple))):
       
    79             res[i] = self.baseurl[i] or urltuple[i]
       
    80                 
       
    81         return urlunsplit(res)
       
    82     
       
    83     def _process_response(self, response, content):        
       
    84         resp = HttpResponse(content=content, status=response.status, content_type=response['content-type'])
       
    85         if 'set-cookie' in response:
       
    86             self.cookies.load(response['set-cookie'])
       
    87             resp.cookies.load(response['set-cookie'])
       
    88         
       
    89         resp.client = self
       
    90         resp.raw_response = response
       
    91         for key,value in response.items():
       
    92             resp[key] = value 
       
    93 
       
    94         return resp
       
    95 
       
    96     def _handle_redirects(self, response):
       
    97 
       
    98         response.redirect_chain = []
       
    99         
       
   100         r = response.raw_response.previous
       
   101         while not r is None:
       
   102             response.redirect_chain.append((r['content-location'],r.status))
       
   103             r = r.previous
       
   104         
       
   105         return response
       
   106 
       
   107 
       
   108     def get(self, path, data={}, follow=False, **extra):
       
   109         """
       
   110         Requests a response from the server using GET.
       
   111         """
       
   112         parsed = list(urlsplit(path))
       
   113         parsed[3] = urlencode(data, doseq=True) or parsed[3]
       
   114                 
       
   115         
       
   116         fullpath = self._mergeurl(parsed)
       
   117         self.handler.follow_redirects = follow
       
   118         
       
   119         headers = {}
       
   120         if len(self.cookies) > 0:
       
   121             headers['Cookie'] = self.cookies.output()
       
   122             
       
   123         if extra:
       
   124             headers.update(extra)
       
   125         
       
   126         response, content = self.handler.request(fullpath, method="GET", headers=headers)
       
   127         
       
   128         resp = self._process_response(response, content)
       
   129         
       
   130         if follow:
       
   131             resp = self._handle_redirects(resp)
       
   132         return resp
       
   133 
       
   134 
       
   135     def post(self, path, data={}, content_type="application/x-www-form-urlencoded",
       
   136              follow=False, **extra):
       
   137         """
       
   138         Requests a response from the server using POST.
       
   139         """
       
   140         if content_type == MULTIPART_CONTENT:
       
   141             post_data = encode_multipart(BOUNDARY, data)
       
   142         elif content_type  == "application/x-www-form-urlencoded":
       
   143             post_data = urlencode(data)            
       
   144         else:
       
   145             # Encode the content so that the byte representation is correct.
       
   146             match = CONTENT_TYPE_RE.match(content_type)
       
   147             if match:
       
   148                 charset = match.group(1)
       
   149             else:
       
   150                 charset = settings.DEFAULT_CHARSET
       
   151             post_data = smart_str(data, encoding=charset)
       
   152 
       
   153         parsed = list(urlsplit(path))
       
   154         fullpath = self._mergeurl(parsed)
       
   155         self.handler.follow_redirects = follow
       
   156         
       
   157         headers = {}
       
   158         headers['Content-type'] = content_type
       
   159         if len(self.cookies) > 0:
       
   160             headers['Cookie'] = self.cookies.output()
       
   161             
       
   162         if extra:
       
   163             headers.update(extra)
       
   164 
       
   165         response,content = self.handler.request(fullpath, method="POST", headers=headers, body=post_data)
       
   166         
       
   167         resp = self._process_response(response, content)
       
   168         
       
   169         if follow:
       
   170             resp = self._handle_redirects(response)
       
   171         return resp
       
   172     
       
   173     def login(self, **credentials):
       
   174         """
       
   175         Sets the Client to appear as if it has successfully logged into a site.
       
   176 
       
   177         Returns True if login is possible; False if the provided credentials
       
   178         are incorrect, or the user is inactive, or if the sessions framework is
       
   179         not available.
       
   180         """
       
   181         resp = self.post(path=self.login_url, data=credentials, follow=False, **{"X-Requested-With" : "XMLHttpRequest"})
       
   182         return resp.status_code == 302
       
   183 
       
   184 #
       
   185 #    def head(self, path, data={}, follow=False, **extra):
       
   186 #        """
       
   187 #        Request a response from the server using HEAD.
       
   188 #        """
       
   189 #        parsed = urlparse(path)
       
   190 #        r = {
       
   191 #            'CONTENT_TYPE':    'text/html; charset=utf-8',
       
   192 #            'PATH_INFO':       urllib.unquote(parsed[2]),
       
   193 #            'QUERY_STRING':    urlencode(data, doseq=True) or parsed[4],
       
   194 #            'REQUEST_METHOD': 'HEAD',
       
   195 #            'wsgi.input':      FakePayload('')
       
   196 #        }
       
   197 #        r.update(extra)
       
   198 #
       
   199 #        response = self.request(**r)
       
   200 #        if follow:
       
   201 #            response = self._handle_redirects(response)
       
   202 #        return response
       
   203 #
       
   204 #    def options(self, path, data={}, follow=False, **extra):
       
   205 #        """
       
   206 #        Request a response from the server using OPTIONS.
       
   207 #        """
       
   208 #        parsed = urlparse(path)
       
   209 #        
       
   210 #        r = {
       
   211 #            'PATH_INFO':       urllib.unquote(parsed[2]),
       
   212 #            'QUERY_STRING':    urlencode(data, doseq=True) or parsed[4],
       
   213 #            'REQUEST_METHOD': 'OPTIONS',
       
   214 #            'wsgi.input':      FakePayload('')
       
   215 #        }
       
   216 #        r.update(extra)
       
   217 #
       
   218 #        response = self.request(**r)
       
   219 #        if follow:
       
   220 #            response = self._handle_redirects(response)
       
   221 #        return response
       
   222 #
       
   223 #    def put(self, path, data={}, content_type=MULTIPART_CONTENT,
       
   224 #            follow=False, **extra):
       
   225 #        """
       
   226 #        Send a resource to the server using PUT.
       
   227 #        """
       
   228 #        if content_type is MULTIPART_CONTENT:
       
   229 #            post_data = encode_multipart(BOUNDARY, data)
       
   230 #        else:
       
   231 #            post_data = data
       
   232 #
       
   233 #        # Make `data` into a querystring only if it's not already a string. If
       
   234 #        # it is a string, we'll assume that the caller has already encoded it.
       
   235 #        query_string = None
       
   236 #        if not isinstance(data, basestring):
       
   237 #            query_string = urlencode(data, doseq=True)
       
   238 #
       
   239 #        parsed = urlparse(path)
       
   240 #        r = {
       
   241 #            'CONTENT_LENGTH': len(post_data),
       
   242 #            'CONTENT_TYPE':   content_type,
       
   243 #            'PATH_INFO':      urllib.unquote(parsed[2]),
       
   244 #            'QUERY_STRING':   query_string or parsed[4],
       
   245 #            'REQUEST_METHOD': 'PUT',
       
   246 #            'wsgi.input':     FakePayload(post_data),
       
   247 #        }
       
   248 #        r.update(extra)
       
   249 #
       
   250 #        response = self.request(**r)
       
   251 #        if follow:
       
   252 #            response = self._handle_redirects(response)
       
   253 #        return response
       
   254 #
       
   255 #    def delete(self, path, data={}, follow=False, **extra):
       
   256 #        """
       
   257 #        Send a DELETE request to the server.
       
   258 #        """
       
   259 #        parsed = urlparse(path)
       
   260 #        r = {
       
   261 #            'PATH_INFO':       urllib.unquote(parsed[2]),
       
   262 #            'QUERY_STRING':    urlencode(data, doseq=True) or parsed[4],
       
   263 #            'REQUEST_METHOD': 'DELETE',
       
   264 #            'wsgi.input':      FakePayload('')
       
   265 #        }
       
   266 #        r.update(extra)
       
   267 #
       
   268 #        response = self.request(**r)
       
   269 #        if follow:
       
   270 #            response = self._handle_redirects(response)
       
   271 #        return response
       
   272 #
       
   273 #    def login(self, **credentials):
       
   274 #        """
       
   275 #        Sets the Client to appear as if it has successfully logged into a site.
       
   276 #
       
   277 #        Returns True if login is possible; False if the provided credentials
       
   278 #        are incorrect, or the user is inactive, or if the sessions framework is
       
   279 #        not available.
       
   280 #        """
       
   281 #        user = authenticate(**credentials)
       
   282 #        if user and user.is_active \
       
   283 #                and 'django.contrib.sessions' in settings.INSTALLED_APPS:
       
   284 #            engine = import_module(settings.SESSION_ENGINE)
       
   285 #
       
   286 #            # Create a fake request to store login details.
       
   287 #            request = HttpRequest()
       
   288 #            if self.session:
       
   289 #                request.session = self.session
       
   290 #            else:
       
   291 #                request.session = engine.SessionStore()
       
   292 #            login(request, user)
       
   293 #
       
   294 #            # Save the session values.
       
   295 #            request.session.save()
       
   296 #
       
   297 #            # Set the cookie to represent the session.
       
   298 #            session_cookie = settings.SESSION_COOKIE_NAME
       
   299 #            self.cookies[session_cookie] = request.session.session_key
       
   300 #            cookie_data = {
       
   301 #                'max-age': None,
       
   302 #                'path': '/',
       
   303 #                'domain': settings.SESSION_COOKIE_DOMAIN,
       
   304 #                'secure': settings.SESSION_COOKIE_SECURE or None,
       
   305 #                'expires': None,
       
   306 #            }
       
   307 #            self.cookies[session_cookie].update(cookie_data)
       
   308 #
       
   309 #            return True
       
   310 #        else:
       
   311 #            return False
       
   312 #
       
   313 #    def logout(self):
       
   314 #        """
       
   315 #        Removes the authenticated user's cookies and session object.
       
   316 #
       
   317 #        Causes the authenticated user to be logged out.
       
   318 #        """
       
   319 #        session = import_module(settings.SESSION_ENGINE).SessionStore()
       
   320 #        session_cookie = self.cookies.get(settings.SESSION_COOKIE_NAME)
       
   321 #        if session_cookie:
       
   322 #            session.delete(session_key=session_cookie.value)
       
   323 #        self.cookies = SimpleCookie()
       
   324 
       
   325         
       
   326 class OAuthPayload(object):
       
   327     
       
   328     def __init__(self, servername="testserver"):
       
   329         self._token = None
       
   330         self._servername = servername
       
   331         self._oauth_parameters = {
       
   332             'oauth_version': '1.0'
       
   333         }
       
   334         self._oauth_parameters_extra = {
       
   335             'oauth_callback': 'http://127.0.0.1/callback',
       
   336             'scope':'all'
       
   337         }
       
   338         self.errors = StringIO()
       
   339         
       
   340     def _get_signed_request(self, method, path, params):
       
   341         
       
   342         parameters = params.copy()
       
   343         parameters.update(self._oauth_parameters)
       
   344         oauth_request = Request.from_consumer_and_token(consumer=self._consumer, token=self._token, http_method=method, http_url=path, parameters=parameters)
       
   345         oauth_request.sign_request(SignatureMethod_HMAC_SHA1(), consumer=self._consumer, token=self._token)
       
   346         
       
   347         return oauth_request
       
   348     
       
   349         
       
   350     def set_consumer(self, key, secret):
       
   351         self._consumer = Consumer(key, secret)
       
   352         self._oauth_parameters['oauth_consumer_key'] = key
       
   353         
       
   354     def set_scope(self, value):
       
   355         self._oauth_parameters_extra['scope'] = value
       
   356 
       
   357     def inject_oauth_data(self, path, method, data):
       
   358         
       
   359         path_parsed = urlparse(path)
       
   360                 
       
   361         if method=='GET' and (data is None or len(data) == 0):
       
   362             new_data = parse_qs(path_parsed[4])
       
   363         elif  data is None:
       
   364             new_data = {}
       
   365         else:
       
   366             new_data = data.copy()
       
   367             
       
   368         clean_path = ['']*6
       
   369         clean_path[0] = 'http'
       
   370         clean_path[1] = self._servername
       
   371         for i in range(0,4):
       
   372             clean_path[i] = path_parsed[i] or clean_path[i]
       
   373         path = urlunparse(clean_path)
       
   374         
       
   375         oauth_request = self._get_signed_request(method, path, new_data)
       
   376                 
       
   377         new_data.update(oauth_request)
       
   378         
       
   379         return new_data
       
   380     
       
   381     def login(self, client, login_method, **credential):
       
   382         
       
   383         
       
   384         self._oauth_parameters.update(self._oauth_parameters_extra)
       
   385         #Obtaining a Request Token
       
   386         resp = client.get(reverse('oauth_request_token'), follow=True)
       
   387         if resp.status_code == 200:
       
   388             self._token = Token.from_string(resp.content)
       
   389         else:
       
   390             self.errors.write("oauth_request_token response status code fail : " + repr(resp))
       
   391             return False
       
   392                 
       
   393         #Requesting User Authorization
       
   394         res = login_method(client, **credential)
       
   395         if not res:
       
   396             self.errors.write("login failed : " + repr(credential))
       
   397             return False
       
   398 
       
   399         resp = client.get(reverse('oauth_user_authorization'))
       
   400         if resp.status_code != 200:
       
   401             self.errors.write("oauth_user_authorization get response status code fail : " + repr(resp))
       
   402             return False
       
   403         
       
   404         #"X-Requested-With" : "XMLHttpRequest"
       
   405         resp = client.post(reverse('oauth_user_authorization'), {'authorize_access':1}, **{"X-Requested-With" : "XMLHttpRequest"})
       
   406         if resp.status_code != 302:
       
   407             self.errors.write("oauth_user_authorization post response status code fail : " + repr(resp))
       
   408             return False
       
   409         
       
   410         location_splitted = urlsplit(resp["Location"])
       
   411         location_query_dict = parse_qs(location_splitted[3])
       
   412         self._token.verifier = location_query_dict['oauth_verifier']
       
   413         
       
   414                 
       
   415         #Obtaining an Access Token
       
   416         resp = client.get(reverse('oauth_access_token'))
       
   417         if resp.status_code == 200:            
       
   418             self._token = Token.from_string(resp.content)
       
   419             for key in self._oauth_parameters_extra.keys():
       
   420                 if key in self._oauth_parameters:
       
   421                     del(self._oauth_parameters[key])
       
   422             return True
       
   423         else:
       
   424             self.errors.write("oauth_access_token get response status code fail : " + repr(resp))
       
   425             return False
       
   426 
       
   427     def logout(self):
       
   428         self._token = None
       
   429 
       
   430 METHOD_MAPPING = {
       
   431     'get'     : 'GET',
       
   432     'post'    : 'POST',
       
   433     'put'     : 'POST',
       
   434     'head'    : 'GET',
       
   435     'options' : 'GET',
       
   436     'delete'  : 'GET'
       
   437 }
       
   438 
       
   439 def _generate_request_wrapper(meth):
       
   440     def request_wrapper(inst, *args, **kwargs):
       
   441         path = args[0] if len(args) > 0 else kwargs.get('path','')
       
   442         data = args[1] if len(args) > 1 else kwargs.get('data',{})
       
   443         args = args[2:]
       
   444         if 'path' in kwargs:
       
   445             del(kwargs['path'])        
       
   446         if 'data' in kwargs:
       
   447             del(kwargs['data'])
       
   448         data = inst._oauth_data.inject_oauth_data(path, METHOD_MAPPING[meth.__name__], data)
       
   449         return meth(inst,path=path, data=data, *args, **kwargs)
       
   450     return request_wrapper
       
   451 
       
   452 def _generate_login_wrapper(meth):
       
   453     def login_wrapper(inst, **credential):
       
   454         return inst._oauth_data.login(inst, meth, **credential)
       
   455     return login_wrapper
       
   456     
       
   457 def _generate_logout_wrapper(meth):
       
   458     def logout_wrapper(inst):
       
   459         inst._oauth_data.logout()
       
   460         meth(inst)
       
   461     return logout_wrapper
       
   462         
       
   463 class OAuthMetaclass(type):
       
   464     
       
   465     def __new__(cls, name, bases, attrs):
       
   466         newattrs = {}
       
   467         def set_consumer(inst, key, secret):
       
   468             inst._oauth_data.set_consumer(key,secret)
       
   469         newattrs['set_consumer'] = set_consumer
       
   470         def set_scope(inst, scope):
       
   471             inst._oauth_data.set_scope(scope)
       
   472         newattrs['set_scope'] = set_scope
       
   473         
       
   474         for attrname, attrvalue in attrs.iteritems():
       
   475             if attrname in ('get', 'post', 'head', 'options', 'put', 'delete'):                
       
   476                 newattrs[attrname] = _generate_request_wrapper(attrvalue)
       
   477             elif attrname == 'login':
       
   478                 newattrs[attrname] = _generate_login_wrapper(attrvalue)
       
   479             elif attrname == 'logout':
       
   480                 newattrs[attrname] = _generate_logout_wrapper(attrvalue)
       
   481             else:
       
   482                 newattrs[attrname] = attrvalue
       
   483                 
       
   484         for klass in bases:
       
   485             for attrname, attrvalue in klass.__dict__.iteritems():
       
   486                 if attrname in newattrs:
       
   487                     continue
       
   488                 if attrname in ('get', 'post', 'head', 'options', 'put', 'delete'):
       
   489                     newattrs[attrname] = _generate_request_wrapper(attrvalue)
       
   490                 elif attrname == 'login':
       
   491                     newattrs[attrname] = _generate_login_wrapper(attrvalue)
       
   492                 elif attrname == 'logout':
       
   493                     newattrs[attrname] = _generate_logout_wrapper(attrvalue)
       
   494         
       
   495         init_method = newattrs.get("__init__", None)
       
   496         
       
   497         def new_init(inst, *args, **kwargs):
       
   498             inst._oauth_data = OAuthPayload(attrs.get('servername','testserver'))
       
   499             if init_method is not None:
       
   500                 init_method(*args,**kwargs)
       
   501             else:
       
   502                 super(inst.__class__,inst).__init__(*args,**kwargs)
       
   503         newattrs["__init__"] = new_init
       
   504                         
       
   505         return super(OAuthMetaclass, cls).__new__(cls, name, bases, newattrs)
       
   506     
       
   507 
       
   508             
       
   509 class OAuthClient(Client):
       
   510     __metaclass__ = OAuthMetaclass
       
   511 #    def __init__(self, **default):
       
   512 #        super(OAuthClient,self).__init__(**default)
       
   513 #        self.__token = None
       
   514 #        self.oauth_parameters = {
       
   515 #            'oauth_version': '1.0',
       
   516 #            'oauth_callback': 'http://127.0.0.1/callback',
       
   517 #            'scope':'all'
       
   518 #        }
       
   519 #        
       
   520 #    def __get_signed_request(self, method, path):
       
   521 #        
       
   522 #        oauth_request = Request.from_consumer_and_token(consumer=self.__consumer, token=self.__token, http_method=method, http_url=path, parameters=self.oauth_parameters)
       
   523 #        oauth_request.sign_request(SignatureMethod_HMAC_SHA1(), consumer=self.__consumer, token=self.__token)
       
   524 #        
       
   525 #        return oauth_request
       
   526 #    
       
   527 #        
       
   528 #    def set_consumer(self, key, secret):
       
   529 #        self.__consumer = Consumer(key, secret)
       
   530 #        self.oauth_parameters['oauth_consumer_key'] = key
       
   531 #        
       
   532 #    def set_scope(self, value):
       
   533 #        self.oauth_parameters['scope'] = value
       
   534 #
       
   535 #    def __inject_oauth_data(self, path, method, data):
       
   536 #        
       
   537 #        path_parsed = urlparse(path)
       
   538 #        
       
   539 #        if method=='GET' and len(data) == 0:
       
   540 #            data= parse_qs(path_parsed[4])
       
   541 #            
       
   542 #        clean_path = ['']*6
       
   543 #        clean_path[0] = 'http'
       
   544 #        clean_path[1] = 'testserver'
       
   545 #        for i in range(0,4):
       
   546 #            clean_path[i] = path_parsed[i] or clean_path[i]
       
   547 #        path = urlunparse(clean_path)
       
   548 #        
       
   549 #        oauth_request = self.__get_signed_request(method, path)
       
   550 #                
       
   551 #        data.update(oauth_request)
       
   552 #
       
   553 #
       
   554 #    def get(self, path, data={}, follow=False, **extra):
       
   555 #        
       
   556 #        self.__inject_oauth_data(path, 'GET', data)
       
   557 #        return super(OAuthClient, self).get(path, data, follow, **extra)
       
   558 #
       
   559 #    
       
   560 #    def post(self, path, data={}, content_type=MULTIPART_CONTENT,
       
   561 #             follow=False, **extra):
       
   562 #        self.__inject_oauth_data(path, 'POST', data)
       
   563 #        return super(OAuthClient,self).post(path, data, content_type, follow, **extra)    
       
   564 #    
       
   565 #    def head(self, path, data={}, follow=False, **extra):
       
   566 #        self.__inject_oauth_data(path, 'GET', data)
       
   567 #        return super(OAuthClient, self).head(path, data, follow, **extra)
       
   568 #    
       
   569 #    def options(self, path, data={}, follow=False, **extra):
       
   570 #        self.__inject_oauth_data(path, 'GET', data)
       
   571 #        return options(OAuthClient, self).options(path, data, follow, **extra)
       
   572 #    
       
   573 #    def put(self, path, data={}, content_type=MULTIPART_CONTENT,
       
   574 #            follow=False, **extra):
       
   575 #        self.__inject_oauth_data(path, 'POST', data)
       
   576 #        return super(OAuthClient,self).put(path, data, content_type, follow, **extra)
       
   577 #    
       
   578 #    def delete(self, path, data={}, follow=False, **extra):
       
   579 #        self.__inject_oauth_data(path, 'GET', data)
       
   580 #        return super(OAuthClient, self).delete(path, data, follow, **extra)
       
   581 #    
       
   582 #    ### TODO: better document errors
       
   583 #    def login(self, **credential):
       
   584 #        
       
   585 #        #Obtaining a Request Token
       
   586 #        resp = self.get(reverse('oauth_request_token'), follow=True)
       
   587 #        if resp.status_code == 200:
       
   588 #            self.__token = Token.from_string(resp.content)
       
   589 #        else:
       
   590 #            self.errors.write("oauth_request_token response status code fail : " + repr(resp))
       
   591 #            return False
       
   592 #                
       
   593 #        #Requesting User Authorization
       
   594 #        res = super(OAuthClient, self).login(**credential)
       
   595 #        if not res:
       
   596 #            self.errors.write("login failed : " + repr(credential))
       
   597 #            return False
       
   598 #
       
   599 #        resp = self.get(reverse('oauth_user_authorization'))
       
   600 #        if resp.status_code != 200:
       
   601 #            self.errors.write("oauth_user_authorization get response status code fail : " + repr(resp))
       
   602 #            return False
       
   603 #        
       
   604 #        resp = self.post(reverse('oauth_user_authorization'), {'authorize_access':1})
       
   605 #        if resp.status_code != 302:
       
   606 #            self.errors.write("oauth_user_authorization post response status code fail : " + repr(resp))
       
   607 #            return False
       
   608 #        
       
   609 #        location_splitted = urlsplit(resp["Location"])
       
   610 #        location_query_dict = parse_qs(location_splitted[3])
       
   611 #        self.__token.verifier = location_query_dict['oauth_verifier']
       
   612 #        
       
   613 #                
       
   614 #        #Obtaining an Access Token
       
   615 #        resp = self.get(reverse('oauth_access_token'))
       
   616 #        if resp.status_code == 200:            
       
   617 #            self.__token = Token.from_string(resp.content)
       
   618 #            return True
       
   619 #        else:
       
   620 #            self.errors.write("oauth_access_token get response status code fail : " + repr(resp))
       
   621 #            return False
       
   622 #        
       
   623 #    def logout(self):
       
   624 #        super(OAuthClient,self).logout()
       
   625 #        self._token = None
       
   626 
       
   627 class OAuthWebClient(WebClient):
       
   628     __metaclass__ = OAuthMetaclass
       
   629     servername = '127.0.0.1:8000'