Skip to content

Commit 8074907

Browse files
authored
Implement campaign creation and subscriber import
1 parent e9d3135 commit 8074907

File tree

7 files changed

+128
-326
lines changed

7 files changed

+128
-326
lines changed

campaigns/urls.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
app_name = 'campaigns'
55

66
urlpatterns = [
7-
path('campaigns/', views.CampaignListCreateAPIView.as_view(), name='campaign-list-create'),
8-
path('campaigns/<uuid:pk>/', views.CampaignRetrieveUpdateDestroyAPIView.as_view(), name='campaign-detail'),
9-
path('campaigns/<uuid:campaign_id>/emails/', views.EmailListCreateAPIView.as_view(), name='email-list-create'),
10-
path('campaigns/<uuid:campaign_id>/emails/<uuid:pk>/', views.EmailRetrieveUpdateDestroyAPIView.as_view(), name='email-detail'),
11-
path('campaigns/<uuid:campaign_id>/activate/', views.activate_campaign, name='campaign-activate'),
12-
path('campaigns/<uuid:campaign_id>/deactivate/', views.deactivate_campaign, name='campaign-deactivate'),
13-
path('campaigns/<uuid:pk>/edit/', views.campaign_edit_view, name='campaign-edit'),
7+
path('campaigns/create/', views.campaign_create, name='create'),
8+
path('campaigns/template/<uuid:campaign_id>/', views.campaign_template, name='template'),
9+
path('campaigns/template/', views.campaign_template, name='new-template'),
10+
path('api/campaigns/create/', views.create_campaign, name='api-create'),
11+
path('api/campaigns/<uuid:campaign_id>/emails/', views.EmailListCreateAPIView.as_view(), name='email-list-create'),
12+
path('api/campaigns/<uuid:campaign_id>/emails/<uuid:pk>/', views.EmailRetrieveUpdateDestroyAPIView.as_view(), name='email-detail'),
13+
path('api/campaigns/<uuid:campaign_id>/activate/', views.activate_campaign, name='campaign-activate'),
14+
path('api/campaigns/<uuid:campaign_id>/deactivate/', views.deactivate_campaign, name='campaign-deactivate'),
1415
]

campaigns/views.py

Lines changed: 37 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,46 @@
1-
from django.shortcuts import get_object_or_404, render
1+
from django.shortcuts import render, redirect, get_object_or_404
22
from django.contrib.auth.decorators import login_required
3-
from rest_framework import generics, permissions, status
3+
from django.contrib import messages
4+
from django.utils.translation import gettext as _
45
from rest_framework.decorators import api_view, permission_classes
6+
from rest_framework.permissions import IsAuthenticated
57
from rest_framework.response import Response
68
from .models import Campaign, Email
79
from .serializers import CampaignSerializer, EmailSerializer
8-
from .tasks import send_campaign_emails
10+
import uuid
911

10-
class CampaignListCreateAPIView(generics.ListCreateAPIView):
11-
"""List and create campaigns."""
12-
serializer_class = CampaignSerializer
13-
permission_classes = [permissions.IsAuthenticated]
14-
15-
def get_queryset(self):
16-
"""Return only campaigns belonging to the current user."""
17-
return Campaign.objects.filter(user=self.request.user).order_by('-created_at')
18-
19-
20-
class CampaignRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
21-
"""Retrieve, update, and delete a campaign."""
22-
serializer_class = CampaignSerializer
23-
permission_classes = [permissions.IsAuthenticated]
24-
25-
def get_queryset(self):
26-
"""Return only campaigns belonging to the current user."""
27-
return Campaign.objects.filter(user=self.request.user)
28-
29-
30-
class EmailListCreateAPIView(generics.ListCreateAPIView):
31-
"""List and create emails for a campaign."""
32-
serializer_class = EmailSerializer
33-
permission_classes = [permissions.IsAuthenticated]
34-
35-
def get_queryset(self):
36-
"""Return only emails for the specified campaign belonging to the current user."""
37-
campaign_id = self.kwargs.get('campaign_id')
38-
return Email.objects.filter(campaign__id=campaign_id, campaign__user=self.request.user).order_by('order')
39-
40-
def perform_create(self, serializer):
41-
"""Create email and associate with the specified campaign."""
42-
campaign_id = self.kwargs.get('campaign_id')
43-
campaign = get_object_or_404(Campaign, id=campaign_id, user=self.request.user)
44-
45-
# Set order to next available if not provided
46-
if 'order' not in serializer.validated_data:
47-
next_order = Email.objects.filter(campaign=campaign).count()
48-
serializer.save(campaign=campaign, order=next_order)
49-
else:
50-
serializer.save(campaign=campaign)
51-
52-
53-
class EmailRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
54-
"""Retrieve, update, and delete an email from a campaign."""
55-
serializer_class = EmailSerializer
56-
permission_classes = [permissions.IsAuthenticated]
57-
58-
def get_queryset(self):
59-
"""Return only emails for the specified campaign belonging to the current user."""
60-
campaign_id = self.kwargs.get('campaign_id')
61-
return Email.objects.filter(campaign__id=campaign_id, campaign__user=self.request.user)
62-
63-
64-
@api_view(['POST'])
65-
@permission_classes([permissions.IsAuthenticated])
66-
def activate_campaign(request, campaign_id):
67-
"""Activate a campaign to start sending emails."""
68-
campaign = get_object_or_404(Campaign, id=campaign_id, user=request.user)
69-
70-
# Check if campaign has emails
71-
if campaign.emails.count() == 0:
72-
return Response(
73-
{"error": "Cannot activate a campaign with no emails."},
74-
status=status.HTTP_400_BAD_REQUEST
75-
)
76-
77-
campaign.is_active = True
78-
campaign.save()
79-
80-
# Schedule emails to be sent
81-
send_campaign_emails.delay(campaign_id=str(campaign.id))
82-
83-
return Response(
84-
{"message": "Campaign activated successfully. Emails will be sent according to schedule."},
85-
status=status.HTTP_200_OK
86-
)
87-
88-
89-
@api_view(['POST'])
90-
@permission_classes([permissions.IsAuthenticated])
91-
def deactivate_campaign(request, campaign_id):
92-
"""Deactivate a campaign to stop sending emails."""
93-
campaign = get_object_or_404(Campaign, id=campaign_id, user=request.user)
94-
95-
campaign.is_active = False
96-
campaign.save()
97-
98-
return Response(
99-
{"message": "Campaign deactivated successfully. No more emails will be sent."},
100-
status=status.HTTP_200_OK
101-
)
12+
@login_required
13+
def campaign_create(request):
14+
"""Render campaign creation page."""
15+
return render(request, 'campaigns/create.html')
10216

