Back to index

plone3  3.1.7
Negotiator.py
Go to the documentation of this file.
00001 import logging
00002 import types
00003 from utils import log
00004 
00005 _langPrefsRegistry = {}
00006 
00007 def getAcceptedHelper(self, request, kind='language'):
00008     """this is patched on prefs classes which don't define the getAccepted
00009     classes but define the deprecated getPreferredLanguages method"""
00010     return self.getPreferredLanguages()
00011 
00012 def registerLangPrefsMethod(prefs, kind='language'):
00013     # check for correct format of prefs
00014     if type(prefs) is not type({}): prefs = {'klass':prefs,'priority':0}
00015     # add chain for kind
00016     if not _langPrefsRegistry.has_key(kind): _langPrefsRegistry[kind]=[]
00017     # backwards compatibilty monkey patch
00018     if not hasattr(prefs['klass'], 'getAccepted'): prefs['klass'].getAccepted = getAcceptedHelper
00019     # add this pref helper
00020     _langPrefsRegistry[kind].append(prefs)
00021     # sort by priority
00022     _langPrefsRegistry[kind].sort(lambda x, y: cmp(y['priority'], x['priority']))
00023 
00024 def getLangPrefs(env, kind='language'):
00025     """get higest prio method for kind"""
00026     for pref in  _langPrefsRegistry[kind]:
00027         handler = pref['klass'](env)
00028         accepted = handler.getAccepted(env, kind)
00029         if accepted:
00030             return accepted
00031     return ()
00032 
00033 def lang_normalize(lang):
00034     """filter"""
00035     return lang.replace('_', '-')
00036 
00037 def str_lower(aString):
00038     """filter"""
00039     return aString.lower()
00040 
00041 def str_strip(aString):
00042     """filter"""
00043     return aString.strip()
00044 
00045 def type_accepted(available, preferred):
00046     # ex: preferred is text/* and available is text/html
00047     av = available.split('/')
00048     pr = preferred.split('/')
00049     if len(av) < 2 or len(pr) < 2:
00050         return False
00051     return pr[1] == '*' and pr[0] == av[0]
00052 
00053 def lang_accepted(available, preferred):
00054     # ex: available is pt, preferred is pt-br
00055     return available.startswith(preferred)
00056 
00057 def _false(*a, **kw):
00058     pass
00059 
00060 
00061 class BrowserAccept:
00062 
00063     filters = {
00064         'content-type': (str_lower,),
00065         'language': (str_lower, lang_normalize, str_strip),
00066     }
00067 
00068     def __init__(self, request):
00069         pass
00070 
00071     def getAccepted(self, request, kind='content-type'):
00072         get = request.get
00073         custom_name = ('user_%s' % kind).lower()
00074         if kind == 'content-type':
00075             header_name = ('HTTP_ACCEPT').upper()
00076         else:
00077             header_name = ('HTTP_ACCEPT_%s' % kind).upper()
00078 
00079         try:
00080             user_accepts = get(custom_name, '')
00081             http_accepts = get(header_name, '')
00082         except:
00083             from traceback import print_exc
00084             print_exc()
00085             return
00086         if user_accepts and http_accepts and user_accepts == request.cookies.get('custom_name'):
00087             user_accepts = [a.strip() for a in user_accepts.split(',')]
00088             http_accepts = [a.strip() for a in http_accepts.split(',')]
00089             for l in user_accepts:
00090                 if l not in http_accepts:
00091                     req_accepts = user_accepts + http_accepts
00092                     break
00093                 else:
00094                     # user_accepts is a subset of http_accepts
00095                     request.RESPONSE.expireCookie('custom_name', path='/')
00096                     req_accepts = http_accepts
00097         else:
00098             req_accepts = (user_accepts +','+ http_accepts).split(',')
00099 
00100         accepts = []
00101         i=0
00102         length=len(req_accepts)
00103         filters = self.filters.get(kind, ())
00104 
00105         # parse quality strings and build a tuple like
00106         # ((float(quality), lang), (float(quality), lang))
00107         # which is sorted afterwards if no quality string is given then the
00108         # list order is used as quality indicator
00109         for accept in req_accepts:
00110             for normalizer in filters:
00111                 accept = normalizer(accept)
00112             if accept:
00113                 l = accept.split(';', 2)
00114                 quality = []
00115 
00116                 if len(l) == 2:
00117                     try:
00118                         q = l[1]
00119                         if q.startswith('q='):
00120                             q = q.split('=', 2)[1]
00121                             quality = float(q)
00122                     except:
00123                         pass
00124 
00125                 if quality == []:
00126                     quality = float(length-i)
00127 
00128                 accepts.append((quality, l[0]))
00129                 i += 1
00130 
00131         # sort and reverse it
00132         accepts.sort()
00133         accepts.reverse()
00134 
00135         return [accept[1] for accept in accepts]
00136 
00137 
00138 class CookieAccept:
00139     filters = (str_lower, lang_normalize, str_strip)
00140 
00141     def __init__(self, request):
00142         pass
00143 
00144     def getAccepted(self, request, kind='language'):
00145         if not hasattr(request, 'cookies'):
00146             return ()
00147         language = request.cookies.get('pts_language', None)
00148         if language:
00149             if type(language) is types.TupleType:
00150                 return language
00151             else:
00152                 #filter
00153                 for filter in self.filters:
00154                     language = filter(language)
00155                 return (language,)
00156         else:
00157             return ()
00158 
00159 def setCookieLanguage(request, lang, REQUEST=None):
00160     """sets the language to a cookie
00161 
00162     request - the request object
00163     lang - language as string like de or pt_BR (it's normalizd)
00164     """
00165     if type(lang) is types.TupleType:
00166         lang = lang[1]
00167     lang = str_lower(lang_normalize(lang))
00168     request.RESPONSE.setCookie('pts_language', lang)
00169     if REQUEST:
00170         REQUEST.RESPONSE.redirect(REQUEST.URL0)
00171     else:
00172         return lang
00173 
00174 # BBB: This handler will be removed in PTS 1.5. It is not registered anymore
00175 # in 1.4 as it interferes with forms that include a field called language.
00176 class RequestGetAccept:
00177     filters = (str_lower, lang_normalize, str_strip)
00178 
00179     def __init__(self, request):
00180         log('DeprecationWarning: The RequestGetAccept handler is deprecated '
00181             'and will be removed in PTS 1.5.', logging.WARNING)
00182 
00183     def getAccepted(self, request, kind='language'):
00184         # get
00185         form = request.form
00186         language=form.get('language', None)
00187         setLanguage=form.get('setlanguage', None)
00188 
00189         if language:
00190             #filter
00191             for filter in self.filters:
00192                 language = filter(language)
00193             try:
00194                 if setLanguage == 1 or setLanguage.lower() in ('1','true', 'yes'):
00195                     setLanguage = True
00196                 else:
00197                     setLanguage = False
00198             except (ValueError, AttributeError), msg:
00199                 setLanguage = False
00200             if setLanguage:
00201                 setCookieLanguage(request, language)
00202             return (language,)
00203         else:
00204             return ()
00205 
00206 
00207 # higher number = higher priority
00208 # if a acceptor returns a false value (() or None) then the next acceptor
00209 # in the chain is queried
00210 registerLangPrefsMethod({'klass':BrowserAccept,   'priority':10 }, 'language')
00211 registerLangPrefsMethod({'klass':CookieAccept,   'priority':40 }, 'language')
00212 
00213 registerLangPrefsMethod({'klass':BrowserAccept,'priority':10 }, 'content-type')
00214 
00215 
00216 class Negotiator:
00217 
00218     tests = {
00219         'content-type': type_accepted,
00220         'language': lang_accepted,
00221     }
00222 
00223     def negotiate(self, choices, request, kind='content-type'):
00224         choices = tuple(choices)
00225         return self._negotiate(choices, request, kind)
00226 
00227     def _negotiate(self, choices, request, kind):
00228         userchoices = getLangPrefs(request, kind)
00229         # Prioritize on the user preferred choices. Return the first user
00230         # preferred choice that the object has available.
00231         test = self.tests.get(kind, _false)
00232         for choice in userchoices:
00233             if choice in choices:
00234                 return choice
00235             for l_avail in choices:
00236                 if test(l_avail, choice):
00237                     return l_avail
00238         return None
00239 
00240     # backwards compatibility... should be deprecated
00241     def getLanguage(self, langs, request):
00242         return self.negotiate(langs, request, 'language')
00243 
00244     def getLanguages(self, request):
00245         return getLangPrefs(request, 'language')
00246 
00247 
00248 negotiator = Negotiator()
00249 
00250 def negotiate(langs, request):
00251     return negotiator.negotiate(langs, request, 'language')