S&Co CRM
A CRM designed and built from scratch for S&Co Formation et Conseils, a training and consulting company in France. It had to be simple for the team, quick to work in, and connected to the tools they already use.
- Role
- Django Developer
- Company
- S&Co Formation et Conseils
- Period
- Oct 2023 – present
- Stack
- Django · DRF · JavaScript · Signals
One place for the whole workflow.
Teams lose time when their work is scattered across social platforms, phone systems and spreadsheets. The brief was a user-centred CRM: smooth, intuitive and built for simplicity and efficiency, with the outside tools feeding into it instead of competing with it.
I designed and developed it end to end in Django: data model, business logic, a REST API for integrations, authentication, and a responsive front end the team uses every day.
How the CRM fits together
A simplified view. The team works through the top row; outside tools connect through the bottom row.
What I built
Conceptualised, designed and developed a user-centred CRM: smooth, intuitive, and optimised for simplicity and efficiency.
Used Django and its MTV architecture to build a robust, scalable, high-performance back end that stays easy to maintain.
Used the Django ORM for efficient database work, from complex queries to everyday data handling.
Set up Django REST Framework to expose a secure RESTful API, so third-party tools and services can integrate cleanly.
Integrated Django's authentication system with role-based access control and multi-factor authentication options.
Built a responsive front end with JavaScript, HTML and CSS that works across devices.
Connected Facebook, Zapier, Ringover and Google APIs to centralise and simplify the team's workflows.
Used caching, query optimisation and asynchronous event handling with Django Signals.
Tested rigorously and validated each business workflow, so the CRM went to production stable.
Role checks in one place
Role-based access works best when every view asks the same question the same way. This is a simplified example of the approach, not the client's code.
from rest_framework.permissions import BasePermission
class HasRole(BasePermission):
"""Allow the request only if the user's role is listed on the view."""
def has_permission(self, request, view):
user = request.user
allowed = getattr(view, "allowed_roles", ())
return bool(user and user.is_authenticated and user.role in allowed)
class ContactViewSet(viewsets.ModelViewSet):
permission_classes = [HasRole]
allowed_roles = ("sales", "manager")
serializer_class = ContactSerializer
def get_queryset(self):
# one JOIN instead of one query per contact
return Contact.objects.select_related("owner")