17+
@login_required
18+
def campaign_template(request, campaign_id=None):
19+
"""Render template creation/edit page."""
20+
campaign = None
21+
if campaign_id:
22+
campaign = get_object_or_404(Campaign, id=campaign_id, user=request.user)
23+
return render(request, 'campaigns/template.html', {'campaign': campaign})
10324

10425
@login_required
105-
def campaign_edit_view(request, pk):
106-
campaign = get_object_or_404(Campaign, pk=pk, user=request.user)
107-
emails = campaign.emails.all() # Emails are already ordered by the 'order' field in the model's Meta
108-
context = {
109-
'campaign': campaign,
110-
'emails': emails,
111-
}
112-
return render(request, 'campaigns/campaign_edit_page.html', context)
26+
@api_view(['POST'])
27+
@permission_classes([IsAuthenticated])
28+
def create_campaign(request):
29+
"""Create a new campaign with templates."""
30+
serializer = CampaignSerializer(data=request.data, context={'request': request})
31+
if serializer.is_valid():
32+
campaign = serializer.save()
33+
34+
# Create email templates
35+
emails_data = request.data.get('emails', [])
36+
for email_data in emails_data:
37+
email_data['campaign'] = campaign.id
38+
email_serializer = EmailSerializer(data=email_data)
39+
if email_serializer.is_valid():
40+
email_serializer.save()
41+
42+
return Response({
43+
'id': campaign.id,
44+
'message': _('Campaign created successfully')
45+
})
46+
return Response(serializer.errors, status=400)

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@vitejs/plugin-react": "^4.2.1",
1818
"autoprefixer": "^10.4.17",
1919
"axios": "^1.6.5",
20+
"pandas": "^0.0.3",
2021
"postcss": "^8.4.33",
2122
"react": "^18.2.0",
2223
"react-dom": "^18.2.0",

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ django-cors-headers==4.3.1
1010
djangorestframework==3.14.0
1111
dj-database-url==2.1.0
1212
django-allauth==0.57.0
13-
python-dateutil==2.8.2
13+
python-dateutil==2.8.2
14+
pandas==2.1.4
15+
openpyxl==3.1.2

subscribers/urls.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
app_name = 'subscribers'
55

66
urlpatterns = [
7-
path('lists/', views.ListListCreateAPIView.as_view(), name='list-list-create'),
8-
path('lists/<uuid:pk>/', views.ListRetrieveUpdateDestroyAPIView.as_view(), name='list-detail'),
9-
path('lists/<uuid:list_id>/subscribers/', views.SubscriberListCreateAPIView.as_view(), name='subscriber-list-create'),
10-
path('subscribers/<uuid:pk>/', views.SubscriberRetrieveUpdateDestroyAPIView.as_view(), name='subscriber-detail'),
11-
path('subscribers/import/', views.import_subscribers, name='import-subscribers'),
12-
path('subscribers/export/<uuid:list_id>/', views.export_subscribers, name='export-subscribers'),
13-
path('unsubscribe/<uuid:subscriber_uuid>/', views.unsubscribe, name='unsubscribe'),
14-
path('confirm-subscription/<uuid:subscriber_uuid>/', views.confirm_subscription, name='confirm-subscription'),
7+
path('subscribers/import/', views.import_subscribers, name='import'),
8+
path('subscribers/process-import/', views.process_import, name='process-import'),
9+
path('api/subscribers/import/', views.process_import, name='api-import'),
10+
path('api/lists/', views.ListListCreateAPIView.as_view(), name='list-list-create'),
11+
path('api/lists/<uuid:pk>/', views.ListRetrieveUpdateDestroyAPIView.as_view(), name='list-detail'),
12+
path('api/lists/<uuid:list_id>/subscribers/', views.SubscriberListCreateAPIView.as_view(), name='subscriber-list-create'),
13+
path('api/subscribers/<uuid:pk>/', views.SubscriberRetrieveUpdateDestroyAPIView.as_view(), name='subscriber-detail'),
1514
]

0 commit comments

Comments
 (0)