refactor podcastlist updates
[mygpo.git] / mygpo / decorators.py
blob3ff1de79421bd35a80456e623ee910e7ea8899f9
1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2009 Thomas Perl and the gPodder Team
6 # gPodder is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # gPodder is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from functools import wraps
21 import inspect
23 from couchdbkit import ResourceConflict
25 from django.http import Http404
26 from django.shortcuts import render
27 from django.http import HttpResponseForbidden, HttpResponseNotAllowed
30 def requires_token(token_name, denied_template=None):
31 """
32 returns a decorator that checks if the security token in the 'token' GET
33 parameter matches the requires token for the resource. The protected
34 resource is indicated by
35 * the username parameter passed to the decorated function
36 * token_name passed to this method
38 The decorated method is returned, if
39 * no token is required for the resource
40 * the token in the 'token' GET parameter matches the required token
42 If the passed token does not match
43 * the denied_template is rendered and returned if given
44 * HttpResponseForbidden is returned, if denied_template is not given
45 """
46 def decorator(fn):
47 @wraps(fn)
48 def tmp(request, username, *args, **kwargs):
50 from mygpo.users.models import User
51 user = User.get_user(username)
52 if not user:
53 raise Http404
55 token = user.get_token(token_name)
56 u_token = request.GET.get('token', '')
58 if token == '' or token == u_token:
59 return fn(request, username, *args, **kwargs)
61 else:
62 if denied_template:
63 return render(request, denied_template, {
64 'other_user': user
67 else:
68 return HttpResponseForbidden()
70 return tmp
71 return decorator
74 def allowed_methods(methods):
75 def decorator(fn):
76 @wraps(fn)
77 def tmp(request, *args, **kwargs):
78 if request.method in methods:
79 return fn(request, *args, **kwargs)
80 else:
81 return HttpResponseNotAllowed(methods)
83 return tmp
85 return decorator
88 class repeat_on_conflict(object):
89 """ Repeats an update operation in case of a ResourceConflict
91 In case of a CouchDB ResourceConflict, reloads the parameter with the given
92 name and repeats the function call until it succeeds. When calling the
93 function, the parameter that should be reloaded must be given as a
94 keyword-argument """
96 ARGSPEC = '__repeat_argspec__'
98 def __init__(self, obj_names=[], reload_f=None):
99 self.obj_names = obj_names
100 self.reload_f = reload_f or self.default_reload
102 def default_reload(self, obj):
103 # if the object knows its DB, use this one
104 if obj._db:
105 doc = obj._db.get(obj._id)
106 return obj.__class__.wrap(doc)
107 # otherwise the class' default DB is used
108 return obj.__class__.get(obj._id)
110 def build_locals(self, f, args, kwargs):
111 argspec = getattr(f, self.ARGSPEC)
112 if len(args) > len(argspec.args):
113 varargs = args[len(args):]
114 args = args[:len(args)]
115 else:
116 varargs = []
117 locals = dict(zip(argspec.args, args))
118 if argspec.varargs is not None:
119 locals.update({argspec.varargs: varargs})
120 if argspec.keywords is not None:
121 locals.update({argspec.keywords: kwargs})
122 locals.update(kwargs)
123 return locals
125 def __call__(self, f):
127 if not hasattr(f, self.ARGSPEC):
128 argspec = inspect.getargspec(f)
129 setattr(f, self.ARGSPEC, argspec)
131 @wraps(f)
132 def wrapper(*args, **kwargs):
133 all_args = before = self.build_locals(f, args, kwargs)
135 # repeat until operation succeeds
136 # TODO: adding an upper bound might make sense
137 while True:
138 try:
139 return f(**all_args)
141 except ResourceConflict:
142 for obj_name in self.obj_names:
143 obj = all_args[obj_name]
144 all_args[obj_name] = self.reload_f(obj)
146 return wrapper
149 def query_if_required():
150 """ If required, queries some resource before calling the function
152 The decorated method is expected to be bound and its class is
153 expected to have define the methods _needs_query() and _query().
156 def decorator(f):
157 @wraps(f)
158 def wrapper(self, *args, **kwargs):
160 if self._needs_query():
161 self._query()
163 return f(self, *args, **kwargs)
165 return wrapper
166 return decorator
169 def cors_origin(allowed_origin='*'):
170 """ Adds an Access-Control-Allow-Origin header to the response """
172 def decorator(f):
173 @wraps(f)
174 def wrapper(*args, **kwargs):
175 resp = f(*args, **kwargs)
176 resp['Access-Control-Allow-Origin'] = allowed_origin
177 return resp
179 return wrapper
180 return decorator