Application example
The same example is used in the first article, feel free to skip this part.
To sum it up, let’s imaging we’re building a publication platform with 2 levels of approval before publication for articles. The following diagram shows the article statuses and transitions:
Frontend VS Backend responsibilities
One common reason to develop an API in Django is to use it in conjunction with a frontend framework such as EmberJS. In our example, we’ll show a button when appropriate on the article page. A button would be “Approve” for example.
Solution 1 - to be avoided
The intuitive way to do this is to write the code in the template: if the article is a draft, let’s show an “Approve” button. That seems easy enough.
Now take a minute to look at the above diagram and keep in mind that everyone doesn’t have access to all transitions and that there could be several possible transitions; having several transitions means several buttons…
For a draft article, the conditions would be: if you’re logged-in as an admin and you’re looking at a draft article you should see an “Approve” button (as long as you have sufficient access to give that first approval). And if you have sufficient permission, you should also see a “Publish” button (see the transition at the very top of the above diagram).
Coding this in the templates present two major issues:
- a lot of if / else statement => code smell
- duplication with the backend => big-ass code odour
This is a really bad choice.
Solution 2
Ideally, you don’t want the frontend to know all the details regarding the transitions. In other words, the frontend shouldn’t include any business logic. What you want is true separation of concern:
-
The frontend is responsible for:
- presenting the transitions in the User Interface
- handling the user interactions
-
The backend must therefore (via the API):
-
provide the available transitions
-
handle permissions & security issues
-
obviously handle the database
In the API, we would add the list of available transitions in the article details call and add a call to update the status (using one of these transitions).
Let’s code it:
For the API, I’m using the famous Django Rest Framework plugin and as you can see, the code is truly straightforward. First let’s expose the available transitions for the currently logged-in user:
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
fields = (
…
'transitions',
)
def get_transitions(self, article):
return dict([
(transition_name, ...)
for transition_name in article.get_available_transitions(user=self.context['request'].user)
])
class ArticleViewSet(…):
…
serializer = ArticleSerializer
For a draft article, an admin would get 2 transitions from the API: " approve_1 " and " publish “. All we now need is an API endpoint to use these transitions. Let’s add: PUT /api-path/articles/xx/update_status :
class ArticleViewSet(…):
@detail_route(methods=['put'], permission_classes=[IsAuthenticated])
def update_status(self, request, pk):
article = get_object_or_404(Article, id=pk)
transition = request.data['transition'] # e.g. "publish"
if transition not in task.get_available_transitions(self.user):
return response.Response(
data={
'message': "Invalid transition"
},
status=status.HTTP_403_FORBIDDEN
)
getattr(article, transition)()
article.save()
return response.Response({'message': "Status updated"}, status.HTTP_200_OK)
Conclusion
Et voilà ! You would obviously do more than this… These code samples are enough to get things working nicely (please comment if you know a good alternative). In a real application, I’d check for example that the user has access to the article at all and I’d provide more information via get_transitions (label, confirmation message, etc.).