wip for crm

sync
Laurent 11 months ago
parent 81fee53656
commit ff91b3023e
  1. 6
      crm/admin.py
  2. 8
      crm/filters.py
  3. 32
      crm/migrations/0003_remove_prospect_region_prospect_address_and_more.py
  4. 32
      crm/migrations/0004_remove_prospect_name_prospect_entity_name_and_more.py
  5. 18
      crm/migrations/0005_prospect_phone.py
  6. 11
      crm/models.py
  7. 33
      crm/templates/crm/add_prospect.html
  8. 34
      crm/templates/crm/csv_import.html
  9. 15
      crm/templates/crm/events.html
  10. 18
      crm/templates/crm/prospect_list.html
  11. 6
      crm/urls.py
  12. 84
      crm/views.py
  13. BIN
      db.sqlite3.main

@ -11,9 +11,9 @@ from .models import (
@admin.register(Prospect)
class ProspectAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'region', 'created_at')
list_filter = ('region', 'created_at')
search_fields = ('name', 'email', 'region')
list_display = ('entity_name', 'first_name', 'last_name', 'email', 'address', 'zip_code', 'city', 'created_at')
list_filter = ('zip_code', 'created_at')
search_fields = ('entity_name', 'first_name', 'last_name', 'email', 'zip_code', 'city')
filter_horizontal = ('users',)
date_hierarchy = 'created_at'

@ -3,16 +3,12 @@ import django_filters
from .models import Event, Status, Prospect
class ProspectFilter(django_filters.FilterSet):
region = django_filters.CharFilter(lookup_expr='icontains')
zip_code = django_filters.CharFilter(lookup_expr='icontains')
events = django_filters.ModelMultipleChoiceFilter(
queryset=Event.objects.all(),
field_name='events',
)
statuses = django_filters.ModelMultipleChoiceFilter(
queryset=Status.objects.all(),
field_name='prospectstatus__status',
)
class Meta:
model = Prospect
fields = ['region', 'events', 'statuses']
fields = ['city', 'events']

@ -0,0 +1,32 @@
# Generated by Django 5.1 on 2024-12-16 15:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('crm', '0002_alter_event_options_alter_prospect_options_and_more'),
]
operations = [
migrations.RemoveField(
model_name='prospect',
name='region',
),
migrations.AddField(
model_name='prospect',
name='address',
field=models.CharField(blank=True, max_length=200, null=True),
),
migrations.AddField(
model_name='prospect',
name='city',
field=models.CharField(blank=True, max_length=500, null=True),
),
migrations.AddField(
model_name='prospect',
name='zip_code',
field=models.CharField(blank=True, max_length=20, null=True),
),
]

@ -0,0 +1,32 @@
# Generated by Django 5.1 on 2024-12-16 16:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('crm', '0003_remove_prospect_region_prospect_address_and_more'),
]
operations = [
migrations.RemoveField(
model_name='prospect',
name='name',
),
migrations.AddField(
model_name='prospect',
name='entity_name',
field=models.CharField(blank=True, max_length=200, null=True),
),
migrations.AddField(
model_name='prospect',
name='first_name',
field=models.CharField(blank=True, max_length=200, null=True),
),
migrations.AddField(
model_name='prospect',
name='last_name',
field=models.CharField(blank=True, max_length=200, null=True),
),
]

@ -0,0 +1,18 @@
# Generated by Django 5.1 on 2024-12-16 16:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('crm', '0004_remove_prospect_name_prospect_entity_name_and_more'),
]
operations = [
migrations.AddField(
model_name='prospect',
name='phone',
field=models.CharField(blank=True, max_length=25, null=True),
),
]

@ -12,8 +12,13 @@ class EventType(models.TextChoices):
class Prospect(models.Model):
email = models.EmailField(unique=True)
name = models.CharField(max_length=200)
region = models.CharField(max_length=100)
entity_name = models.CharField(max_length=200, null=True, blank=True)
first_name = models.CharField(max_length=200, null=True, blank=True)
last_name = models.CharField(max_length=200, null=True, blank=True)
address = models.CharField(max_length=200, null=True, blank=True)
zip_code = models.CharField(max_length=20, null=True, blank=True)
city = models.CharField(max_length=500, null=True, blank=True)
phone = models.CharField(max_length=25, null=True, blank=True)
users = models.ManyToManyField(get_user_model(), blank=True)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(
@ -37,7 +42,7 @@ class Prospect(models.Model):
]
def __str__(self):
return f"{self.name} ({self.email})"
return ' - '.join(filter(None, [self.entity_name, self.first_name, self.last_name, f"({self.email})"]))
class Status(models.Model):
name = models.CharField(max_length=100, unique=True)

