[Podcasts] check for episode history in Django ORM
[mygpo.git] / mygpo / decorators.py
blobf6bbdd1c27fb929c0c71c8e6a474e2fe085b2f36
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, get_object_or_404
27 from django.http import HttpResponseForbidden, HttpResponseNotAllowed
28 from django.contrib.auth import get_user_model
30 import logging
31 logger = logging.getLogger(__name__)
34 def requires_token(token_name, denied_template=None):
35 """
36 returns a decorator that checks if the security token in the 'token' GET
37 parameter matches the requires token for the resource. The protected
38 resource is indicated by
39 * the username parameter passed to the decorated function
40 * token_name passed to this method
42 The decorated method is returned, if
43 * no token is required for the resource
44 * the token in the 'token' GET parameter matches the required token
46 If the passed token does not match
47 * the denied_template is rendered and returned if given
48 * HttpResponseForbidden is returned, if denied_template is not given
49 """
50 def decorator(fn):
51 @wraps(fn)
52 def tmp(request, username, *args, **kwargs):
54 User = get_user_model()
55 user = get_object_or_404(User, username=username)
56 token = user.profile.get_token(token_name)
57 u_token = request.GET.get('token', '')
59 if token == '' or token == u_token:
60 return fn(request, username, *args, **kwargs)
62 else:
63 if denied_template:
64 return render(request, denied_template, {
65 'other_user': user
68 else:
69 return HttpResponseForbidden()
71 return tmp
72 return decorator
75 def allowed_methods(methods):
76 def decorator(fn):
77 @wraps(fn)
78 def tmp(request, *args, **kwargs):
79 if request.method in methods:
80 return fn(request, *args, **kwargs)
81 else:
82 return HttpResponseNotAllowed(methods)
84 return tmp
86 return decorator
89 class repeat_on_conflict(object):
90 """ Repeats an update operation in case of a ResourceConflict
92 In case of a CouchDB ResourceConflict, reloads the parameter with the given
93 name and repeats the function call until it succeeds. When calling the
94 function, the parameter that should be reloaded must be given as a
95 keyword-argument """
97 ARGSPEC = '__repeat_argspec__'
99 def __init__(self, obj_names=[], reload_f=None):
100 self.obj_names = obj_names
101 self.reload_f = reload_f or self.default_reload
103 def default_reload(self, obj):
104 # if the object knows its DB, use this one
105 if obj._db:
106 doc = obj._db.get(obj._id)
107 return obj.__class__.wrap(doc)
108 # otherwise the class' default DB is used
109 return obj.__class__.get(obj._id)
111 def build_locals(self, f, args, kwargs):
112 argspec = getattr(f, self.ARGSPEC)
113 if len(args) > len(argspec.args):
114 varargs = args[len(args):]
115 args = args[:len(args)]
116 else:
117 varargs = []
118 locals = dict(zip(argspec.args, args))
119 if argspec.varargs is not None:
120 locals.update({argspec.varargs: varargs})
121 if argspec.keywords is not None:
122 locals.update({argspec.keywords: kwargs})
123 locals.update(kwargs)
124 return locals
126 def __call__(self, f):
128 if not hasattr(f, self.ARGSPEC):
129 argspec = inspect.getargspec(f)
130 setattr(f, self.ARGSPEC, argspec)
132 @wraps(f)
133 def wrapper(*args, **kwargs):
134 all_args = before = self.build_locals(f, args, kwargs)
136 # repeat until operation succeeds
137 # TODO: adding an upper bound might make sense
138 while True:
139 try:
140 return f(**all_args)
142 except ResourceConflict as e:
143 logger.info('retrying %s, reloading %s', f, self.obj_names)
144 for obj_name in self.obj_names:
145 obj = all_args[obj_name]
146 all_args[obj_name] = self.reload_f(obj)
148 return wrapper
151 def query_if_required():
152 """ If required, queries some resource before calling the function
154 The decorated method is expected to be bound and its class is
155 expected to have define the methods _needs_query() and _query().
158 def decorator(f):
159 @wraps(f)
160 def wrapper(self, *args, **kwargs):
162 if self._needs_query():
163 self._query()
165 return f(self, *args, **kwargs)
167 return wrapper
168 return decorator
171 def cors_origin(allowed_origin='*'):
172 """ Adds an Access-Control-Allow-Origin header to the response """
174 def decorator(f):
175 @wraps(f)
176 def wrapper(*args, **kwargs):
177 resp = f(*args, **kwargs)
178 resp['Access-Control-Allow-Origin'] = allowed_origin
179 return resp
181 return wrapper
182 return decorator