Skip to content

Instantly share code, notes, and snippets.

@linuxct
Last active July 30, 2022 02:56
Show Gist options
  • Save linuxct/12af906e117ff85a49d1c925534449e8 to your computer and use it in GitHub Desktop.
Save linuxct/12af906e117ff85a49d1c925534449e8 to your computer and use it in GitHub Desktop.
First part of my attempt at implementing MVC logic in Python with Django and Wagtail

Introduction

While tinkering with Wagtail (and inherently, Django), I found myself in no time with a very large views.py file due to how Django sets up its endpoint management. For this reason, I decided I would attempt at segregating the responsibility of views.py into separate files/classes, in order to make it easier to maintain and read.

The idea is close to an MVC pattern. Ideally there will be a Controller that will implement the Action (endpoint) logic, which will parse the request, its data, and will implement the necessary logic for the data lookup part. The result of this will be converted into a ViewModel (Data class) which will be passed to a View (Django template), ideally binding them together.

Implementation details

I have decided to implement a ControllerBase (see below), that will be inherited by the rest of the Controllers in the solution. All redundant logic, as well as the necessary methods needed in order to support Django's decorators, will be implemented and performed at ControllerBase.

By default, the onRequest method will be called by the URL binding in urls.py (see 2 files below). ControllerBase also exposes 2 abstract methods that need to be implemented by the controllers inheriting it: onGet and onPost. These functions will be called respectively depending on the HTTP verb used in the request to the endpoint.

SampleController implements ControllerBase through its Actions: SampleAction and TestAction. Both actions are exposed in /test/{int} and /sample/{int} (see urls.py below). Each individual Action is a class that will implement the desired HTTP verb methods through the exposed abstract methods, such as onGet and onPost.

In the future, details will be added regarding how to implement the ViewModel generation and the binding with the Django View template.

from abc import ABC, abstractclassmethod
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
from django.core.handlers.wsgi import WSGIRequest
from models import User
class ControllerBase(ABC):
user: User
currentRequest: WSGIRequest
def __init__(self):
pass
def onRequest(self, request: WSGIRequest, **kwargs):
self.user = request.user
self.currentRequest = request
if request.method == 'GET':
return self.onGet(request, **kwargs)
if request.method == 'POST':
return self.onPost(request, **kwargs)
reason = f'Unsupported HTTP verb {request.method} was requested'
return HttpResponseBadRequest(JsonResponse({'result': 'Error', 'reason': reason}))
def build_absolute_uri(self, *args):
return self.currentRequest.build_absolute_uri(*args)
def get_full_path(self, *args):
return self.currentRequest.get_full_path(*args)
"""The following functions are method definitions,
and will not be called by the runtime. Please implement
them in your controller by inheriting ControllerBase"""
@abstractclassmethod
def onGet(self, request, **kwargs):
pass
@abstractclassmethod
def onPost(self, request, **kwargs):
pass
from controllers.ControllerBase import ControllerBase
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
from django.core.handlers.wsgi import WSGIRequest
from django.contrib.auth.decorators import login_required
class SampleAction(ControllerBase):
@login_required
def onGet(self, request: WSGIRequest, **kwargs):
return JsonResponse({'result': f'GET Sample action OK: {kwargs.keys()}::{kwargs.values()}'})
def onPost(self, request: WSGIRequest, **kwargs):
return JsonResponse({'result': f'POST Sample action OK: {kwargs.keys()}::{kwargs.values()}'})
class TestAction(ControllerBase):
@login_required
def onGet(self, request: WSGIRequest, **kwargs):
return JsonResponse({'result': f'GET test action OK: {kwargs.keys()}::{kwargs.values()}'})
def onPost(self, request: WSGIRequest, **kwargs):
return JsonResponse({'result': f'POST test action OK: {kwargs.keys()}::{kwargs.values()}'})
from django.urls import path
from controllers.SampleController import SampleAction, TestAction
from . import views
urlpatterns = [
path('', views.home_view, name='home'),
path('sample/<int:sample_id>/', SampleAction().onRequest, name='sample'),
path('test/<int:test_id>/', TestAction().onRequest, name='test'),
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment