Developing a Customer Relationship Management System with Django

To begin, create a new Django project. Configure the database connection to use MySQL by modifying the settings.py file. Ensure the database PerfectCRM is created in your MySQL instance before running migrations.


# settings.py
DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.mysql',
       'NAME': 'PerfectCRM',
       'USER': 'root',
       'PASSWORD': 'mysql',
       'HOST': '127.0.0.1',
       'PORT': '3306',
   }
}
   

Next, configure the static file directories to serve CSS and JavaScript assets.


# settings.py
STATICFILES_DIRS = [
   os.path.join(BASE_DIR, "static"),
]
   

Create the core application for the CRM logic and register it in the installed apps list.


python manage.py startapp crm
   

Data Model Design

The system requires a robust data schema to handle clients, courses, enrollments, and user roles. Define the following models in crm/models.py.

Client and Tag Models

The Client model stores prospective customer details, while Tag allows for flexible caetgorization.


from django.db import models
from django.contrib.auth.models import User

class Tag(models.Model):
   name = models.CharField(max_length=32, unique=True)

   def __str__(self):
       return self.name

   class Meta:
       verbose_name = "Tag"
       verbose_name_plural = "Tags"

class Client(models.Model):
   name = models.CharField(max_length=32, blank=True, null=True)
   contact_qq = models.CharField(max_length=64, unique=True, verbose_name="QQ Number")
   qq_name = models.CharField(max_length=64, blank=True, null=True)
   phone = models.CharField(max_length=64, blank=True, null=True)
   
   SOURCE_OPTIONS = [
       (0, 'Referral'),
       (1, 'QQ Group'),
       (2, 'Official Website'),
       (3, 'Baidu Promotion'),
       (4, '51CTO'),
       (5, 'Zhihu'),
       (6, 'Marketing'),
   ]
   source = models.SmallIntegerField(choices=SOURCE_OPTIONS)
   referral_from = models.CharField("Referrer QQ", max_length=64, blank=True, null=True)
   
   consulted_course = models.ForeignKey("Course", on_delete=models.SET_NULL, null=True, verbose_name="Consulted Course")
   content = models.TextField("Consultation Details")
   tags = models.ManyToManyField("Tag", blank=True)
   
   STATUS_OPTIONS = [(0, 'Enrolled'), (1, 'Not Enrolled')]
   status = models.SmallIntegerField(choices=STATUS_OPTIONS, default=1)
   consultant = models.ForeignKey("UserProfile", on_delete=models.SET_NULL, null=True)
   memo = models.TextField(blank=True, null=True)
   created_at = models.DateTimeField(auto_now_add=True)

   def __str__(self):
       return self.contact_qq

   class Meta:
       verbose_name = "Client"
       verbose_name_plural = "Clients"
   

Follow-up Records

Track the history of interactions with clients.


class FollowUpRecord(models.Model):
   client = models.ForeignKey("Client", on_delete=models.CASCADE)
   content = models.TextField("Follow-up Content")
   consultant = models.ForeignKey("UserProfile", on_delete=models.CASCADE)
   
   INTENTION_LEVELS = [
       (0, 'Enroll in 2 weeks'),
       (1, 'Enroll in 1 month'),
       (2, 'No plan'),
       (3, 'Enrolled elsewhere'),
       (4, 'Enrolled'),
       (5, 'Blacklisted'),
   ]
   intention = models.SmallIntegerField(choices=INTENTION_LEVELS)
   date = models.DateTimeField(auto_now_add=True)

   def __str__(self):
       return f"{self.client.contact_qq} - {self.get_intention_display()}"

   class Meta:
       verbose_name = "Follow-up Record"
       verbose_name_plural = "Follow-up Records"
   

Course and Branch Management

Define the curriculum and physical locations.


class Course(models.Model):
   title = models.CharField(max_length=64, unique=True)
   price = models.PositiveSmallIntegerField()
   duration_months = models.PositiveSmallIntegerField("Duration (Months)")
   outline = models.TextField()

   def __str__(self):
       return self.title

class Branch(models.Model):
   name = models.CharField(max_length=128, unique=True)
   address = models.CharField(max_length=128)

   def __str__(self):
       return self.name
   

Class Batch and Records

Manage class schedules and daily teaching records.


class ClassBatch(models.Model):
   branch = models.ForeignKey("Branch", on_delete=models.CASCADE)
   course = models.ForeignKey("Course", on_delete=models.CASCADE)
   CLASS_TYPES = [(0, 'Full-time'), (1, 'Weekend'), (2, 'Online')]
   class_type = models.SmallIntegerField(choices=CLASS_TYPES)
   semester = models.PositiveSmallIntegerField()
   teachers = models.ManyToManyField("UserProfile")
   start_date = models.DateField()
   end_date = models.DateField(blank=True, null=True)

   def __str__(self):
       return f"{self.branch} - {self.course} (Sem {self.semester})"

   class Meta:
       unique_together = ('branch', 'course', 'semester')

class TeachingRecord(models.Model):
   class_batch = models.ForeignKey("ClassBatch", on_delete=models.CASCADE)
   day_num = models.PositiveSmallIntegerField("Day Number")
   teacher = models.ForeignKey("UserProfile", on_delete=models.CASCADE)
   has_homework = models.BooleanField(default=True)
   homework_title = models.CharField(max_length=128, blank=True, null=True)
   homework_content = models.TextField(blank=True, null=True)
   outline = models.TextField("Lesson Outline")
   date = models.DateField(auto_now_add=True)

   def __str__(self):
       return f"{self.class_batch} - Day {self.day_num}"

   class Meta:
       unique_together = ('class_batch', 'day_num')
   