@ -7,8 +7,18 @@
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="name">Name:</label>
<input type="text" name="name" id="name" required />
<label for="entity_name">Entité (nom de club...):</label>
<input type="text" name="entity_name" id="entity_name" />
</div>
<div class="form-group">
<label for="first_name">Prénom:</label>
<input type="text" name="first_name" id="first_name" />
</div>
<div class="form-group">
<label for="last_name">Nom:</label>
<input type="text" name="last_name" id="last_name" />
</div>
<div class="form-group">
@ -17,8 +27,23 @@
</div>
<div class="form-group">
<label for="region">Region:</label>
<input type="text" name="region" id="region" required />
<label for="phone">Téléphone:</label>
<input type="text" name="phone" id="phone" />
</div>
<div class="form-group">
<label for="address">Adresse:</label>
<input type="text" name="address" id="address" />
</div>
<div class="form-group">
<label for="zip_code">Code postal:</label>
<input type="text" name="zip_code" id="zip_code" />
</div>
<div class="form-group">
<label for="city">Ville:</label>
<input type="text" name="city" id="city" />
</div>
<button type="submit" class="small-button margin-v20">

@ -0,0 +1,34 @@
{% extends "crm/base.html" %}
{% block content %}
<div class="container mt-4">
<h2>Import Prospects from CSV</h2>
<div class="card">
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
{{ form.as_p }}
</div>
<div class="alert alert-info">
<h5>CSV Format Requirements:</h5>
<p>The CSV file should contain the following columns in order:</p>
<ul>
<li>Column 1: Club Code</li>
<li>Column 2: Last Name</li>
<li>Column 3: First Name</li>
<li>Column 4: Email</li>
<li>Column 9: ZIP Code</li>
<li>Column 10: City</li>
</ul>
</div>
<button type="submit" class="btn btn-primary">Import CSV</button>
</form>
</div>
</div>
</div>
{% endblock %}

@ -7,11 +7,18 @@
{% if request.user|is_crm_manager %}
<div class="d-flex">
<a href="{% url 'crm:add_event' %}" class="small-button margin-v20">
Add Event
<a href="{% url 'crm:prospect-list' %}" class="small-button margin-v20">
Prospects
</a>
<a href="{% url 'crm:add-event' %}" class="small-button margin-v20">
Ajouter un évènement
</a>
<a href="{% url 'crm:add-prospect' %}" class="small-button margin-v20 left-margin">
Ajouter un prospect
</a>
<a href="{% url 'crm:add_prospect' %}" class="small-button margin-v20 left-margin">
Add Prospect
<a href="{% url 'crm:csv-import' %}" class="small-button margin-v20 left-margin">
Import
</a>
</div>

@ -17,7 +17,7 @@
</div>
<div class="mb-3">
<a href="{% url 'crm:prospect-import' %}" class="btn btn-success">Import CSV</a>
<a href="{% url 'crm:csv-import' %}" class="btn btn-success">Import CSV</a>
<a href="{% url 'crm:send-bulk-email' %}" class="btn btn-primary">Send Email</a>
</div>
@ -26,10 +26,12 @@
<thead>
<tr>
<th><input type="checkbox" id="select-all"></th>
<th>Name</th>
<th>Entité</th>
<th>Prénom</th>
<th>Nom</th>
<th>Email</th>
<th>Region</th>
<th>Status</th>
<th>Téléphone</th>
<th>Statut</th>
<th>Actions</th>
</tr>
</thead>
@ -37,9 +39,11 @@
{% for prospect in prospects %}
<tr>
<td><input type="checkbox" name="selected_prospects" value="{{ prospect.id }}"></td>
<td>{{ prospect.name }}</td>
<td>{{ prospect.email }}</td>
<td>{{ prospect.region }}</td>
<td>{{ prospect.entity_name }}</td>
<td>{{ prospect.first_name }}</td>
<td>{{ prospect.last_name }}</td>
<td><a href="mailto:{{ prospect.email }}">{{ prospect.email }}</a></td>
<td>{{ prospect.phone }}</td>
<td>
{% for status in prospect.prospectstatus_set.all %}
<span class="badge bg-primary">{{ status.status.name }}</span>

