Kinda unlikely anyone here is going to care, but I have this piece of code I've been copy pasting between Django projects for years because it addresses a really common situation in a way I think is quite neat relative to the approach you typically see used in the docs and such. Might be worth some SEO points or something. Also possibly useful to the couple of you who poke through the ISS code now and then because it's used pervasively there.
The situation is you have some view (the thing that handles an incoming HTTP request) and you want to do something different when a client does a GET request vs. a POST request vs. something else. This is done constantly with form handling, GET should present a form POST should validate the form and either represent with error messaging or take some action if the form is valid. The usual approach is to look at the request method in your view and branch according to what you want to do. It typically looks like this (taken from django docs):
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()
return render(request, 'name.html', {'form': form})
If these alternative paths get long however this quickly becomes unreadable. Half the function is doing one thing and half is doing something else. Gets worse the more methods you're trying to support. So enter MethodSplitView:
class MethodSplitView(object):
"""
A flexible class for splitting handling of different HTTP methods being
dispatched to the same view into separate class methods. Subclasses may
define a separate class method for each HTTP method the view handles (e.g.
GET(self, request, ...), POST(self, request, ...) which will be called with
the usual view signature when that sort of request is made.
Subclasses may also define a `pre_method_check` method which, if it returns
a HttpResponse, will be used to response to the request instead of
delegating to the corresponding method.
"""
def __call__(self, request, *args, **kwargs):
if getattr(self, 'active_required', False):
if not request.user.is_active:
return HttpResponseForbidden('You must be an active user '
'to do this')
if getattr(self, 'staff_required', False):
if not request.user.is_staff:
return HttpResponseForbidden('You must be staff to do this.')
meth = getattr(self, request.method, None)
if not meth:
return HttpResponseBadRequest('Request method %s not supported'
% request.method)
response_maybe = self.pre_method_check(request, *args, **kwargs)
if isinstance(response_maybe, HttpResponse):
return response_maybe
return meth(request, *args, **kwargs)
def pre_method_check(request, *args, **kwargs):
return None
@classmethod
def as_view(cls):
if getattr(cls, 'require_login', False):
return login_required(cls())
else:
return cls()
https://gist.github.com/RyanJenkins/258723c74485fb01ee85272075bad967Essentially this the behavior here is to look at the request method and pick a method from the subclass with the appropriate name and delegate request handling to that. So submission logic lives in one method, inquiry logic in another. On the whole it's a lot easier to visually parse quickly. One downside is that sharing logic between multiple methods is harder here, in my experience the vast majority of shared logic between methods is in the form of some kind of auth check (is the user logged in, do the have the appropriate privileges, etc) so I added a `pre_method_check` method that subclasses can specify to do this check and optionally return a response (preventing delegation to other methods). There are more opportunities for reducing boilerplate but generally at the cost of flexibility, empirically this seems to be the sweet spot and any other logic sharing can be delegated to private methods that request handling method call.
For a concrete example, here's the user registration method using this "pattern" (kinda hate that word):
class RegisterUser(utils.MethodSplitView):
def pre_method_check(self, request, *args, **kwargs):
if not utils.get_config('enable_registration'):
return render(request, 'generic_message.html', {
'page_title': 'Registration Closed',
'heading': 'Registration Closed',
'message': ('Registration is temporarily closed. Check back '
'later to register a new account. If you think '
'this is in error, please contact the '
'administrator.')
})
def GET(self, request):
form = forms.RegistrationForm()
ctx = {'form': form}
return render(request, 'register.html', ctx)
def POST(self, request):
form = forms.RegistrationForm(request.POST)
if form.is_valid():
poster = form.save()
poster = authenticate(username = form.cleaned_data['username'],
password = form.cleaned_data['password1'])
login(request, poster)
return HttpResponseRedirect('/')
else:
ctx = { 'form': form }
return render(request, 'register.html', ctx)