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.