diff --git a/.gitignore b/.gitignore index a306a5a8..62f48aa6 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ ref.css.map .coverage crm/dev_settings\.py env -docs/build \ No newline at end of file +docs/build +celerybeat-schedule \ No newline at end of file diff --git a/accounts/forms.py b/accounts/forms.py index 28c1e76d..dafcb2e6 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -1,5 +1,5 @@ from django import forms -from .models import Account +from .models import Account, Email from common.models import Comment, Attachments, User from leads.models import Lead from contacts.models import Contact @@ -93,13 +93,72 @@ class AccountAttachmentForm(forms.ModelForm): fields = ('attachment', 'account') -class EmailForm(forms.Form): - recipients = forms.CharField(max_length=500) - message_subject = forms.CharField(max_length=500) - message_body = forms.CharField(widget=forms.Textarea) +class EmailForm(forms.ModelForm): + recipients = forms.CharField() + # message_subject = forms.CharField(max_length=500) + # message_body = forms.CharField(widget=forms.Textarea) + # timezone = forms.CharField(max_length=200) + # scheduled_date_time = forms.DateTimeField() + # scheduled_later = forms.CharField(max_length=20) def __init__(self, *args, **kwargs): + self.account_obj = kwargs.pop('account', False) super(EmailForm, self).__init__(*args, **kwargs) - self.fields['message_subject'].widget.attrs['class'] = 'form-control' - self.fields['message_subject'].widget.attrs['required'] = True - self.fields['message_subject'].widget.attrs['placeholder'] = 'Email Subject' + # self.fields['message_subject'].widget.attrs['class'] = 'form-control' + # self.fields['message_subject'].widget.attrs['required'] = True + # self.fields['message_subject'].widget.attrs['placeholder'] = 'Email Subject' + + # self.fields['recipient'].required = False + self.fields['from_email'].required = True + self.fields['message_subject'].required = True + self.fields['message_body'].required = True + + self.fields['scheduled_date_time'].required = False + self.fields['scheduled_later'].required = False + self.fields['recipients'].required = True + self.fields['recipients'].query = self.account_obj.contacts.all() + # self.fields['recipients'].choices = list((contact.get('id'), contact.get('email')) + # for contact in self.account_obj.contacts.values('id', 'email')) + + class Meta: + model = Email + fields = ['recipients', 'message_subject', 'from_email', + 'message_body', 'timezone', 'scheduled_date_time', 'scheduled_later'] + + # def clean_recipients(self): + # recipients = self.cleaned_data.get('recipients') + # recipients = recipients.split(',') + # if len(recipients) == 0: + # raise forms.ValidationError('This field is required.') + # contacts = list(self.account_obj.contacts.values_list('id', flat=True)) + # for recipient in recipients: + # if int(recipient) not in contacts: + # raise forms.ValidationError('{} is not a valid choice.'.format(recipient)) + # return recipients + + def clean_scheduled_date_time(self): + scheduled_date_time = self.cleaned_data.get('scheduled_date_time') + if self.data.get('scheduled_later') not in ['', None, False, 'false']: + if scheduled_date_time in ['', None]: + raise forms.ValidationError('This Field is required.') + + if self.data.get('scheduled_later') == 'true': + if scheduled_date_time in ['', None]: + raise forms.ValidationError('This Field is required.') + + return scheduled_date_time + + + def clean_message_body(self): + message_body = self.cleaned_data.get('message_body') + count = 0 + for i in message_body: + if i == "{": + count += 1 + elif i == "}": + count -= 1 + if count < 0: + raise forms.ValidationError('Brackets do not match, Enter valid tags.') + if count != 0: + raise forms.ValidationError('Brackets do not match, Enter valid tags.') + return message_body diff --git a/accounts/migrations/0009_auto_20190809_1659.py b/accounts/migrations/0009_auto_20190809_1659.py new file mode 100644 index 00000000..ecab2777 --- /dev/null +++ b/accounts/migrations/0009_auto_20190809_1659.py @@ -0,0 +1,70 @@ +# Generated by Django 2.2.3 on 2019-08-09 11:29 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('contacts', '0003_merge_20190214_1427'), + ('accounts', '0008_account_assigned_to'), + ] + + operations = [ + migrations.RenameField( + model_name='email', + old_name='sent_at', + new_name='created_on', + ), + migrations.RenameField( + model_name='email', + old_name='sender', + new_name='from_account', + ), + migrations.RemoveField( + model_name='email', + name='recipient', + ), + migrations.AddField( + model_name='email', + name='from_email', + field=models.EmailField(default=django.utils.timezone.now, max_length=254), + preserve_default=False, + ), + migrations.AddField( + model_name='email', + name='recipients', + field=models.ManyToManyField(related_name='recieved_email', to='contacts.Contact'), + ), + migrations.AddField( + model_name='email', + name='rendered_message_body', + field=models.TextField(null=True), + ), + migrations.AddField( + model_name='email', + name='scheduled_date_time', + field=models.DateTimeField(null=True), + ), + migrations.AddField( + model_name='email', + name='scheduled_later', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='email', + name='timezone', + field=models.CharField(default='UTC', max_length=100), + ), + migrations.CreateModel( + name='EmailLog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_sent', models.BooleanField(default=False)), + ('contact', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contact_email_log', to='contacts.Contact')), + ('email', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='email_log', to='accounts.Email')), + ], + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 8c9674d6..75cdff3f 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -118,20 +118,27 @@ class Account(models.Model): class Email(models.Model): - sender = models.ForeignKey( + from_account = models.ForeignKey( Account, related_name='sent_email', on_delete=models.SET_NULL, null=True) - - recipient = models.ForeignKey( - Contact, related_name='recieved_email', on_delete=models.SET_NULL, null=True) - - sent_at = models.DateTimeField(auto_now_add=True) + recipients = models.ManyToManyField(Contact, related_name='recieved_email') message_subject = models.TextField(null=True) message_body = models.TextField(null=True) + timezone = models.CharField(max_length=100, default='UTC') + scheduled_date_time = models.DateTimeField(null=True) + scheduled_later = models.BooleanField(default=False) + created_on = models.DateTimeField(auto_now_add=True) + from_email = models.EmailField() + rendered_message_body = models.TextField(null=True) + def __str__(self): - return self.message_body + return self.message_subject - def save(self, *args, **kwargs): - # prevent user from sending messages to themselves - if self.sender.email != self.recipient.email: - super(Email, self).save(*args, **kwargs) + + +class EmailLog(models.Model): + """ this model is used to track if the email is sent or not """ + + email = models.ForeignKey(Email, related_name='email_log', on_delete=models.SET_NULL, null=True) + contact = models.ForeignKey(Contact, related_name='contact_email_log', on_delete=models.SET_NULL, null=True) + is_sent = models.BooleanField(default=False) diff --git a/accounts/tasks.py b/accounts/tasks.py index 8a0c6c1b..a2c5fcbf 100644 --- a/accounts/tasks.py +++ b/accounts/tasks.py @@ -1,34 +1,51 @@ +from datetime import datetime + +import pytz from celery.task import task from django.conf import settings from django.core.mail import EmailMessage -from django.template.loader import render_to_string from django.shortcuts import reverse +from django.template import Context, Template +from django.template.loader import render_to_string +from django.utils import timezone -from accounts.models import Account, Email +from accounts.models import Account, Email, EmailLog from common.models import User +from common.utils import convert_to_custom_timezone from contacts.models import Contact @task -def send_email(subject, html_content, from_email=None, - recipients=[]): - account = Account.objects.filter(id=from_email).first() - if account: - contacts = recipients - for contact in contacts: - contact_obj = Contact.objects.filter(email=contact).first() - if contact_obj: - Email.objects.create(sender=account, - recipient=contact_obj,message_subject=subject, - message_body=html_content) - msg = EmailMessage( - subject, - html_content, - from_email=account.email, - to=[contact_obj.email, ] - ) - msg.content_subtype = "html" - msg.send() +def send_email(email_obj_id): + email_obj = Email.objects.filter(id=email_obj_id).first() + if email_obj: + from_email = email_obj.from_email + contacts = email_obj.recipients.all() + for contact_obj in contacts: + if not EmailLog.objects.filter(email=email_obj, contact=contact_obj, is_sent=True).exists(): + html = email_obj.message_body + context_data = { + 'email': contact_obj.email if contact_obj.email else '', + 'name': contact_obj.first_name if contact_obj.first_name else '' + ' ' + contact_obj.last_name if contact_obj.last_name else '', + } + try: + html_content = Template(html).render(Context(context_data)) + subject = email_obj.message_subject + msg = EmailMessage( + subject, + html_content, + from_email=from_email, + to=[contact_obj.email, ] + ) + msg.content_subtype = "html" + res = msg.send() + if res: + email_obj.rendered_message_body = html_content + email_obj.save() + EmailLog.objects.create(email=email_obj, contact=contact_obj, is_sent=True) + except Exception as e: + print(e) + pass @@ -59,3 +76,26 @@ def send_email_to_assigned_user(recipients, from_email, domain='demo.django-crm. ) msg.content_subtype = "html" msg.send() + + +@task +def send_scheduled_emails(): + email_objs = Email.objects.filter(scheduled_later=True) + # TODO: modify this later , since models are updated + for each in email_objs: + scheduled_date_time = each.scheduled_date_time + + sent_time = datetime.now().strftime('%Y-%m-%d %H:%M') + sent_time = datetime.strptime(sent_time, '%Y-%m-%d %H:%M') + local_tz = pytz.timezone(settings.TIME_ZONE) + sent_time = local_tz.localize(sent_time) + sent_time = convert_to_custom_timezone( + sent_time, each.timezone, to_utc=True) + + if ( + str(each.scheduled_date_time.date()) == str(sent_time.date()) and + str(scheduled_date_time.hour) == str(sent_time.hour) and + (str(scheduled_date_time.minute + 5) < str(sent_time.minute) or + str(scheduled_date_time.minute - 5) > str(sent_time.minute)) + ): + send_email.delay(each.id) diff --git a/accounts/templates/accounts.html b/accounts/templates/accounts.html index 74907dc6..91355797 100644 --- a/accounts/templates/accounts.html +++ b/accounts/templates/accounts.html @@ -4,10 +4,19 @@ {% load thumbnail %} {% block extralinks %} + + + {% endblock %} {% block content %} @@ -102,11 +111,11 @@ {% if open_accounts|length > 0 %} ID - Name - Created By - City - State - Created On + Name + Created By + City + State + Created On Tags Actions @@ -161,6 +170,13 @@ {% endwith %} + {% if each_account.contacts.all %} + + + {% endif %}