'''
Created on 26 Mar 2016
@author: alex
'''
from django.shortcuts import render
from django.http.response import HttpResponse
from django.shortcuts import HttpResponseRedirect
from .exceptions import RedirectException
try:
from useful_inkleby.decorators import GenericDecorator
except:
from ..decorators import GenericDecorator
[docs]def handle_redirect(func):
def inner(*args, **kwargs):
try:
return func(*args, **kwargs)
except RedirectException as new_url:
return HttpResponseRedirect(new_url)
return inner
[docs]class FunctionalView(object):
"""
Very simple class-based view that simple expects the class to have a
'template' variable and a 'view' function that expects (self, request).
Idea is to preserve cleanness of functional view logic but tidy up the
most common operation.
"""
template = ""
require_staff = False
require_login = False
login_test = staticmethod(lambda u: u.is_authenticated())
staff_test = staticmethod(lambda u: u.is_staff)
view_decorators = []
[docs] @classmethod
def as_view(cls, decorators=True, no_auth=False):
"""
if decorators is True - we apply any view_decorators listed for the class
if no_auth = True, we bypass staff and user testing(useful for baking)
"""
def render_func(request, *args, **kwargs):
view = cls()
if no_auth == False:
if cls.require_login and cls.login_test(request.user) == False:
return view.access_denied_no_auth(request)
if cls.require_staff and cls.staff_test(request.user) == False:
return view.access_denied_no_staff(request)
context = view._get_view_context(request, *args, **kwargs)
if isinstance(context, dict):
context = view.extra_params(context)
return view.context_to_html(request, context)
else:
# if we're returning a redirect view
return context
func = render_func
func = handle_redirect(func) # allow RedirectException
if decorators:
for v in cls.view_decorators:
func = v(func)
return func
[docs] def access_denied_no_auth(self, request):
"""
override to provide a better response if someone needs to login
"""
return HttpResponse("No Access")
[docs] def access_denied_no_staff(self, request):
"""
override to provide a better response if someone needs to be staff
"""
return HttpResponse("No Access")
def _get_view_context(self, request, *args, **kwargs):
context = self.view(request, *args, **kwargs)
if isinstance(context, dict):
context = self.extra_params(context)
return context
[docs] def context_to_html(self, request, context):
html = render(request,
self.__class__.template,
context=context
)
return html
[docs] def view(self, request):
return {}
[docs]class LogicalView(FunctionalView):
"""
Runs with class-based logic while trying to keep the guts exposed.
request becomes self.request
expects a 'logic' rather than a view function (no args).
giving the class an 'args' list of strings tells it what to convert
view arguments into.
e.g. args = ['id_no'] - will create self.id_no from the view argument.
if an arg is a tuple ('id_no','5') - will set a default value.
Use prelogic and postlogic decorators to run functions before or afer logic.
These accept an optional order paramter.
Lower order have priority.
Default order is 5.
"""
args = []
def __init__(self):
self.record_new = False
self.values = []
def __setattr__(self, key, value):
if hasattr(self, "record_new") and self.record_new:
self.values.append(key)
super(FunctionalView, self).__setattr__(key, value)
[docs] def view(self, request, *args, **kwargs):
"""
override with something that returns a dictionary
to use plain functional view logic
"""
self.request = request
self.record_new = True # enable new value recording
arg_mappings = []
"""
assign any default values
"""
for a in self.__class__.args:
if isinstance(a, tuple):
setattr(self, a[0], a[1])
arg_mappings.append(a[0])
else:
arg_mappings.append(a)
"""
assign other results
"""
for x, a in enumerate(args):
try:
arg_name = arg_mappings[x]
except IndexError:
raise ValueError("Mapping not assigned for {0}".format(a))
setattr(self, arg_name, a)
for k, v in kwargs.iteritems():
setattr(self, k, v)
prelogic = self._prelogic()
if prelogic:
return prelogic
logic = self.logic()
if logic:
return logic
postlogic = self._postlogic()
if postlogic:
return postlogic
return {k: getattr(self, k) for k in self.values}
def _logic_processing(self, prefix):
"""
run all pre and post logic functions in order.
"""
def keying(v):
if hasattr(v, "order"):
return v.order
else:
return 5
def ga(k):
return getattr(self, k)
"""
has a function had either a decorator or a name prefix assigned to it
"""
def passes_func_test(k): return hasattr(
k, "_prefix") and k._prefix == prefix
def passes_test(k): return prefix + "_" in k or passes_func_test(ga(k))
funcs = [k for k in dir(self) if passes_test(k)]
funcs = [ga(k) for k in funcs]
funcs.sort(key=lambda x: keying(x))
for f in funcs:
r = f()
if r: # if any value is returned, escalate
return r
def _prelogic(self):
return self._logic_processing("prelogic")
def _postlogic(self):
return self._logic_processing("postlogic")
[docs] def logic(self):
"""
any new values assigned to self will be passed to the template
self.value becomes value
"""
pass
class prelogic(GenericDecorator):
"""
decorates a function to run before the logic view
accepts an order kwarg to manage competing functions
"""
prefix = "prelogic"
default_kwargs = {"order": 5}
def post_creation(self, obj):
obj._prefix = self.__class__.prefix
obj.order = self.order
return obj
class postlogic(GenericDecorator):
"""
decorates a function to run after the logic view
accepts an order kwarg to manage competing functions
"""
prefix = "postlogic"
default_kwargs = {"order": 5}
def post_creation(self, obj):
obj._prefix = self.__class__.prefix
obj.order = self.order
return obj