|
|
|
@ -12,13 +12,13 @@ class MailingList(BaseModel): |
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) |
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) |
|
|
|
name = models.CharField(max_length=100) |
|
|
|
name = models.CharField(max_length=100) |
|
|
|
description = models.TextField(blank=True, null=True) |
|
|
|
description = models.TextField(blank=True, null=True) |
|
|
|
|
|
|
|
|
|
|
|
def delete_dependencies(self): |
|
|
|
def delete_dependencies(self): |
|
|
|
pass |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
def __str__(self): |
|
|
|
return self.name |
|
|
|
return self.name |
|
|
|
|
|
|
|
|
|
|
|
def subscriber_count(self): |
|
|
|
def subscriber_count(self): |
|
|
|
return self.subscribers.filter(is_active=True).count() |
|
|
|
return self.subscribers.filter(is_active=True).count() |
|
|
|
|
|
|
|
|
|
|
|
@ -31,22 +31,22 @@ class Subscriber(BaseModel): |
|
|
|
mailing_lists = models.ManyToManyField(MailingList, related_name='subscribers', blank=True) |
|
|
|
mailing_lists = models.ManyToManyField(MailingList, related_name='subscribers', blank=True) |
|
|
|
is_active = models.BooleanField(default=True) |
|
|
|
is_active = models.BooleanField(default=True) |
|
|
|
unsubscribe_token = models.UUIDField(default=uuid.uuid4, unique=True) |
|
|
|
unsubscribe_token = models.UUIDField(default=uuid.uuid4, unique=True) |
|
|
|
|
|
|
|
|
|
|
|
# Optional reference to actual user models |
|
|
|
# Optional reference to actual user models |
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True) |
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True) |
|
|
|
prospect = models.ForeignKey('biz.Prospect', on_delete=models.CASCADE, blank=True, null=True) |
|
|
|
# prospect = models.ForeignKey('biz.Prospect', on_delete=models.CASCADE, blank=True, null=True) |
|
|
|
|
|
|
|
|
|
|
|
class Meta: |
|
|
|
class Meta: |
|
|
|
unique_together = ['email'] |
|
|
|
unique_together = ['email'] |
|
|
|
|
|
|
|
|
|
|
|
def delete_dependencies(self): |
|
|
|
def delete_dependencies(self): |
|
|
|
pass |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
def __str__(self): |
|
|
|
if self.first_name and self.last_name: |
|
|
|
if self.first_name and self.last_name: |
|
|
|
return f"{self.first_name} {self.last_name} ({self.email})" |
|
|
|
return f"{self.first_name} {self.last_name} ({self.email})" |
|
|
|
return self.email |
|
|
|
return self.email |
|
|
|
|
|
|
|
|
|
|
|
def full_name(self): |
|
|
|
def full_name(self): |
|
|
|
if self.first_name and self.last_name: |
|
|
|
if self.first_name and self.last_name: |
|
|
|
return f"{self.first_name} {self.last_name}" |
|
|
|
return f"{self.first_name} {self.last_name}" |
|
|
|
@ -63,10 +63,10 @@ class EmailTemplate(BaseModel): |
|
|
|
subject = models.CharField(max_length=200) |
|
|
|
subject = models.CharField(max_length=200) |
|
|
|
html_content = models.TextField(help_text="HTML content with image support") |
|
|
|
html_content = models.TextField(help_text="HTML content with image support") |
|
|
|
text_content = models.TextField(blank=True, null=True, help_text="Plain text fallback") |
|
|
|
text_content = models.TextField(blank=True, null=True, help_text="Plain text fallback") |
|
|
|
|
|
|
|
|
|
|
|
def delete_dependencies(self): |
|
|
|
def delete_dependencies(self): |
|
|
|
pass |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
def __str__(self): |
|
|
|
return self.name |
|
|
|
return self.name |
|
|
|
|
|
|
|
|
|
|
|
@ -79,23 +79,23 @@ class Campaign(BaseModel): |
|
|
|
html_content = models.TextField(blank=True, null=True) |
|
|
|
html_content = models.TextField(blank=True, null=True) |
|
|
|
text_content = models.TextField(blank=True, null=True) |
|
|
|
text_content = models.TextField(blank=True, null=True) |
|
|
|
mailing_lists = models.ManyToManyField(MailingList, related_name='campaigns') |
|
|
|
mailing_lists = models.ManyToManyField(MailingList, related_name='campaigns') |
|
|
|
|
|
|
|
|
|
|
|
sent_at = models.DateTimeField(blank=True, null=True) |
|
|
|
sent_at = models.DateTimeField(blank=True, null=True) |
|
|
|
is_sent = models.BooleanField(default=False) |
|
|
|
is_sent = models.BooleanField(default=False) |
|
|
|
|
|
|
|
|
|
|
|
# Email tracking |
|
|
|
# Email tracking |
|
|
|
total_sent = models.IntegerField(default=0) |
|
|
|
total_sent = models.IntegerField(default=0) |
|
|
|
total_delivered = models.IntegerField(default=0) |
|
|
|
total_delivered = models.IntegerField(default=0) |
|
|
|
total_opened = models.IntegerField(default=0) |
|
|
|
total_opened = models.IntegerField(default=0) |
|
|
|
total_clicked = models.IntegerField(default=0) |
|
|
|
total_clicked = models.IntegerField(default=0) |
|
|
|
total_bounced = models.IntegerField(default=0) |
|
|
|
total_bounced = models.IntegerField(default=0) |
|
|
|
|
|
|
|
|
|
|
|
def delete_dependencies(self): |
|
|
|
def delete_dependencies(self): |
|
|
|
pass |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
def __str__(self): |
|
|
|
return self.name |
|
|
|
return self.name |
|
|
|
|
|
|
|
|
|
|
|
def get_content(self): |
|
|
|
def get_content(self): |
|
|
|
if self.template: |
|
|
|
if self.template: |
|
|
|
return { |
|
|
|
return { |
|
|
|
@ -106,7 +106,7 @@ class Campaign(BaseModel): |
|
|
|
'html': self.html_content, |
|
|
|
'html': self.html_content, |
|
|
|
'text': self.text_content |
|
|
|
'text': self.text_content |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def get_subject(self): |
|
|
|
def get_subject(self): |
|
|
|
return self.subject or (self.template.subject if self.template else "") |
|
|
|
return self.subject or (self.template.subject if self.template else "") |
|
|
|
|
|
|
|
|
|
|
|
@ -115,44 +115,44 @@ class EmailLog(BaseModel): |
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) |
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) |
|
|
|
campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE, related_name='email_logs') |
|
|
|
campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE, related_name='email_logs') |
|
|
|
subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE, related_name='email_logs') |
|
|
|
subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE, related_name='email_logs') |
|
|
|
|
|
|
|
|
|
|
|
tracking_id = models.UUIDField(default=uuid.uuid4, unique=True) |
|
|
|
tracking_id = models.UUIDField(default=uuid.uuid4, unique=True) |
|
|
|
|
|
|
|
|
|
|
|
# Email status |
|
|
|
# Email status |
|
|
|
sent_at = models.DateTimeField(blank=True, null=True) |
|
|
|
sent_at = models.DateTimeField(blank=True, null=True) |
|
|
|
delivered_at = models.DateTimeField(blank=True, null=True) |
|
|
|
delivered_at = models.DateTimeField(blank=True, null=True) |
|
|
|
opened_at = models.DateTimeField(blank=True, null=True) |
|
|
|
opened_at = models.DateTimeField(blank=True, null=True) |
|
|
|
clicked_at = models.DateTimeField(blank=True, null=True) |
|
|
|
clicked_at = models.DateTimeField(blank=True, null=True) |
|
|
|
bounced_at = models.DateTimeField(blank=True, null=True) |
|
|
|
bounced_at = models.DateTimeField(blank=True, null=True) |
|
|
|
|
|
|
|
|
|
|
|
# Error tracking |
|
|
|
# Error tracking |
|
|
|
error_message = models.TextField(blank=True, null=True) |
|
|
|
error_message = models.TextField(blank=True, null=True) |
|
|
|
|
|
|
|
|
|
|
|
class Meta: |
|
|
|
class Meta: |
|
|
|
unique_together = ['campaign', 'subscriber'] |
|
|
|
unique_together = ['campaign', 'subscriber'] |
|
|
|
|
|
|
|
|
|
|
|
def delete_dependencies(self): |
|
|
|
def delete_dependencies(self): |
|
|
|
pass |
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
def __str__(self): |
|
|
|
return f"{self.campaign.name} - {self.subscriber.email}" |
|
|
|
return f"{self.campaign.name} - {self.subscriber.email}" |
|
|
|
|
|
|
|
|
|
|
|
@property |
|
|
|
@property |
|
|
|
def is_sent(self): |
|
|
|
def is_sent(self): |
|
|
|
return self.sent_at is not None |
|
|
|
return self.sent_at is not None |
|
|
|
|
|
|
|
|
|
|
|
@property |
|
|
|
@property |
|
|
|
def is_delivered(self): |
|
|
|
def is_delivered(self): |
|
|
|
return self.delivered_at is not None |
|
|
|
return self.delivered_at is not None |
|
|
|
|
|
|
|
|
|
|
|
@property |
|
|
|
@property |
|
|
|
def is_opened(self): |
|
|
|
def is_opened(self): |
|
|
|
return self.opened_at is not None |
|
|
|
return self.opened_at is not None |
|
|
|
|
|
|
|
|
|
|
|
@property |
|
|
|
@property |
|
|
|
def is_clicked(self): |
|
|
|
def is_clicked(self): |
|
|
|
return self.clicked_at is not None |
|
|
|
return self.clicked_at is not None |
|
|
|
|
|
|
|
|
|
|
|
@property |
|
|
|
@property |
|
|
|
def is_bounced(self): |
|
|
|
def is_bounced(self): |
|
|
|
return self.bounced_at is not None |
|
|
|
return self.bounced_at is not None |
|
|
|
|