Enrollment and Payments

Handle student sign-ups and financial transactions.


class Enrollment(models.Model):
   client = models.ForeignKey("Client", on_delete=models.CASCADE)
   enrolled_class = models.ForeignKey("ClassBatch", on_delete=models.CASCADE)
   consultant = models.ForeignKey("UserProfile", on_delete=models.CASCADE)
   contract_agreed = models.BooleanField(default=False)
   contract_approved = models.BooleanField(default=False)
   date = models.DateTimeField(auto_now_add=True)

   def __str__(self):
       return f"{self.client} -> {self.enrolled_class}"

   class Meta:
       unique_together = ('client', 'enrolled_class')

class PaymentRecord(models.Model):
   client = models.ForeignKey("Client", on_delete=models.CASCADE)
   course = models.ForeignKey("Course", on_delete=models.CASCADE)
   amount = models.PositiveIntegerField(default=500)
   consultant = models.ForeignKey("UserProfile", on_delete=models.CASCADE)
   date = models.DateTimeField(auto_now_add=True)

   def __str__(self):
       return f"{self.client} paid {self.amount}"
   

User Roles and Permissions

Implement a role-based access control system linking users to menus.


class Menu(models.Model):
   name = models.CharField(max_length=32)
   url_name = models.CharField(max_length=64)

   def __str__(self):
       return self.name

class Role(models.Model):
   name = models.CharField(max_length=32, unique=True)
   menus = models.ManyToManyField("Menu", blank=True)

   def __str__(self):
       return self.name

class UserProfile(models.Model):
   user = models.OneToOneField(User, on_delete=models.CASCADE)
   name = models.CharField(max_length=32)
   roles = models.ManyToManyField("Role", blank=True)

   def __str__(self):
       return self.name
   

Study Records

Track student performance and attendance.


class StudyRecord(models.Model):
   student = models.ForeignKey("Enrollment", on_delete=models.CASCADE)
   teaching_record = models.ForeignKey("TeachingRecord", on_delete=models.CASCADE)
   ATTENDANCE_STATUS = [(0, 'Present'), (1, 'Late'), (2, 'Absent'), (3, 'Early Leave')]
   attendance = models.SmallIntegerField(choices=ATTENDANCE_STATUS, default=0)
   
   SCORE_GRADES = [
       (100, "A+"), (90, "A"), (85, "B+"), (80, "B"),
       (75, "B-"), (70, "C+"), (60, "C"), (40, "C-"),
       (-50, "D"), (-100, "COPY"), (0, "N/A")
   ]
   score = models.SmallIntegerField(choices=SCORE_GRADES, default=0)
   memo = models.TextField(blank=True, null=True)
   date = models.DateField(auto_now_add=True)

   def __str__(self):
       return f"{self.student} - Score: {self.get_score_display()}"

   class Meta:
       unique_together = ('student', 'teaching_record')
   

Admin Confiugration

Register the models in admin.py to manage them via the Django admin interface.


from django.contrib import admin
from .models import Client, UserProfile, Role, Menu, FollowUpRecord, Course, ClassBatch

class ClientAdmin(admin.ModelAdmin):
   list_display = ('id', 'contact_qq', 'source', 'consultant', 'status', 'created_at')
   list_filter = ('source', 'consultant', 'created_at')
   search_fields = ('contact_qq', 'name')
   filter_horizontal = ('tags',)

class UserProfileAdmin(admin.ModelAdmin):
   list_display = ('user', 'name')

admin.site.register(Client, ClientAdmin)
admin.site.register(FollowUpRecord)
admin.site.register(Course)
admin.site.register(ClassBatch)
admin.site.register(Role)
admin.site.register(Menu)
admin.site.register(UserProfile, UserProfileAdmin)
# Register other models as needed...
   

Views and URL Routing

Implement the controller logic to handle requests. Start by including the app URLs in the project's root urls.py.


# project/urls.py
from django.conf.urls import include, url

urlpatterns = [
   url(r'^crm/', include('crm.urls')),
   # ... other patterns
]
   

Define the CRM specific URLs and views. We will create a dashboard view that handles role-based rendering.


# crm/urls.py
from django.conf.urls import url
from . import views

urlpatterns = [
   url(r'^$', views.DashboardView.as_view(), name="dashboard"),
   url(r'^customers/$', views.customer_list, name="customer_list"),
]

# crm/views.py
from django.shortcuts import render
from django.views import View

class DashboardView(View):
   def get(self, request):
       return render(request, 'crm/dashboard.html')

def customer_list(request):
   return render(request, 'crm/customers.html')
   

Role-Based Menu Rendering

In the frontend templates, dynamically render navigation menus based on the logged-in user's assigned roles. The following template snippet iterates through the user's profile, their roles, and the associated menus.


<div class="user-info">
   Current User: {{ request.user.userprofile.name }}
</div>

<ul class="nav-menu">
   {% for role in request.user.userprofile.roles.all %}
       {% for menu in role.menus.all %}
           <li><a href="{% url menu.url_name %}">{{ menu.name }}</a></li>
       {% endfor %}
   {% endfor %}
</ul>
   

Extending for Student Access

To separate student functionality, create a new app named student and add its routes.


python manage.py startapp student
   

Configure the student views and URLs similar to the CRM app. If a user with a "Student" role logs in, the template logic will render the specific menus assigned to that role, directing them to the student dashboard.

Tags: Django python MySQL CRM Model Design

Posted on Sat, 27 Jun 2026 17:50:55 +0000 by aboldock