@ -5,11 +5,11 @@ app_name = 'crm'
urlpatterns = [
path('', views.EventListView.as_view(), name='planned_events'),path('', views.EventListView.as_view(), name='planned-events'),
path('events/add/', views.EventCreateView.as_view(), name='add_event'),
path('events/add/', views.EventCreateView.as_view(), name='add-event'),
path('events/<int:pk>/edit/', views.EditEventView.as_view(), name='edit_event'),
path('events/<int:pk>/start/', views.StartEventView.as_view(), name='start_event'),
path('prospects/', views.ProspectListView.as_view(), name='prospect-list'),
path('add-prospect/', views.add_prospect, name='add_prospect'),
path('prospects/import/', views.CSVImportView.as_view(), name='prospect-import'),
path('add-prospect/', views.add_prospect, name='add-prospect'),
path('prospects/import/', views.CSVImportView.as_view(), name='csv-import'),
path('email/send/', views.SendBulkEmailView.as_view(), name='send-bulk-email'),
]

@ -13,6 +13,7 @@ from django.contrib.sites.shortcuts import get_current_site
from django.template.loader import render_to_string
from django.core.mail import send_mail
from django.conf import settings
from django.db import IntegrityError
from .models import Event, Prospect, EmailTracker, EmailCampaign, EventType
from .filters import ProspectFilter
@ -27,15 +28,27 @@ from datetime import datetime
@permission_required('crm.view_crm', raise_exception=True)
def add_prospect(request):
if request.method == 'POST':
name = request.POST.get('name')
entity_name = request.POST.get('entity_name')
first_name = request.POST.get('first_name')
last_name = request.POST.get('last_name')
email = request.POST.get('email')
region = request.POST.get('region')
phone = request.POST.get('phone')
address = request.POST.get('address')
zip_code = request.POST.get('zip_code')
city = request.POST.get('city')
# region = request.POST.get('region')
try:
prospect = Prospect.objects.create(
name=name,
entity_name=entity_name,
first_name=first_name,
last_name=last_name,
email=email,
region=region,
phone=phone,
address=address,
zip_code=zip_code,
city=city,
# region=region,
created_by=request.user,
modified_by=request.user
)
@ -126,6 +139,7 @@ class ProspectListView(CRMAccessMixin, ListView):
)
return context
class CSVImportView(CRMAccessMixin, FormView):
template_name = 'crm/csv_import.html'
form_class = CSVImportForm
@ -134,17 +148,63 @@ class CSVImportView(CRMAccessMixin, FormView):
def form_valid(self, form):
csv_file = TextIOWrapper(
form.cleaned_data['csv_file'].file,
encoding='utf-8'
encoding='utf-8-sig' # Handle potential BOM in CSV
)
reader = csv.DictReader(csv_file)
reader = csv.reader(csv_file, delimiter=';') # Using semicolon delimiter
# Skip header if exists
next(reader, None)
created_count = 0
updated_count = 0
error_count = 0
for row in reader:
Prospect.objects.create(
email=row['email'],
name=row.get('name', ''),
region=row.get('region', ''),
created_by=self.request.user,
modified_by=self.request.user
try:
if len(row) < 10: # Ensure we have enough columns
continue
# Extract data from correct columns
entity_name = row[0].strip()
last_name = row[1].strip()
first_name = row[2].strip()
email = row[3].strip()
phone = row[4].strip()
zip_code = row[8].strip()
city = row[9].strip()
# Try to update existing prospect or create new one
prospect, created = Prospect.objects.update_or_create(
email=email, # Use email as unique identifier
defaults={
'entity_name': entity_name,
'first_name': first_name,
'last_name': last_name,
'phone': phone,
'zip_code': zip_code,
'city': city,
'modified_by': self.request.user,
}
)
if created:
prospect.created_by = self.request.user
prospect.save()
created_count += 1
else:
updated_count += 1
except Exception as e:
error_count += 1
messages.error(
self.request,
f"Error processing row with email {email}: {str(e)}"
)
# Add success message
messages.success(
self.request,
f"Import completed: {created_count} created, {updated_count} updated, {error_count} errors"
)
return super().form_valid(form)

Binary file not shown.
Loading…
Cancel
Save