Model-View-Controller, or “MVC”, is a well-established paradigm for structuring complex projects. In essence:
As we all write code in Python, and the standard Python way of building useful web-apps is Django, let’s look at this from a Django perspective.
The idea is that the Model class is instantiated once for each piece of data. Django is very good at mapping data between tables in a database and object-oriented classes in your code. This is about half of Django’s job, and is sometimes called an Object Relation Mapping, or ORM. (Other Python ORM systems exist besides Django, such as SQLAlchemy.)
Similarly, the View class can present your data to the user. This is the other half of Django’s job, and encompasses the entire HTTP request/response framework. Django even includes some sophisticated form processing to help receive changes to data and communicate them back to the model.
But high-level business logic, logic that understands your data, that’s up to you to write, and to squeeze into the system anywhere you can fit it. Often, we end up writing this stuff as methods on either the Model classes or the View classes, though it doesn’t really fit either.
Django really shows its limits when you have polymorphic data – where a single Model class handles data of related but not identical types.
For example, imagine a
Document class that is used to handle various types of documents. Very likely, your
Document model has a
type field that specifies one of various values, like “HTML Document” vs. “Markdown Document” vs. “Plain Text Document”. Depending on any specific Document’s type, the methods you write on your model need to branch into very different code paths.
DOCUMENT_TYPES = [ ('HTML', 'HTML'), ('MD', 'Markdown'), ('TXT', 'Plain Text'), ] class Document(models.Model): type = models.CharField(max_length=4, choices=DOCUMENT_TYPES) # ...other fields... # ...and methods to manipulate this Document...
This is classic polymorphism: all your Documents are stored in the same table, that’s nice, but most every method you might write is going to need a bunch of if-then cases to deal with the different types of documents.
With our controllers, your model attaches to a namespace of polymorphic options. Then, with one syntactic step, you can ask any
Document for it’s controller, and get back a different class depending on which type of
Document it is.
Since we like our Enum class, we’re going to use it for our types of documents. As such, our example becomes this:
base.Enum.Define(('DOCUMENT_TYPE', 'DocumentTypes'), ( ('Undefined', None), ('HTML', 'HTML'), ('Markdown', 'MD'), ('Plain Text', 'TXT'), )) class Document(base.ControllerMixin, models.Model): CONTROLLER_NAMESPACE = DocumentTypes CONTROLLER_NAME_PROPERTY = 'type' type = models.CharField(DocumentTypes.MaxTagLength(), choices=DocumentTypes.Choices()) # ...other fields...
We also need to define some
class DocumentController(base.Controller): CONTROLLER_NAMESPACE = DocumentTypes # ... methods in common for all documents ... class HTMLDocumentController(DocumentController): CONTROLLER_NAME = DOCUMENT_TYPE_HTML # ... methods for HTML documents ... class MarkdownDocumentController(DocumentController): CONTROLLER_NAME = DOCUMENT_TYPE_MARKDOWN # ... methods for Markdown documents ... class TextDocumentController(DocumentController): CONTROLLER_NAME = DOCUMENT_TYPE_PLAIN_TEXT # ... methods for plain text documents ...
Now whenever you have a
Document instance, that instance has an extra property called
controller. When you request that
controller you get an instance of the specific
Controller class that handles that
doc1 = Document() doc1.type = 'HTML' print(doc1.controller) doc2 = Document() doc2.type = 'MD' print(doc2.controller) doc3 = Document() doc3.type = 'TXT' print(doc3.controller)
This will print something similar to:
<__main__.HTMLDocumentController object at 0x10425db38> <__main__.MarkdownDocumentController object at 0x104239f98> <__main__.TextDocumentController object at 0x104275128>
.controller property is cached on each instance after it is first created, hence why this example uses three different instances.
That’s it in a nutshell: Instead of trying to implement all your polymorphic logic in one class, you get to split it out to separate classes and our
Controller picks the right one to use based on the model’s
Every instance of a
Controller can refer to the model instance it’s attached to as
Additionally, it can also use the slugified name of the class. So in our example above, each controller instance has a
.document attribute which is exactly the same as its
If you want to change this, the controller may override
class DocumentController(base.Controller): CONTROLLER_NAMESPACE = DocumentTypes CONTROLLER_ITEM_NAME = `foobar`
These controllers would have
.foobar properties that both refer to the model instance.
CONTROLLER_NAMESPACE does not have to be an
Enum. Anything that supports “
in” testing will work, which means you can use even a simple list if you’d like:
MY_DOCUMENT_TYPES = ['HTML', 'MD', 'TXT'] class DocumentController(base.Controller): CONTROLLER_NAMESPACE = MY_DOCUMENT_TYPES
Additionally, we have a trivial base class type called
ControllerNamespace that you can derive a class from, and we’ll take sub-classes of that class as options in the namespace.
If a specific controller can’t be found for an instance of your model, the top-level controller for your namespace will be used.
For instance, if the
MarkdownDocumentController did not exist and you had a markdown
Document, the top-level
DocumentController would be instantiated instead.
Similarly, if the
Document specifies a
.type that isn’t in the namespace at all, we will log a message to console about the problem, but will again use the top-level
It is absolutely possible to request a
.controller for a model class instead of a class instance. The same logic is followed for finding the right controller to instantiate. The difference is the controller instance’s
.item property is given a totally new instance of the model class, rather than a populated instance.
One key takeaway is that the class hierarchy of your controllers is totally separate from the class hierarchy of your models.
In fact, unless you’re using
ControllerNamespace, the actual class hierarchies are irrelevant to how controllers function. It is fully possible for multiple different model classes to use the same controller namespace and thus the same controllers.
We could not log you in, reset your password, sign you up, please try again.