updated testcases, update code for account view page, modified code for attachments and comments for assigned user (#270)
This commit is contained in:
parent
46f1a1bbb0
commit
3bc30d3c95
26 changed files with 2201 additions and 397 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -16,4 +16,5 @@ ref.css.map
|
|||
.coverage
|
||||
crm/dev_settings\.py
|
||||
env
|
||||
docs/build
|
||||
docs/build
|
||||
celerybeat-schedule
|
||||
|
|
@ -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
|
||||
|
|
|
|||
70
accounts/migrations/0009_auto_20190809_1659.py
Normal file
70
accounts/migrations/0009_auto_20190809_1659.py
Normal file
|
|
@ -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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -4,10 +4,19 @@
|
|||
{% load thumbnail %}
|
||||
{% block extralinks %}
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.6/css/selectize.bootstrap3.min.css">
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.0-alpha14/css/tempusdominus-bootstrap-4.min.css" />
|
||||
<style>
|
||||
.form-group label {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
#editor-container {
|
||||
height: 20rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
|
@ -102,11 +111,11 @@
|
|||
{% if open_accounts|length > 0 %}
|
||||
<tr>
|
||||
<th width="5%">ID</th>
|
||||
<th width="15%">Name</th>
|
||||
<th width="10%">Created By</th>
|
||||
<th width="15%">City</th>
|
||||
<th width="15%">State</th>
|
||||
<th width="15%">Created On</th>
|
||||
<th width="12%">Name</th>
|
||||
<th width="8%">Created By</th>
|
||||
<th width="5%">City</th>
|
||||
<th width="5%">State</th>
|
||||
<th width="10%">Created On</th>
|
||||
<th width="10%">Tags</th>
|
||||
<th width="10%">Actions</th>
|
||||
</tr>
|
||||
|
|
@ -161,6 +170,13 @@
|
|||
{% endwith %}
|
||||
</td>
|
||||
<td class="actions">
|
||||
{% if each_account.contacts.all %}
|
||||
<a href="{% url 'accounts:create_mail' %}" class="btn btn-primary edit account_send_mail"
|
||||
data-account="{{each_account.id}}" data-account-email="{{each_account.email}}"
|
||||
data-toggle="modal" data-target="#send_email_to_contacts"><i
|
||||
class="fas fa-paper-plane"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'accounts:view_account' each_account.id %}" class="btn btn-info edit"><i
|
||||
class="fas fa-eye"></i></a>
|
||||
<a href="{% url 'accounts:edit_account' each_account.id %}" class="btn btn-success edit"><i
|
||||
|
|
@ -647,10 +663,151 @@
|
|||
|
||||
{% endfor %}
|
||||
|
||||
<div id="send_email_to_contacts" class="modal fade bd-example-modal-lg" tabindex="-1" role="dialog"
|
||||
aria-labelledby="myLargeModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="col-md-12">
|
||||
<form id="add_form_mail" method="POST" action="{% url 'accounts:create_mail' %}" novalidate>
|
||||
<div class="overview_form_block row marl justify-content-center">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title text-center">
|
||||
Send Mail
|
||||
</div>
|
||||
<form>
|
||||
<div class="row marl">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="">From Email</label>
|
||||
<!-- <input type="text" class="form-control" name="contacts_list" id="id_contacts"> -->
|
||||
<!-- <select name="recipients" id="_id_recipients">
|
||||
<option value="">email</option>
|
||||
</select> -->
|
||||
<input class="form-control" type="text" placeholder="From Email" name="from_email" id="set_from_email">
|
||||
<span id="id_from_email"></span>
|
||||
</div>
|
||||
<span id="id_recipientsa"></span>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="">Contacts</label>
|
||||
<!-- <input type="text" class="form-control" name="contacts_list" id="id_contacts"> -->
|
||||
<!-- <select name="recipients" id="_id_recipients">
|
||||
<option value="">email</option>
|
||||
</select> -->
|
||||
<input type="text" placeholder="Contacts" name="recipients" id="_id_recipients">
|
||||
</div>
|
||||
<span id="id_recipients"></span>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="">Subject</label>
|
||||
<input type="text" class="form-control" name="message_subject" placeholder="Enter Subject">
|
||||
</div>
|
||||
<span id="id_message_subject"></span>
|
||||
<input type="hidden" value="" name="account_id" id="mail_account_id">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label><input type="checkbox" name="scheduled_later" value="true" class="scheduled_later">
|
||||
Schedule Later</label>
|
||||
<span id="id_schedule_later"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 timezone-div" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label>Timezone</label>
|
||||
<select name="timezone" id="timezone"
|
||||
class="form-control timezone-select js-example-basic-single">
|
||||
<option value="">Select a Timezone</option>
|
||||
{% for timezone in timezones %}
|
||||
<option value="{{timezone.0}}"
|
||||
{% if timezone.0 == settings_timezone %}selected{% endif %}>{{timezone.1}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<span id="id_timezone"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 timezone-div" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label for="">Schedule Date Time</label><span class="error_marker" style="color:red">
|
||||
*</span>
|
||||
<div class="input-group date" id="datetimepicker1" data-target-input="nearest">
|
||||
<input type="text" class="form-control datetimepicker-input"
|
||||
data-target="#datetimepicker1" name="scheduled_date_time" />
|
||||
<div class="input-group-append" data-target="#datetimepicker1"
|
||||
data-toggle="datetimepicker">
|
||||
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <input type="text" class="form-control" name="schedule_date_time" value="" placeholder="Schedule Date Time"> -->
|
||||
<span id="id_scheduled_date_time"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label>Message</label>
|
||||
<div id="editor-container"></div>
|
||||
</div>
|
||||
<input type="hidden" name="message_body" id="html_content">
|
||||
<span id="id_message_body"></span>
|
||||
</div>
|
||||
<!-- <div class="col-md-12 text-center">
|
||||
<pre id="myPrecious"></pre>
|
||||
<div id="justText"></div>
|
||||
<div id="justHtml"></div>
|
||||
</div> -->
|
||||
<div class="col-md-12">
|
||||
<div class="heading">Personalization tags</div>
|
||||
<p class="desc">The following tags can be used on both Plain text or HTML. Add these tags to
|
||||
customize the email template, these tags will be replaced with the associated contact details
|
||||
while the mails are sent.</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Name: </strong>
|
||||
<code>{% templatetag openvariable %} name {% templatetag closevariable %}</code>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Email: </strong>
|
||||
<code>{% templatetag openvariable %} email {% templatetag closevariable %}</code>
|
||||
</li>
|
||||
<!-- <li>
|
||||
<strong>Two digit day of the month (eg. 01 to 31):</strong>
|
||||
<code>[currentdaynumber]</code>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-12 text-center">
|
||||
<button type="submit" class="primary_btn mar_btm save_btn">Send</button>
|
||||
<a href="#" class="primary_btn" data-dismiss="modal">Cancel</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block js_block %}
|
||||
<script src="{% static 'js/ajaxForm.js' %}"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.6/js/standalone/selectize.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.0-alpha14/js/tempusdominus-bootstrap-4.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$("#close-tab").click(function (e) {
|
||||
$("#tab_status").val('Closed');
|
||||
|
|
@ -662,37 +819,6 @@
|
|||
window.location.href = url;
|
||||
});
|
||||
|
||||
// $(".account_modal").click(function (e) {
|
||||
// console.log('clicked')
|
||||
// console.log(e)
|
||||
// console.log($(this).attr('data-id'))
|
||||
|
||||
// $.ajax({
|
||||
// url:`get-account-details/${$(this).attr('data-id')}/`,
|
||||
// type: 'GET',
|
||||
// success:function(data){
|
||||
// console.log(data)
|
||||
// console.log((data.data.fields))
|
||||
// $('#modal_body_ajax').empty()
|
||||
// for(key in data.data.fields){
|
||||
// console.log(key, data.data.fields[key])
|
||||
// $('#modal_body_ajax').append(
|
||||
// "<div class=''><span>" + key + "</span> " + "<span>" + data.data.fields[key] + "</span>" + "</div>"
|
||||
// )
|
||||
// }
|
||||
// // $('textarea#modal_data').val(JSON.stringify(data))
|
||||
// $('#exampleModalCenter_account').modal()
|
||||
// if(data.error){
|
||||
// // TODO
|
||||
// data.fields
|
||||
// }
|
||||
// else{
|
||||
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
|
||||
$("#open-tab").click(function (e) {
|
||||
$("#tab_status").val('Open')
|
||||
})
|
||||
|
|
@ -759,5 +885,168 @@
|
|||
$('#accounts_filter').attr("action", $(this).attr("href"));
|
||||
$('#accounts_filter').submit();
|
||||
});
|
||||
|
||||
|
||||
$(".timezone-select").select2({
|
||||
placeholder: "Select a Timezone",
|
||||
});
|
||||
|
||||
$('.scheduled_later').on('change', function () {
|
||||
// console.log('changed')
|
||||
if ($('.check_delete:checked').length > 1) {
|
||||
$('.timezone-div').show();
|
||||
}
|
||||
else {
|
||||
$('.timezone-div').hide();
|
||||
}
|
||||
if ($(this).is(":checked")) {
|
||||
$('.timezone-div').show();
|
||||
} else {
|
||||
$('.timezone-div').hide();
|
||||
}
|
||||
console.log($(this))
|
||||
console.log($(this).is(':checked'))
|
||||
})
|
||||
|
||||
$.fn.datetimepicker.Constructor.Default = $.extend({}, $.fn.datetimepicker.Constructor.Default, {
|
||||
icons: {
|
||||
time: 'fa fa-clock',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-arrow-up',
|
||||
down: 'fa fa-arrow-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'far fa-calendar-check-o',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-times'
|
||||
}
|
||||
});
|
||||
$('#datetimepicker1').datetimepicker({
|
||||
format: "YYYY-MM-DD HH:mm"
|
||||
});
|
||||
|
||||
var selectedAccountId = '';
|
||||
var contactOptions = [];
|
||||
// $("#id_contacts").select2();
|
||||
$('.account_send_mail').click(function (e) {
|
||||
// console.log($(this).attr('data-account'))
|
||||
selectedAccountId = $(this).attr('data-account')
|
||||
$('#mail_account_id').val(selectedAccountId)
|
||||
$('#set_from_email').val($(this).attr('data-account-email'))
|
||||
$.ajax({
|
||||
url: "{% url 'accounts:get_contacts_for_account' %}",
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
'account_id': selectedAccountId,
|
||||
},
|
||||
error: function (err) {
|
||||
console.log(err)
|
||||
},
|
||||
success: function (res) {
|
||||
contactOptions = [];
|
||||
contactOptions = res;
|
||||
$('#_id_recipients').selectize({
|
||||
valueField: 'id',
|
||||
labelField: 'name',
|
||||
searchField: ['name', 'email'],
|
||||
// options: [
|
||||
// {email: 'brian@thirdroute.com', name: 'Brian Reavis'},
|
||||
// {email: 'nikola@tesla.com', name: 'Nikola Tesla'},
|
||||
// {email: 'someone@gmail.com'}
|
||||
// ],
|
||||
options: res,
|
||||
render: {
|
||||
item: function (item, escape) {
|
||||
return '<div class="selectize_options_remove">' +
|
||||
(item.name ? '<span class="name">' + escape(item.name) + '</span>' : '') +
|
||||
(item.email ? '<span class="email">' + escape(item.email) + '</span>' : '') +
|
||||
'</div>';
|
||||
},
|
||||
option: function (item, escape) {
|
||||
var label = item.name || item.email;
|
||||
var caption = item.name ? item.email : null;
|
||||
return '<div class="selectize_options_remove">' +
|
||||
'<span class="label">' + escape(label) + '</span>' +
|
||||
(caption ? '<span class="caption">' + escape(caption) + '</span>' : '') +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
$("#send_email_to_contacts").on('hidden.bs.modal', function () {
|
||||
$('#_id_recipients')[0].selectize.destroy()
|
||||
$('p.failure').remove();
|
||||
$('#_id_recipients').val('')
|
||||
|
||||
})
|
||||
|
||||
$("#send_email_to_contacts").on('shown.bs.modal', function () {
|
||||
});
|
||||
|
||||
var quill = new Quill('#editor-container', {
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ header: [1, 2, false] }],
|
||||
['bold', 'italic', 'underline'],
|
||||
['code-block', 'link']
|
||||
]
|
||||
},
|
||||
placeholder: 'Compose mail...',
|
||||
theme: 'snow' // or 'bubble'
|
||||
});
|
||||
|
||||
var preciousContent = document.getElementById('myPrecious');
|
||||
var justTextContent = document.getElementById('justText');
|
||||
var justHtmlContent = document.getElementById('justHtml');
|
||||
|
||||
quill.on('text-change', function () {
|
||||
var delta = quill.getContents();
|
||||
var text = quill.getText();
|
||||
var justHtml = quill.root.innerHTML;
|
||||
/*preciousContent.innerHTML = JSON.stringify(delta);
|
||||
justTextContent.innerHTML = text;
|
||||
justHtmlContent.innerHTML = justHtml;*/
|
||||
$('#html_content').val(justHtml);
|
||||
$('.ql-syntax').css({"background-color":"#23241f","color":"#f8f8f2","overflow":"visible","border-radius":"3px","white-space":"pre-wrap","margin-bottom":"5px","margin-top":"5px","padding":"5px 10px"})
|
||||
|
||||
});
|
||||
|
||||
|
||||
$('.save_btn').click(function (e) {
|
||||
e.preventDefault();
|
||||
$("form#add_form_mail").submit()
|
||||
})
|
||||
|
||||
$('form#add_form_mail').ajaxForm({
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
url: $(this).attr('action'),
|
||||
data: $(this).serialize(),
|
||||
beforeSend: function (e) {
|
||||
$('.ql-syntax').css({"background-color":"#23241f","color":"#f8f8f2","overflow":"visible","border-radius":"3px","white-space":"pre-wrap","margin-bottom":"5px","margin-top":"5px","padding":"5px 10px"})
|
||||
},
|
||||
success: function (data) {
|
||||
console.log("data", data)
|
||||
if (data.error == false) {
|
||||
window.location = "{% url 'accounts:list' %}";
|
||||
} else {
|
||||
/*$(document).ajaxStop($.unblockUI);*/
|
||||
// $.unblockUI();
|
||||
$('p.failure').remove();
|
||||
for (var key in data.errors) {
|
||||
$('#id_' + key).html('<p class="error failure"> *' + data.errors[key] + '</p>');
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function (xhr, errmsg, err) {
|
||||
console.log('error data', errmsg, err)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock js_block %}
|
||||
|
|
@ -3,8 +3,23 @@
|
|||
{% load paginate %}
|
||||
{% block extralinks %}
|
||||
{% load thumbnail %}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.6/css/selectize.bootstrap3.min.css">
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.6/css/selectize.bootstrap3.min.css">
|
||||
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.0-alpha14/css/tempusdominus-bootstrap-4.min.css" />
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'accounts:list' %}">Accounts</a></li>
|
||||
<li class="breadcrumb-item active">Create Mail</li>
|
||||
</ol>
|
||||
</nav>
|
||||
{% endblock breadcrumb %}
|
||||
|
||||
{% block content %}
|
||||
<!-- main_container starts here -->
|
||||
<div class="main_container">
|
||||
|
|
@ -36,10 +51,51 @@
|
|||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="">Subject</label>
|
||||
<input type="text" class="form-control" name="message_subject" placeholder="Enter Subject">
|
||||
<input type="text" class="form-control" name="message_subject" placeholder="Enter Subject">
|
||||
</div>
|
||||
<span id="id_message_subject"></span>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-group">
|
||||
<label><input type="checkbox" name="scheduled_later" value="true" class="scheduled_later">
|
||||
Schedule Later</label>
|
||||
<span id="id_schedule_later"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 timezone-div" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label>Timezone</label>
|
||||
<select name="timezone" id="timezone"
|
||||
class="form-control timezone-select js-example-basic-single">
|
||||
<option value="">Select a Timezone</option>
|
||||
{% for timezone in timezones %}
|
||||
<option value="{{timezone.0}}"
|
||||
{% if timezone.0 == settings_timezone %}selected{% endif %}>{{timezone.1}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<span id="id_timezone"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 timezone-div" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label for="">Schedule Date Time</label><span class="error_marker" style="color:red">
|
||||
*</span>
|
||||
<div class="input-group date" id="datetimepicker1" data-target-input="nearest">
|
||||
<input type="text" class="form-control datetimepicker-input"
|
||||
data-target="#datetimepicker1" name="scheduled_date_time" />
|
||||
<div class="input-group-append" data-target="#datetimepicker1"
|
||||
data-toggle="datetimepicker">
|
||||
<div class="input-group-text"><i class="fa fa-calendar"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <input type="text" class="form-control" name="schedule_date_time" value="" placeholder="Schedule Date Time"> -->
|
||||
<span id="id_scheduled_date_time"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label>Message</label>
|
||||
|
|
@ -53,9 +109,29 @@
|
|||
<div id="justText"></div>
|
||||
<div id="justHtml"></div>
|
||||
</div> -->
|
||||
<div class="col-md-12">
|
||||
<div class="heading">Personalization tags</div>
|
||||
<p class="desc">The following tags can be used on both Plain text or HTML. Add these tags to
|
||||
customize the email template, these tags will be replaced with the associated contact details
|
||||
while the mails are sent.</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Name: </strong>
|
||||
<code>{% templatetag openvariable %} name {% templatetag closevariable %}</code>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Email: </strong>
|
||||
<code>{% templatetag openvariable %} email {% templatetag closevariable %}</code>
|
||||
</li>
|
||||
<!-- <li>
|
||||
<strong>Two digit day of the month (eg. 01 to 31):</strong>
|
||||
<code>[currentdaynumber]</code>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-12 text-center">
|
||||
<button type="submit" class="primary_btn mar_btm save_btn">Send</button>
|
||||
<a href="{% url 'accounts:create_mail' account_id %}" class="primary_btn">Cancel</a>
|
||||
<a href="#" class="primary_btn">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -71,64 +147,121 @@
|
|||
{% block js_block %}
|
||||
<script src="https://malsup.github.com/jquery.form.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.6/js/standalone/selectize.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
|
||||
<!-- <script src="https://malsup.github.com/jquery.form.js"></script> -->
|
||||
<script src="{% static 'js/ajaxForm.js' %}"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/tempusdominus-bootstrap-4/5.0.0-alpha14/js/tempusdominus-bootstrap-4.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
// $('[name="schedule_later"]').on('change', function (e) {
|
||||
// if ($(this).is(":checked")) {
|
||||
// $('.timezone-div').show();
|
||||
// } else {
|
||||
// $('.timezone-div').hide();
|
||||
// }
|
||||
// console.log($(this))
|
||||
// console.log($(this).is(':checked'))
|
||||
// })
|
||||
$(".timezone-select").select2({
|
||||
placeholder: "Select a Timezone",
|
||||
});
|
||||
|
||||
$('.scheduled_later').on('change', function () {
|
||||
// console.log('changed')
|
||||
if ($('.check_delete:checked').length > 1) {
|
||||
$('.timezone-div').show();
|
||||
}
|
||||
else {
|
||||
$('.timezone-div').hide();
|
||||
}
|
||||
if ($(this).is(":checked")) {
|
||||
$('.timezone-div').show();
|
||||
} else {
|
||||
$('.timezone-div').hide();
|
||||
}
|
||||
console.log($(this))
|
||||
console.log($(this).is(':checked'))
|
||||
})
|
||||
|
||||
$.fn.datetimepicker.Constructor.Default = $.extend({}, $.fn.datetimepicker.Constructor.Default, {
|
||||
icons: {
|
||||
time: 'fa fa-clock',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-arrow-up',
|
||||
down: 'fa fa-arrow-down',
|
||||
previous: 'fa fa-chevron-left',
|
||||
next: 'fa fa-chevron-right',
|
||||
today: 'far fa-calendar-check-o',
|
||||
clear: 'fa fa-trash',
|
||||
close: 'fa fa-times'
|
||||
}
|
||||
});
|
||||
$('#datetimepicker1').datetimepicker({
|
||||
format: "YYYY-MM-DD HH:mm"
|
||||
});
|
||||
|
||||
// $("#id_contacts").select2();
|
||||
var REGEX_EMAIL = '([a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@' +
|
||||
'(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)';
|
||||
'(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)';
|
||||
|
||||
$('#_id_recipients').selectize({
|
||||
persist: false,
|
||||
maxItems: null,
|
||||
valueField: 'email',
|
||||
labelField: 'name',
|
||||
searchField: ['name', 'email'],
|
||||
// options: [
|
||||
// {email: 'brian@thirdroute.com', name: 'Brian Reavis'},
|
||||
// {email: 'nikola@tesla.com', name: 'Nikola Tesla'},
|
||||
// {email: 'someone@gmail.com'}
|
||||
// ],
|
||||
options: {{contacts_list|safe}},
|
||||
persist: false,
|
||||
maxItems: null,
|
||||
valueField: 'email',
|
||||
labelField: 'name',
|
||||
searchField: ['name', 'email'],
|
||||
// options: [
|
||||
// {email: 'brian@thirdroute.com', name: 'Brian Reavis'},
|
||||
// {email: 'nikola@tesla.com', name: 'Nikola Tesla'},
|
||||
// {email: 'someone@gmail.com'}
|
||||
// ],
|
||||
options: {{ contacts_list| safe}},
|
||||
render: {
|
||||
item: function(item, escape) {
|
||||
return '<div>' +
|
||||
(item.name ? '<span class="name">' + escape(item.name) + '</span>' : '') +
|
||||
(item.email ? '<span class="email">' + escape(item.email) + '</span>' : '') +
|
||||
'</div>';
|
||||
item: function (item, escape) {
|
||||
return '<div>' +
|
||||
(item.name ? '<span class="name">' + escape(item.name) + '</span>' : '') +
|
||||
(item.email ? '<span class="email">' + escape(item.email) + '</span>' : '') +
|
||||
'</div>';
|
||||
},
|
||||
option: function (item, escape) {
|
||||
var label = item.name || item.email;
|
||||
var caption = item.name ? item.email : null;
|
||||
return '<div>' +
|
||||
'<span class="label">' + escape(label) + '</span>' +
|
||||
(caption ? '<span class="caption">' + escape(caption) + '</span>' : '') +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
option: function(item, escape) {
|
||||
var label = item.name || item.email;
|
||||
var caption = item.name ? item.email : null;
|
||||
return '<div>' +
|
||||
'<span class="label">' + escape(label) + '</span>' +
|
||||
(caption ? '<span class="caption">' + escape(caption) + '</span>' : '') +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
createFilter: function(input) {
|
||||
var match, regex;
|
||||
createFilter: function (input) {
|
||||
var match, regex;
|
||||
|
||||
// email@address.com
|
||||
regex = new RegExp('^' + REGEX_EMAIL + '$', 'i');
|
||||
match = input.match(regex);
|
||||
if (match) return !this.options.hasOwnProperty(match[0]);
|
||||
// email@address.com
|
||||
regex = new RegExp('^' + REGEX_EMAIL + '$', 'i');
|
||||
match = input.match(regex);
|
||||
if (match) return !this.options.hasOwnProperty(match[0]);
|
||||
|
||||
// name <email@address.com>
|
||||
regex = new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$' , 'i' ); match=input.match(regex); if (match) return
|
||||
!this.options.hasOwnProperty(match[2]); return false; }, create: function(input) { if ((new RegExp('^' +
|
||||
REGEX_EMAIL + '$' , 'i' )).test(input)) { return {email: input}; } var match=input.match(new RegExp('^([^<]*)\<'
|
||||
+ REGEX_EMAIL + '\>$' , 'i' )); if (match) { return { email : match[2], name : $.trim(match[1]) }; }
|
||||
alert('Invalid email address.'); return false; } });
|
||||
// name <email@address.com>
|
||||
regex = new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i'); match = input.match(regex); if (match) return
|
||||
!this.options.hasOwnProperty(match[2]); return false;
|
||||
}, create: function (input) {
|
||||
if ((new RegExp('^' +
|
||||
REGEX_EMAIL + '$', 'i')).test(input)) { return { email: input }; } var match = input.match(new RegExp('^([^<]*)\<'
|
||||
+ REGEX_EMAIL + '\>$', 'i')); if (match) { return { email: match[2], name: $.trim(match[1]) }; }
|
||||
alert('Invalid email address.'); return false;
|
||||
} });
|
||||
});
|
||||
var quill = new Quill('#editor-container', {
|
||||
modules: {
|
||||
toolbar: [
|
||||
[{ header: [1, 2, false] }],
|
||||
['bold', 'italic', 'underline'],
|
||||
['image', 'code-block']
|
||||
['code-block', 'link']
|
||||
]
|
||||
},
|
||||
placeholder: 'Compose an epic...',
|
||||
placeholder: 'Compose mail...',
|
||||
theme: 'snow' // or 'bubble'
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@
|
|||
.opacity_block {
|
||||
opacity: 0.2;
|
||||
}
|
||||
label {
|
||||
font-weight: 600;
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
{% endblock extralinks %}
|
||||
|
||||
|
|
@ -312,8 +316,8 @@
|
|||
<a href="{% url 'contacts:add_contact' %}?view_account={{account_record.id}}"><span
|
||||
class="fa fa-plus pull-right" style="color:black;"></span></a>
|
||||
{% if contacts|length > 0 %}
|
||||
<a href="{% url 'accounts:create_mail' account_record.id %}" style="color:black">
|
||||
<span class="pull-right mr-2">Send Mail</span>
|
||||
<!-- <a href="{% url 'accounts:create_mail' %}" style="color:black">
|
||||
<span class="pull-right mr-2">Send Mail</span> -->
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -472,7 +476,7 @@
|
|||
<div class="panel-heading-list card-title view-pad">
|
||||
Tasks
|
||||
<a href="{% url 'tasks:task_create' %}?view_account={{account_record.id}}"><span
|
||||
class="fa fa-plus pull-right" style="color:black;"></span></a>
|
||||
class="fa fa-plus pull-right" style="color:black;"></span></a>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-striped table-condensed">
|
||||
|
|
@ -568,6 +572,57 @@
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form method='GET' class="accountdetails_row">
|
||||
<div class="table_container_row row marl no-gutters">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="panel-heading-list card-title view-pad">
|
||||
Sent Mails
|
||||
<a href="#"><span></span></a>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-striped table-condensed">
|
||||
{% if emails|length > 0 %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%" style="text-align:center;">S.No</th>
|
||||
<th width="30%" style="text-align:center;">From mail</th>
|
||||
<th width="20%" style="text-align:center;">Contact</th>
|
||||
<th width="20%" style="text-align:center;">Subject</th>
|
||||
<th width="25%" style="text-align:center;">Created On </th>
|
||||
<th width="25%" style="text-align:center;">View </th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% endif %}
|
||||
<tbody>
|
||||
{% for email in emails %}
|
||||
<tr id="invoice_{{invoice.id}}" class="text-center">
|
||||
<td class="text-center" scope="row">{{ forloop.counter }}</td>
|
||||
<td class="text-center">{{email.from_email}}</td>
|
||||
<td class="text-center">{{email.recipients.count}}</td>
|
||||
<td class="text-center">{{email.message_subject}}</td>
|
||||
<td class="text-center">{{email.created_on}}</td>
|
||||
<td class="text-center">
|
||||
<a href="#" class="btn btn-primary get_email_info" style="color: white;" data-toggle="modal"
|
||||
data-target="#email_info_ajax" data-account="{{email.id}}">View</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% ifequal emails|length 0 %}
|
||||
<h6 class="text-center">No Emails Records Found</h6>
|
||||
{% endifequal %}
|
||||
</div>
|
||||
</div>
|
||||
<br clear="all">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div class="overview_form_block row marl justify-content-center" id="loading_spinner"
|
||||
style="z-index: 10; left: 0; right: 0; vertical-align: middle; position: absolute; margin-top: 15%;">
|
||||
|
|
@ -578,6 +633,56 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="modal fade bd-example-modal-lg" id="email_info_ajax" tabindex="-1" role="dialog"
|
||||
aria-labelledby="myLargeModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="" id="">
|
||||
<div class="" style="padding: 1em;">
|
||||
<div class="col-md-12">
|
||||
<div class="card" style="padding: 1em;">
|
||||
<div class="card-body" id="datashow" style="margin: 0; padding: 0;">
|
||||
<div class="row marl no-gutters">
|
||||
<div class="col-md-12">
|
||||
<div class="filter_col col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="contact_field_label" for="id_title" data-name="title">Subject</label>
|
||||
<div class="contact_field" id="mail_subject" data-name="name"> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter_col col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="contact_field_label" for="id_status" data-name="status">Body</label>
|
||||
<div class="contact_field" id="mail_body" data-name="status"> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter_col col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="contact_field_label" for="id_status" data-name="status">Contacts</label>
|
||||
<div class="contact_field" id="mail_contacts" data-name="status"> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter_col col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="contact_field_label" for="id_created_on" data-name="created_on">Created
|
||||
On</label>
|
||||
<div class="contact_field" id="mail_created_on" data-name="created_on">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ACCOUNTS VIEW ENDS -->
|
||||
{% endblock content %}
|
||||
{% block js_block %}
|
||||
|
|
@ -760,5 +865,52 @@
|
|||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<script>
|
||||
$('.get_email_info').click(function (e) {
|
||||
var email_account_id = $(this).attr('data-account')
|
||||
$.ajax({
|
||||
url: "{% url 'accounts:get_email_data_for_account' %}",
|
||||
type: "POST",
|
||||
dataType: 'json',
|
||||
data: { 'email_account_id': email_account_id, },
|
||||
success: function (data) {
|
||||
if (data.error) {
|
||||
alert('No Data found.')
|
||||
} else {
|
||||
|
||||
$('#mail_subject').html(data.subject);
|
||||
$('#mail_body').html(data.body);
|
||||
d = new Date(data.created_on)
|
||||
let options = {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
};
|
||||
d = d.toLocaleString('en-us', options);
|
||||
$('#mail_created_on').html(d);
|
||||
// $('#mail_emails').html(data.contacts);
|
||||
if (data.contacts.length >= 1) {
|
||||
var contacts = data.contacts.map((value) => `<p>
|
||||
${value.email}
|
||||
</p>`)
|
||||
$('#mail_contacts').html(contacts);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// $('#email_info_ajax').on('hidden.bs.modal', function () {
|
||||
// // $('#mail_subject').html('');
|
||||
// // $('#mail_body').html('');
|
||||
// // $('#mail_emails').html('');
|
||||
// // do something…
|
||||
// })
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock js_block %}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
from django.test import TestCase
|
||||
from cases.models import Case
|
||||
from accounts.models import Account, Tags
|
||||
from common.models import User, Comment, Attachments, Address
|
||||
from django.urls import reverse
|
||||
from leads.models import Lead
|
||||
from contacts.models import Contact
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from accounts.models import Account, Tags, Email
|
||||
from cases.models import Case
|
||||
from common.models import Address, Attachments, Comment, User
|
||||
from contacts.models import Contact
|
||||
from leads.models import Lead
|
||||
from teams.models import Teams
|
||||
|
||||
|
||||
|
|
@ -49,21 +51,11 @@ class AccountCreateTest(object):
|
|||
)
|
||||
self.client.login(email='johnAccount@example.com', password='password')
|
||||
self.lead = Lead.objects.create(title="LeadCreation",
|
||||
first_name="john lead",
|
||||
last_name="doe",
|
||||
email="johnLead@example.com",
|
||||
address_line="",
|
||||
street="street name",
|
||||
city="city name",
|
||||
state="state",
|
||||
postcode="5079",
|
||||
country="IN",
|
||||
website="www.example.com",
|
||||
status="assigned",
|
||||
source="Call",
|
||||
opportunity_amount="700",
|
||||
description="lead description",
|
||||
created_by=self.user)
|
||||
first_name="john lead",last_name="doe",email="johnLead@example.com",
|
||||
address_line="",street="street name",city="city name",
|
||||
state="state",postcode="5079",country="IN",
|
||||
website="www.example.com",status="assigned",source="Call",
|
||||
opportunity_amount="700",description="lead description",created_by=self.user)
|
||||
self.lead.assigned_to.add(self.user)
|
||||
self.address = Address.objects.create(
|
||||
street="street number",
|
||||
|
|
@ -346,25 +338,25 @@ class TestCreateLeadPostView(AccountCreateTest, TestCase):
|
|||
upload_file = open('static/images/user.png', 'rb')
|
||||
response = self.client.post(reverse(
|
||||
'accounts:new_account'), {"name": "janeLead",
|
||||
"email": "janeLead@example.com",
|
||||
"phone": "+911234567891",
|
||||
"billing_address_line": "address line",
|
||||
"billing_street": "street name",
|
||||
"billing_city": "city name",
|
||||
"billing_state": "usa",
|
||||
"billing_postcode": "1234",
|
||||
"billing_country": "IN",
|
||||
"website": "www.example.com",
|
||||
"created_by": self.user,
|
||||
"status": "open",
|
||||
"industry": "SOFTWARE",
|
||||
"description": "Test description",
|
||||
"lead": str(self.lead.id),
|
||||
'contacts': str(self.contact.id),
|
||||
'tags': 'tag1',
|
||||
'account_attachment': SimpleUploadedFile(
|
||||
upload_file.name, upload_file.read())
|
||||
},
|
||||
"email": "janeLead@example.com",
|
||||
"phone": "+911234567891",
|
||||
"billing_address_line": "address line",
|
||||
"billing_street": "street name",
|
||||
"billing_city": "city name",
|
||||
"billing_state": "usa",
|
||||
"billing_postcode": "1234",
|
||||
"billing_country": "IN",
|
||||
"website": "www.example.com",
|
||||
"created_by": self.user,
|
||||
"status": "open",
|
||||
"industry": "SOFTWARE",
|
||||
"description": "Test description",
|
||||
"lead": str(self.lead.id),
|
||||
'contacts': str(self.contact.id),
|
||||
'tags': 'tag1',
|
||||
'account_attachment': SimpleUploadedFile(
|
||||
upload_file.name, upload_file.read())
|
||||
},
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
|
@ -401,9 +393,90 @@ class test_account_forms(AccountCreateTest, TestCase):
|
|||
response = self.client.get(reverse('accounts:new_account'))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.get(reverse('accounts:create_mail', args=(self.account.id,)))
|
||||
response = self.client.get(reverse('accounts:create_mail') + '?account_id={}'.format(self.account.id))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
data = {
|
||||
'account_id': self.account.id,
|
||||
'message_body': 'message body {{email}}',
|
||||
'message_subject':'message subject',
|
||||
'recipients':[self.contact.id, self.contact_user1.id],
|
||||
'scheduled_date_time': timezone.now(),
|
||||
}
|
||||
|
||||
response = self.client.post(reverse('accounts:create_mail'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
data = {
|
||||
'account_id': self.account.id,
|
||||
'message_body': 'message body {{email}',
|
||||
'message_subject':'message subject',
|
||||
'recipients':[self.contact.id, self.contact_user1.id],
|
||||
'scheduled_date_time': '',
|
||||
'scheduled_later': 'true',
|
||||
}
|
||||
|
||||
response = self.client.post(reverse('accounts:create_mail'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
data = {
|
||||
'account_id': self.account.id,
|
||||
'message_body': 'message body {{email}}}',
|
||||
'message_subject':'message subject',
|
||||
'recipients':[self.contact.id, self.contact_user1.id],
|
||||
'scheduled_date_time': timezone.now().strftime('%Y-%m-%d %H:%M'),
|
||||
}
|
||||
|
||||
response = self.client.post(reverse('accounts:create_mail'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
data = {
|
||||
'account_id': self.account.id,
|
||||
'message_body': 'message body {{email}}',
|
||||
'message_subject':'message subject',
|
||||
'recipients':[self.contact.id, self.contact_user1.id],
|
||||
'scheduled_date_time': timezone.now().strftime('%Y-%m-%d %H:%M'),
|
||||
'scheduled_later':'true',
|
||||
}
|
||||
response = self.client.post(reverse('accounts:create_mail'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
data = {
|
||||
'account_id': self.account.id,
|
||||
'message_body': 'message body {{email}}',
|
||||
'message_subject':'message subject',
|
||||
'recipients':[self.contact.id, self.contact_user1.id],
|
||||
'from_email': 'jane@doe.com',
|
||||
'timezone':'UTC',
|
||||
}
|
||||
response = self.client.post(reverse('accounts:create_mail'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
data = {
|
||||
'account_id': 0,
|
||||
'message_body': 'message body {{email}}',
|
||||
'message_subject':'message subject',
|
||||
'recipients':[self.contact.id, self.contact_user1.id],
|
||||
'from_email': 'jane@doe.com',
|
||||
'timezone':'UTC',
|
||||
}
|
||||
response = self.client.post(reverse('accounts:create_mail'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
data = {
|
||||
'account_id': self.account.id,
|
||||
'message_body': 'message body {{email}}',
|
||||
'message_subject':'message subject',
|
||||
'recipients':[self.contact.id, self.contact_user1.id],
|
||||
'from_email': 'jane@doe.com',
|
||||
'timezone':'UTC',
|
||||
'scheduled_later': 'true',
|
||||
'scheduled_date_time': timezone.now().strftime('%Y-%m-%d %H:%M'),
|
||||
}
|
||||
response = self.client.post(reverse('accounts:create_mail'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
|
||||
class test_account_models(AccountCreateTest, TestCase):
|
||||
|
||||
def test_account_model(self):
|
||||
|
|
@ -545,3 +618,186 @@ class test_account_views_list(AccountCreateTest, TestCase):
|
|||
'teams': [self.team_account.id,]},
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.account_edit = Account.objects.create(
|
||||
name="account edit", email="johndoe@example.com", phone="123456789",
|
||||
billing_address_line="", billing_street="street name",
|
||||
billing_city="city name",
|
||||
billing_state="state", billing_postcode="1234",
|
||||
billing_country="US",
|
||||
website="www.example.como", created_by=self.user, status="open",
|
||||
industry="SOFTWARE", description="Testing")
|
||||
self.account_by_user = Account.objects.create(
|
||||
name="account edit", email="johndoe@example.com", phone="123456789",
|
||||
billing_address_line="", billing_street="street name",
|
||||
billing_city="city name",
|
||||
billing_state="state", billing_postcode="1234",
|
||||
billing_country="US",
|
||||
website="www.example.como", created_by=self.user, status="open",
|
||||
industry="SOFTWARE", description="Testing")
|
||||
upload_file = open('static/images/user.png', 'rb')
|
||||
response = self.client.post(reverse('accounts:edit_account', args=(self.account_edit.id,)), {
|
||||
'name': "account", 'email': "johndoe@example.com",
|
||||
'phone': "+91-123-456-7894",
|
||||
'billing_address_line': "address line",
|
||||
'billing_street': "billing street",
|
||||
'billing_city': "billing city",
|
||||
'billing_state': "state",
|
||||
'billing_postcode': "1234",
|
||||
'billing_country': "IN",
|
||||
'website': "www.example.com",
|
||||
'industry': "SOFTWARE", 'description': "Testing",
|
||||
'contacts':[self.contact_user1.id,],
|
||||
'tags': self.tag_name.name + ', another tag edit',
|
||||
'assigned_to' : [self.user.id, ],
|
||||
'teams': [self.team_account.id,],
|
||||
'savenewform': 'true',
|
||||
'account_attachment': SimpleUploadedFile(
|
||||
upload_file.name, upload_file.read())})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
response = self.client.post(reverse('accounts:edit_account', args=(self.account_edit.id,)), {
|
||||
'name': "account", 'email': "johndoe@example.com",
|
||||
'phone': "+91-123-456-7894",
|
||||
'billing_address_line': "address line",
|
||||
'billing_street': "billing street",
|
||||
'billing_city': "billing city",
|
||||
'billing_state': "state",
|
||||
'billing_postcode': "1234",
|
||||
'billing_country': "IN",
|
||||
'website': "www.example.com",
|
||||
'industry': "SOFTWARE", 'description': "Testing",
|
||||
'contacts':[self.contact_user1.id,],
|
||||
'tags': self.tag_name.name + ', another tag edit',
|
||||
'teams': [self.team_account.id,],
|
||||
'savenewform': 'true',},
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.post(reverse('accounts:edit_account', args=(self.account_edit.id,)), {
|
||||
'name': "account", 'email': "johndoe@example.com",
|
||||
'phone': "+91-123-456 ",
|
||||
'billing_address_line': "address line",
|
||||
'billing_street': "billing street",
|
||||
'billing_city': "billing city",
|
||||
'billing_state': "state",
|
||||
'billing_postcode': "1234",
|
||||
'billing_country': "IN",
|
||||
'website': "www.example.com",
|
||||
'industry': "SOFTWARE", 'description': "Testing",
|
||||
'contacts':[self.contact_user1.id,],
|
||||
'tags': self.tag_name.name + ', another tag edit',
|
||||
'teams': [self.team_account.id,],
|
||||
'savenewform': 'true',},
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeAccount@example.com', password='password')
|
||||
response = self.client.get(reverse('accounts:edit_account', args=(self.account_by_user.id,)), {})
|
||||
self.assertEqual(403, response.status_code)
|
||||
self.account_by_user1 = Account.objects.create(
|
||||
name="account edit", email="johndoe@example.com", phone="123456789",
|
||||
billing_address_line="", billing_street="street name",
|
||||
billing_city="city name",
|
||||
billing_state="state", billing_postcode="1234",
|
||||
billing_country="US",
|
||||
website="www.example.como", created_by=self.user1, status="open",
|
||||
industry="SOFTWARE", description="Testing")
|
||||
response = self.client.get(reverse('accounts:edit_account', args=(self.account_by_user1.id,)), {})
|
||||
self.assertEqual(200, response.status_code)
|
||||
response = self.client.get(reverse('accounts:remove_account', args=(self.account_by_user.id,)), {})
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
response = self.client.post(reverse('accounts:add_comment'), {'accountid':self.account_by_user.id})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.post(reverse('accounts:edit_comment'), {'commentid':self.comment.id})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='johnAccount@example.com', password='password')
|
||||
|
||||
response = self.client.post(reverse('accounts:edit_comment'), {'commentid':self.comment.id, 'comment':''})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeAccount@example.com', password='password')
|
||||
response = self.client.post(reverse('accounts:remove_comment'), {'comment_id':self.comment.id, 'comment':''})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='johnAccount@example.com', password='password')
|
||||
|
||||
response = self.client.post(reverse('accounts:add_attachment'), {'accountid':self.account.id, 'comment':''})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
email_str = Email.objects.create(message_subject='message subject', message_body='message body')
|
||||
self.assertEqual(str(email_str), 'message subject')
|
||||
|
||||
response = self.client.post(reverse('accounts:get_contacts_for_account'), {
|
||||
'account_id':self.account.id
|
||||
})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.account.contacts.add(self.contact.id, self.contact_user1.id)
|
||||
|
||||
response = self.client.post(reverse('accounts:get_contacts_for_account'), {
|
||||
'account_id':self.account.id
|
||||
})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.get(reverse('accounts:get_contacts_for_account'), {
|
||||
'account_id':self.account.id
|
||||
})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.post(reverse('accounts:get_email_data_for_account'), {
|
||||
'email_account_id':email_str.id
|
||||
})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.get(reverse('accounts:get_email_data_for_account'), {
|
||||
'email_account_id':email_str.id
|
||||
})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
|
||||
class TestAccountUserMentions(AccountCreateTest, TestCase):
|
||||
|
||||
def test_account_views(self):
|
||||
self.user_created_by = User.objects.create(
|
||||
first_name="jane",
|
||||
username='janeAccountCreatedBy',
|
||||
email='janeAccountCreatedBy@example.com',
|
||||
role="USER",
|
||||
has_sales_access=True)
|
||||
self.user_created_by.set_password('password')
|
||||
self.user_created_by.save()
|
||||
|
||||
self.user_assigned_to = User.objects.create(
|
||||
first_name="jane",
|
||||
username='janeAccountUserAssigned',
|
||||
email='janeAccountUserAssigned@example.com',
|
||||
role="USER",
|
||||
has_sales_access=True)
|
||||
self.user_assigned_to.set_password('password')
|
||||
self.user_assigned_to.save()
|
||||
|
||||
self.account = Account.objects.create(
|
||||
name="john doe acc created by", email="johndoe@example.com", phone="123456789",
|
||||
billing_address_line="", billing_street="street name",
|
||||
billing_city="city name",
|
||||
billing_state="state", billing_postcode="1234",
|
||||
billing_country="US",
|
||||
website="www.example.como", created_by=self.user_created_by, status="open",
|
||||
industry="SOFTWARE", description="Testing")
|
||||
|
||||
self.account.assigned_to.add(self.user_assigned_to.id)
|
||||
self.client.logout()
|
||||
self.client.login(email='janeAccountCreatedBy@example.com', password='password')
|
||||
response = self.client.get(reverse('accounts:view_account', args=(self.account.id,)))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeAccountUserAssigned@example.com', password='password')
|
||||
response = self.client.get(reverse('accounts:view_account', args=(self.account.id,)))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ from django.urls import path
|
|||
from accounts.views import (
|
||||
AccountsListView, CreateAccountView, AccountDetailView, AccountUpdateView,
|
||||
AccountDeleteView, AddCommentView, UpdateCommentView, DeleteCommentView,
|
||||
AddAttachmentView, DeleteAttachmentsView, create_mail, #get_account_details
|
||||
AddAttachmentView, DeleteAttachmentsView, create_mail, # get_account_details,
|
||||
get_contacts_for_account, get_email_data_for_account
|
||||
)
|
||||
|
||||
app_name = 'accounts'
|
||||
|
|
@ -23,8 +24,11 @@ urlpatterns = [
|
|||
name="add_attachment"),
|
||||
path('attachment/remove/', DeleteAttachmentsView.as_view(),
|
||||
name="remove_attachment"),
|
||||
path('create-mail/<int:account_id>/', create_mail,
|
||||
name="create_mail"),
|
||||
# path('get-account-details/<int:account_id>/', get_account_details, name="get_account_details"),
|
||||
path('create-mail', create_mail, name="create_mail"),
|
||||
path('get_contacts_for_account/', get_contacts_for_account,
|
||||
name="get_contacts_for_account"),
|
||||
path('get_email_data_for_account/', get_email_data_for_account,
|
||||
name="get_email_data_for_account"),
|
||||
# path('get-account-details/<int:account_id>/', get_account_details, name="get_account_details"),
|
||||
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,29 +1,35 @@
|
|||
import pytz
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.views.generic import (
|
||||
CreateView, UpdateView, DetailView, TemplateView, View, DeleteView, FormView)
|
||||
from accounts.forms import AccountForm, AccountCommentForm, \
|
||||
AccountAttachmentForm, EmailForm
|
||||
from accounts.models import Account, Tags, Email
|
||||
from common.models import User, Comment, Attachments
|
||||
from common.utils import INDCHOICES, COUNTRIES, \
|
||||
CURRENCY_CODES, CASE_TYPE, PRIORITY_CHOICE, STATUS_CHOICE
|
||||
from contacts.models import Contact
|
||||
from opportunity.models import Opportunity, STAGES, SOURCES
|
||||
from cases.models import Case
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from leads.models import Lead
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.views.generic import (CreateView, DeleteView, DetailView, FormView,
|
||||
TemplateView, UpdateView, View)
|
||||
|
||||
from accounts.forms import (AccountAttachmentForm, AccountCommentForm,
|
||||
AccountForm, EmailForm)
|
||||
from accounts.models import Account, Email, Tags
|
||||
from accounts.tasks import send_email, send_email_to_assigned_user
|
||||
from cases.models import Case
|
||||
from common.access_decorators_mixins import (MarketingAccessRequiredMixin,
|
||||
SalesAccessRequiredMixin,
|
||||
marketing_access_required,
|
||||
sales_access_required)
|
||||
from common.models import Attachments, Comment, User
|
||||
from common.tasks import send_email_user_mentions
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from common.access_decorators_mixins import (
|
||||
sales_access_required, marketing_access_required, SalesAccessRequiredMixin, MarketingAccessRequiredMixin)
|
||||
from common.utils import (CASE_TYPE, COUNTRIES, CURRENCY_CODES, INDCHOICES,
|
||||
PRIORITY_CHOICE, STATUS_CHOICE)
|
||||
from contacts.models import Contact
|
||||
from leads.models import Lead
|
||||
from opportunity.models import SOURCES, STAGES, Opportunity
|
||||
from teams.models import Teams
|
||||
|
||||
|
||||
class AccountsListView(SalesAccessRequiredMixin, LoginRequiredMixin, TemplateView):
|
||||
model = Account
|
||||
context_object_name = "accounts_list"
|
||||
|
|
@ -87,6 +93,10 @@ class AccountsListView(SalesAccessRequiredMixin, LoginRequiredMixin, TemplateVie
|
|||
if self.request.POST.get('tab_status'):
|
||||
tab_status = self.request.POST.get('tab_status')
|
||||
context['tab_status'] = tab_status
|
||||
TIMEZONE_CHOICES = [(tz, tz) for tz in pytz.common_timezones]
|
||||
context["timezones"] = TIMEZONE_CHOICES
|
||||
context["settings_timezone"] = settings.TIME_ZONE
|
||||
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
|
@ -195,7 +205,7 @@ class CreateAccountView(SalesAccessRequiredMixin, LoginRequiredMixin, CreateView
|
|||
context["industries"] = INDCHOICES
|
||||
context["countries"] = COUNTRIES
|
||||
# context["contact_count"] = Contact.objects.count()
|
||||
if self.request.user.role == 'ADMIN':
|
||||
if self.request.user.role == 'ADMIN' or self.request.user.is_superuser:
|
||||
context["leads"] = Lead.objects.exclude(
|
||||
status__in=['converted', 'closed'])
|
||||
context["contacts"] = Contact.objects.all()
|
||||
|
|
@ -259,6 +269,7 @@ class AccountDetailView(SalesAccessRequiredMixin, LoginRequiredMixin, DetailView
|
|||
'comment_permission': comment_permission,
|
||||
'tasks':account_record.accounts_tasks.all(),
|
||||
'invoices':account_record.accounts_invoices.all(),
|
||||
'emails':account_record.sent_email.all(),
|
||||
'users_mention': users_mention,
|
||||
})
|
||||
return context
|
||||
|
|
@ -552,32 +563,75 @@ class DeleteAttachmentsView(LoginRequiredMixin, View):
|
|||
return JsonResponse(data)
|
||||
|
||||
@login_required
|
||||
def create_mail(request, account_id):
|
||||
|
||||
def create_mail(request):
|
||||
if request.method == 'GET':
|
||||
account = get_object_or_404(Account, pk=account_id)
|
||||
account = get_object_or_404(Account, pk=request.GET.get('account_id'))
|
||||
contacts_list = list(account.contacts.all().values('email'))
|
||||
email_form = EmailForm()
|
||||
return render(request, 'create_mail_accounts.html', {'account_id': account_id,
|
||||
'contacts_list': contacts_list, 'email_form': email_form})
|
||||
TIMEZONE_CHOICES = [(tz, tz) for tz in pytz.common_timezones]
|
||||
email_form = EmailForm(account=account)
|
||||
return render(request, 'create_mail_accounts.html', {'account_id': request.GET.get('account_id'),
|
||||
'contacts_list': contacts_list, 'email_form': email_form,
|
||||
"timezones": TIMEZONE_CHOICES, "settings_timezone": settings.TIME_ZONE})
|
||||
|
||||
if request.method == 'POST':
|
||||
form = EmailForm(request.POST)
|
||||
if form.is_valid():
|
||||
send_email.delay(form.data.get('message_subject'), form.data.get('message_body'),
|
||||
from_email=account_id, recipients=form.data.get('recipients').split(','))
|
||||
account = Account.objects.filter(id=request.POST.get('account_id')).first()
|
||||
if account:
|
||||
form = EmailForm(request.POST, account=account)
|
||||
if form.is_valid():
|
||||
contacts = form.data.get('recipients').split(',')
|
||||
email_obj = Email.objects.create(
|
||||
from_account=account,
|
||||
message_body=form.cleaned_data.get('message_body'),
|
||||
message_subject=form.cleaned_data.get('message_subject'),
|
||||
from_email=form.cleaned_data.get('from_email'),
|
||||
timezone=form.cleaned_data.get('timezone'),
|
||||
)
|
||||
email_obj.recipients.add(*contacts)
|
||||
if request.POST.get('scheduled_later') != 'true':
|
||||
send_email.delay(email_obj.id)
|
||||
else:
|
||||
email_obj.scheduled_later = True
|
||||
email_obj.scheduled_date_time = form.cleaned_data.get('scheduled_date_time')
|
||||
email_obj.save()
|
||||
|
||||
return JsonResponse({'error': False})
|
||||
return JsonResponse({'error': False})
|
||||
else:
|
||||
return JsonResponse({'error': True, 'errors': form.errors})
|
||||
else:
|
||||
return JsonResponse({'error': True, 'errors': form.errors})
|
||||
|
||||
return JsonResponse({'error':True, 'message': "Account does not exist."})
|
||||
|
||||
# @login_required
|
||||
# def get_account_details(request, account_id):
|
||||
# from django.core import serializers
|
||||
# import json
|
||||
# fields = ['name', 'email', 'phone', 'industry', 'billing_address_line', 'billing_street', 'billing_city',
|
||||
# 'billing_state', 'billing_postcode', 'billing_country', 'website', 'description',
|
||||
# 'created_by__email', 'created_on', 'tags__name', 'status', 'contacts__email', 'assigned_to__email']
|
||||
# data = serializers.serialize('json', Account.objects.filter(id=account_id), fields=fields)
|
||||
# return JsonResponse({'data': json.loads(data)[0]})
|
||||
# @sales_access_required
|
||||
# def create_mail(request, pk):
|
||||
# account_obj = get_object_or_404(Account, pk=pk)
|
||||
# if request.method == 'GET':
|
||||
# context = {}
|
||||
# context['account_obj'] = account_obj
|
||||
# return render(request, 'create_email_for_contacts.html')
|
||||
|
||||
|
||||
def get_contacts_for_account(request):
|
||||
account_id = request.POST.get('account_id')
|
||||
if account_id:
|
||||
account_obj = Account.objects.filter(pk=account_id).first()
|
||||
if account_obj:
|
||||
if account_obj.contacts.all():
|
||||
data = list(account_obj.contacts.values('id', 'email'))
|
||||
import json
|
||||
return HttpResponse(json.dumps(data))
|
||||
return JsonResponse({})
|
||||
return JsonResponse({})
|
||||
|
||||
|
||||
def get_email_data_for_account(request):
|
||||
email_obj = Email.objects.filter(id=request.POST.get('email_account_id')).first()
|
||||
if email_obj:
|
||||
ctx = {}
|
||||
ctx['subject'] = email_obj.message_subject
|
||||
ctx['body'] = email_obj.message_body
|
||||
ctx['created_on'] = email_obj.created_on
|
||||
ctx['contacts'] = list(email_obj.recipients.values('email'))
|
||||
ctx['error'] = False
|
||||
return JsonResponse(ctx)
|
||||
else:
|
||||
return JsonResponse({'error': True, 'data' : 'No emails found.'})
|
||||
203
cases/tests.py
203
cases/tests.py
|
|
@ -1,15 +1,16 @@
|
|||
from django.test import TestCase
|
||||
from cases.models import Case
|
||||
from contacts.models import Contact
|
||||
from accounts.models import Account
|
||||
from common.models import Address, Comment, Attachments
|
||||
from common.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from planner.models import Event, Reminder
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from accounts.models import Account
|
||||
from cases.models import Case
|
||||
from common.models import Address, Attachments, Comment, User
|
||||
from contacts.models import Contact
|
||||
from planner.models import Event, Reminder
|
||||
from teams.models import Teams
|
||||
|
||||
|
||||
class CaseCreation(object):
|
||||
|
||||
|
|
@ -436,6 +437,192 @@ class TestCasesListViewForUser(CaseCreation, TestCase):
|
|||
response = self.client.post(reverse('cases:close_case'), {'case_id':self.case.id})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
self.client.login(email='johnDoeCase@example.com', password='password')
|
||||
|
||||
self.team_case = Teams.objects.create(name='dev team case')
|
||||
self.team_case.users.add(self.user1.id)
|
||||
|
||||
response = self.client.post(reverse('cases:add_case') + '?view_account={}'.format(self.account.id), {
|
||||
'name':'team case',
|
||||
'status':'New',
|
||||
'priority':'Low',
|
||||
'closed_on':'2019-03-14',
|
||||
'teams':self.team_case.id,
|
||||
'from_account':self.account.id
|
||||
})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.get(reverse('cases:add_case') + '?view_account={}'.format(self.account.id))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
|
||||
self.case_without_account = Case.objects.create(
|
||||
name="case_without_account", case_type="Problem", status="New",
|
||||
priority="Low", description="something",
|
||||
created_by=self.user, closed_on="2016-05-04")
|
||||
|
||||
response = self.client.get(reverse('cases:view_case', args=(self.case_without_account.id,)))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.account_user = Account.objects.create(
|
||||
name="account name",
|
||||
email="account@example.com", phone="12345",
|
||||
billing_address_line="",
|
||||
billing_street="street",
|
||||
billing_city="city",
|
||||
billing_postcode="1234",
|
||||
billing_country='US',
|
||||
website="www.example.com", description="account",
|
||||
created_by=self.usermp)
|
||||
self.account_user.assigned_to.add(self.usermp.id)
|
||||
|
||||
self.case_user = Case.objects.create(
|
||||
name="case user", case_type="Problem", status="New",
|
||||
priority="Low", description="something",
|
||||
account=self.account_user,
|
||||
created_by=self.usermp, closed_on="2016-05-04")
|
||||
|
||||
self.usermp.has_sales_access = True
|
||||
self.usermp.save()
|
||||
self.client.logout()
|
||||
self.client.login(username='joedoeCase7@user.com', password='password')
|
||||
response = self.client.get(reverse('cases:view_case', args=(self.case_user.id,)))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.case_user_1 = Case.objects.create(
|
||||
name="case user authorization", case_type="Problem", status="New",
|
||||
priority="Low", description="something",
|
||||
created_by=self.user, closed_on="2016-05-04")
|
||||
|
||||
self.case_user_1.assigned_to.add(self.user1, self.user)
|
||||
self.case_user_1.save()
|
||||
response = self.client.get(reverse('cases:view_case', args=(self.case_user_1.id,)))
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(username='joedoeCase7@user.com', password='password')
|
||||
response = self.client.get(reverse('cases:edit_case', args=(self.case_user.id,)))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.login(email='johnDoeCase@example.com', password='password')
|
||||
data = {
|
||||
'name':"case user", 'case_type':"Problem", 'status':"New",
|
||||
'priority':"Low", 'description':"something",
|
||||
'created_by':self.usermp, 'closed_on':"2016-05-04",
|
||||
'teams': self.team_case.id,
|
||||
'from_account':self.account.id,
|
||||
}
|
||||
response = self.client.post(reverse('cases:edit_case', args=(self.case_user.id,)), data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.get(reverse('cases:edit_case', args=(self.case_user.id,)) + '?view_account={}'.format(self.account.id))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(username='joedoeCase7@user.com', password='password')
|
||||
response = self.client.get(reverse('cases:edit_case', args=(self.case.id,)))
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
response = self.client.get(reverse('cases:remove_case', args=(self.case_user_1.id,)) + '?view_account={}'.format(self.account.id))
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='johnDoeCase@example.com', password='password')
|
||||
response = self.client.get(reverse('cases:remove_case', args=(self.case_user.id,)) + '?view_account={}'.format(self.account.id))
|
||||
self.assertEqual(302, response.status_code)
|
||||
|
||||
self.case_user = Case.objects.create(
|
||||
name="case user", case_type="Problem", status="New",
|
||||
priority="Low", description="something",
|
||||
account=self.account_user,
|
||||
created_by=self.usermp, closed_on="2016-05-04")
|
||||
self.client.logout()
|
||||
self.client.login(email='johnDoeCase@example.com', password='password')
|
||||
response = self.client.post(reverse('cases:remove_case', args=(self.case_user.id,)) + '?view_account={}'.format(self.account.id),
|
||||
{'case_id': self.case_user.id}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.case_user = Case.objects.create(
|
||||
name="case user", case_type="Problem", status="New",
|
||||
priority="Low", description="something",
|
||||
account=self.account_user,
|
||||
created_by=self.usermp, closed_on="2016-05-04")
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(username='joedoeCase7@user.com', password='password')
|
||||
response = self.client.post(reverse('cases:remove_case', args=(self.case.id,)) + '?view_account={}'.format(self.account.id),
|
||||
{'case_id': self.case.id})
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
data = {
|
||||
'comment':'comment update',
|
||||
'caseid': self.case.id,
|
||||
'commentid': self.comment.id
|
||||
}
|
||||
|
||||
response = self.client.post(reverse('cases:edit_comment'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.post(reverse('cases:close_case') + '?view_account={}'.format(self.account.id),
|
||||
{'case_id': self.case.id})
|
||||
self.assertEqual(403, response.status_code)
|
||||
data = {
|
||||
'caseid': self.case.id,
|
||||
'comment': ''
|
||||
}
|
||||
response = self.client.post(reverse('cases:add_comment'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='johnDoeCase@example.com', password='password')
|
||||
response = self.client.get(reverse('cases:select_contacts') + '?account={}'.format(self.account.id))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
data = {
|
||||
'caseid': self.case.id,
|
||||
'comment': ''
|
||||
}
|
||||
response = self.client.post(reverse('cases:add_comment'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
|
||||
data = {
|
||||
'comment':'comment update',
|
||||
'caseid': self.case.id,
|
||||
'commentid': self.comment.id
|
||||
}
|
||||
|
||||
response = self.client.post(reverse('cases:edit_comment'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
data = {
|
||||
'comment':'',
|
||||
'caseid': self.case.id,
|
||||
'commentid': self.comment.id
|
||||
}
|
||||
|
||||
response = self.client.post(reverse('cases:edit_comment'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(username='joedoeCase7@user.com', password='password')
|
||||
data = {
|
||||
'comment_id': self.comment.id
|
||||
}
|
||||
response = self.client.post(reverse('cases:remove_comment'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='johnDoeCase@example.com', password='password')
|
||||
data = {
|
||||
'caseid': self.case.id,
|
||||
'attachment':'',
|
||||
}
|
||||
response = self.client.post(reverse('cases:add_attachment'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
|
||||
# def test_comment_add_error(self):
|
||||
# self.client.login(email='mpmp@micropyramid.com', password='mp')
|
||||
# response = self.client.post(reverse('cases:add_comment'), {})
|
||||
|
|
@ -448,5 +635,3 @@ class TestCasesListViewForUser(CaseCreation, TestCase):
|
|||
# self.client.login(email='mpmp1@micropyramid.com', password='mp')
|
||||
# response = self.client.post(reverse('cases:edit_comment'), {})
|
||||
# self.assertJSONEqual(force_text(response.content), {'error': "You don't have permission to edit this comment."})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -361,10 +361,10 @@ class RemoveCaseView(SalesAccessRequiredMixin, LoginRequiredMixin, View):
|
|||
self.request.user == self.object.created_by
|
||||
):
|
||||
self.object.delete()
|
||||
if request.GET.get('view_account'):
|
||||
account = request.GET.get('view_account')
|
||||
return redirect("accounts:view_account", pk=account)
|
||||
return redirect("cases:list")
|
||||
if request.GET.get('view_account'):
|
||||
account = request.GET.get('view_account')
|
||||
return redirect("accounts:view_account", pk=account)
|
||||
return redirect("cases:list")
|
||||
raise PermissionDenied
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<!-- filter_row starts here -->
|
||||
<div class="row marl">
|
||||
<div class="col-lg-12 text-right">
|
||||
<span class="d-inline"><a href="{% url 'marketing:list_all_emails_for_campaigns' %}" class="btn primary_btn">Contacts</a></span>
|
||||
<span class="d-inline"><a href="{% url 'common:add_api_settings'%}" class="btn primary_btn"><i class="fa fa-plus"></i> Add New Setting</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -430,6 +430,7 @@ class PasswordResetView(PasswordResetView):
|
|||
form_class = PasswordResetEmailForm
|
||||
email_template_name = 'registration/password_reset_email.html'
|
||||
html_email_template_name = 'registration/password_reset_email.html'
|
||||
from_email = 'no-reply@' + settings.EMAIL_HOST_USER + '.com'
|
||||
|
||||
|
||||
@login_required
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
from django.test import TestCase
|
||||
from contacts.models import Contact
|
||||
from common.models import Address, User, Comment, Attachments
|
||||
from cases.models import Case
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from contacts.forms import ContactAttachmentForm
|
||||
from accounts.models import Account
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from accounts.models import Account
|
||||
from cases.models import Case
|
||||
from common.models import Address, Attachments, Comment, User
|
||||
from contacts.forms import ContactAttachmentForm
|
||||
from contacts.models import Contact
|
||||
from teams.models import Teams
|
||||
|
||||
|
||||
class ContactObjectsCreation(object):
|
||||
|
||||
|
|
@ -56,7 +57,10 @@ class ContactObjectsCreation(object):
|
|||
first_name="janeUser@example.com",
|
||||
username='janeUserContact',
|
||||
email="janeUser@example.com",
|
||||
role="USER")
|
||||
role="USER",
|
||||
has_sales_access=True)
|
||||
self.user_contacts_mp.set_password('password')
|
||||
self.user_contacts_mp.save()
|
||||
|
||||
self.client.login(username='johnContact@example.com', password='password')
|
||||
|
||||
|
|
@ -344,7 +348,9 @@ class TestContactCreateContact(ContactObjectsCreation, TestCase):
|
|||
first_name="janeDoe@example.com",
|
||||
username='janeDoe',
|
||||
email="janeDoe@example.com",
|
||||
role="USER")
|
||||
role="USER",
|
||||
is_active=True,
|
||||
has_sales_access=True)
|
||||
self.usermp1.set_password('password')
|
||||
self.usermp1.save()
|
||||
self.contact = Contact.objects.create(
|
||||
|
|
@ -390,3 +396,145 @@ class TestContactCreateContact(ContactObjectsCreation, TestCase):
|
|||
'/contacts/attachment/remove/', {'attachment_id': self.attachment.id})
|
||||
self.assertJSONEqual(force_text(response.content), {
|
||||
'error': "You don't have permission to delete this attachment."})
|
||||
|
||||
|
||||
class TestContactViews(ContactObjectsCreation, TestCase):
|
||||
|
||||
def test_create_contact(self):
|
||||
self.client.logout()
|
||||
self.client.login(username='janeUser@example.com', password='password')
|
||||
response = self.client.get(reverse('contacts:list'),{})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(username='johnContact@example.com', password='password')
|
||||
self.account_by_user = Account.objects.create(
|
||||
name="account edit", email="johndoe@example.com", phone="123456789",
|
||||
billing_address_line="", billing_street="street name",
|
||||
billing_city="city name",
|
||||
billing_state="state", billing_postcode="1234",
|
||||
billing_country="US",
|
||||
website="www.example.como", created_by=self.user, status="open",
|
||||
industry="SOFTWARE", description="Testing")
|
||||
self.teams_contacts = Teams.objects.create(name='teams contacts')
|
||||
self.teams_contacts.users.add(self.user)
|
||||
data = {
|
||||
'first_name':'first name',
|
||||
'last_name':'last name',
|
||||
'phone':'+91-123-456-7854',
|
||||
'email':'example@user.com',
|
||||
'teams':[self.teams_contacts.id,]
|
||||
}
|
||||
response = self.client.post(reverse('contacts:add_contact') + '?view_account={}&address_form='.format(self.account_by_user.id), data)
|
||||
self.assertEqual(302, response.status_code)
|
||||
|
||||
data = {
|
||||
'first_name':'first name',
|
||||
'last_name':'last name',
|
||||
'phone':'+91-123-456-7858',
|
||||
'email':'example@mail.com',
|
||||
'teams':[self.teams_contacts.id,]
|
||||
}
|
||||
response = self.client.post(reverse('contacts:add_contact') + '?view_account={}'.format(self.account_by_user.id), data,
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
data = {
|
||||
'first_name':'first name',
|
||||
'last_name':'last name',
|
||||
'phone':'+91-123-456-7854',
|
||||
'email':'example@user',
|
||||
'teams':[self.teams_contacts.id,]
|
||||
}
|
||||
response = self.client.post(reverse('contacts:add_contact') + '?view_account={}'.format(self.account_by_user.id), data,
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(200, response.status_code)
|
||||
response = self.client.get(reverse('contacts:add_contact') + '?address_form=')
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.post(reverse('contacts:edit_contact', args=(self.contact.id,)),{
|
||||
'first_name':'contact',
|
||||
'last_name':'contact@example.com',
|
||||
'phone':'+91-123-456-7856',
|
||||
'email':'contact@example.com',
|
||||
'teams':[self.teams_contacts.id,]
|
||||
})
|
||||
self.assertEqual(302, response.status_code)
|
||||
|
||||
self.user_contacts_mp = User.objects.create(
|
||||
first_name="joeUser@contact.com",
|
||||
username='joeUser@contact.com',
|
||||
email="joeUser@contact.com",
|
||||
role="USER",
|
||||
has_sales_access=True)
|
||||
self.user_contacts_mp.set_password('password')
|
||||
self.user_contacts_mp.save()
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(username='joeUser@contact.com', password='password')
|
||||
response = self.client.get(reverse('contacts:view_contact', args=(self.contact.id,)))
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(username='janeUser@example.com', password='password')
|
||||
response = self.client.get(reverse('contacts:view_contact', args=(self.contact.id,)))
|
||||
# self.assertEqual(403, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(username='johnContact@example.com', password='password')
|
||||
self.teams_contacts.users.add(self.user)
|
||||
data = {
|
||||
'first_name':'contact',
|
||||
'last_name':'contact',
|
||||
'phone':'+91-123-456-7852',
|
||||
'email':'contact@example.com',
|
||||
'teams':[self.teams_contacts.id,]
|
||||
}
|
||||
response = self.client.post(reverse('contacts:edit_contact', args=(self.contact.id,)) + '?from_account={}'.format(self.account_by_user.id), data)
|
||||
self.assertEqual(302, response.status_code)
|
||||
response = self.client.post(reverse('contacts:edit_contact', args=(self.contact.id,)) + '?from_account={}&address_form='.format(self.account_by_user.id), data,
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
data = {
|
||||
'first_name':'contact',
|
||||
'last_name':'contact',
|
||||
'phone':'+91-123-456',
|
||||
'email':'contact@example',
|
||||
'teams':[self.teams_contacts.id,]
|
||||
}
|
||||
response = self.client.post(reverse('contacts:edit_contact', args=(self.contact.id,)) + '?from_account={}'.format(self.account_by_user.id), data,
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.post(reverse('contacts:edit_contact', args=(self.contact.id,)) + '?from_account={}'.format(self.account_by_user.id), data,)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(username='johnContact@example.com', password='password')
|
||||
self.contact_delete = Contact.objects.create(
|
||||
first_name="contact",
|
||||
email="contact_delete@example.com",
|
||||
phone="12345",
|
||||
address=self.address,
|
||||
description="contact",
|
||||
created_by=self.user)
|
||||
response = self.client.post(reverse('contacts:remove_contact', args=(self.contact_delete.id,)), {'pk': self.contact_delete.id},
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.contact_edit = Contact.objects.create(
|
||||
first_name="contact",
|
||||
email="contact_edit@example.com",
|
||||
phone="12345",
|
||||
description="contact",
|
||||
created_by=self.user_contacts_mp)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(username='joeUser@contact.com', password='password')
|
||||
response = self.client.get(reverse('contacts:view_contact', args=(self.contact_edit.id,)), {'pk': self.contact_edit.id},
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(200, response.status_code)
|
||||
response = self.client.get(reverse('contacts:add_contact'))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
|
@ -89,8 +89,6 @@ class CreateContactView(SalesAccessRequiredMixin, LoginRequiredMixin, CreateView
|
|||
def dispatch(self, request, *args, **kwargs):
|
||||
if self.request.user.role == 'ADMIN' or self.request.user.is_superuser:
|
||||
self.users = User.objects.filter(is_active=True).order_by('email')
|
||||
elif request.user.google.all():
|
||||
self.users = []
|
||||
else:
|
||||
self.users = User.objects.filter(role='ADMIN').order_by('email')
|
||||
return super(CreateContactView, self).dispatch(
|
||||
|
|
@ -251,8 +249,6 @@ class UpdateContactView(SalesAccessRequiredMixin, LoginRequiredMixin, UpdateView
|
|||
def dispatch(self, request, *args, **kwargs):
|
||||
if self.request.user.role == 'ADMIN' or self.request.user.is_superuser:
|
||||
self.users = User.objects.filter(is_active=True).order_by('email')
|
||||
elif request.user.google.all():
|
||||
self.users = []
|
||||
else:
|
||||
self.users = User.objects.filter(role='ADMIN').order_by('email')
|
||||
return super(UpdateContactView, self).dispatch(
|
||||
|
|
|
|||
|
|
@ -237,6 +237,10 @@ CELERY_BEAT_SCHEDULE = {
|
|||
"runs-scheduled-campaigns-for-every-one-hour": {
|
||||
"task": "marketing.tasks.send_scheduled_campaigns",
|
||||
"schedule": crontab(hour='*/1')
|
||||
},
|
||||
"runs-scheduled-emails-for-accounts-every-five-minutes": {
|
||||
"task": "accounts.tasks.send_scheduled_emails",
|
||||
"schedule": crontab(minute='*/1')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -138,60 +138,6 @@ def csv_doc_validate(document):
|
|||
return {"error": False, "validated_rows": temp_row, "invalid_rows": invalid_row, "headers":csv_headers,
|
||||
"failed_leads_csv": failed_leads_csv}
|
||||
|
||||
|
||||
def get_validated_rows(wb, sheet_name, validated_rows, invalid_rows):
|
||||
# headers = ["first name", "last name", "email"]
|
||||
# required_headers = ["first name", "last name", "email"]
|
||||
headers = ["title"]
|
||||
required_headers = ["title"]
|
||||
work_sheet = wb.get_sheet_by_name(name=sheet_name)
|
||||
for y_index, row in enumerate(work_sheet.iter_rows()):
|
||||
if y_index == 0:
|
||||
missing_headers = set(required_headers) - \
|
||||
set([str(cell.value).lower() for cell in row])
|
||||
if missing_headers:
|
||||
missing_headers_str = ', '.join(missing_headers)
|
||||
message = "Missing headers: %s %s" % (
|
||||
missing_headers_str, sheet_name)
|
||||
return {"error": True, "message": message}
|
||||
continue
|
||||
elif not ''.join(str(cell.value) for cell in row):
|
||||
continue
|
||||
else:
|
||||
temp_row = []
|
||||
invalid_row = []
|
||||
for x_index, cell in enumerate(row):
|
||||
try:
|
||||
headers[x_index]
|
||||
except IndexError:
|
||||
continue
|
||||
if headers[x_index] in required_headers:
|
||||
if not cell.value:
|
||||
# message = 'Missing required \
|
||||
# value %s for row %s in sheet %s'\
|
||||
# % (headers[x_index], y_index + 1, sheet_name)
|
||||
# return {"error": True, "message": message}
|
||||
invalid_row.append(headers[x_index])
|
||||
temp_row.append(cell.value)
|
||||
if len(invalid_row) > 0:
|
||||
invalid_rows.append(temp_row)
|
||||
else:
|
||||
if len(temp_row) >= len(required_headers):
|
||||
validated_rows.append(temp_row)
|
||||
return validated_rows, invalid_rows
|
||||
|
||||
|
||||
def xls_doc_validate(document):
|
||||
wb = openpyxl.load_workbook(document)
|
||||
sheets = wb.get_sheet_names()
|
||||
validated_rows = []
|
||||
invalid_rows = []
|
||||
for sheet_name in sheets:
|
||||
validated_rows, invalid_rows = get_validated_rows(
|
||||
wb, sheet_name, validated_rows, invalid_rows)
|
||||
return {"error": False, "validated_rows": validated_rows, "invalid_rows": invalid_rows}
|
||||
|
||||
|
||||
def import_document_validator(document):
|
||||
try:
|
||||
# dialect = csv.Sniffer().sniff(document.read(1024).decode("ascii"))
|
||||
|
|
@ -199,11 +145,7 @@ def import_document_validator(document):
|
|||
return csv_doc_validate(document)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
try:
|
||||
return xls_doc_validate(document)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return {"error": True, "message": "Not a valid CSV/XLS file"}
|
||||
return {"error": True, "message": "Not a valid CSV file"}
|
||||
|
||||
|
||||
class LeadListForm(forms.Form):
|
||||
|
|
@ -212,12 +154,12 @@ class LeadListForm(forms.Form):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super(LeadListForm, self).__init__(*args, **kwargs)
|
||||
self.fields['leads_file'].widget.attrs.update({
|
||||
"accept": ".csv,.xls,.xlsx,.xlsm,.xlsb,.xml",
|
||||
"accept": ".csv",
|
||||
})
|
||||
self.fields['leads_file'].required = True
|
||||
if self.data.get('leads_file'):
|
||||
self.fields['leads_file'].widget.attrs.update({
|
||||
"accept": ".csv,.xls,.xlsx,.xlsm,.xlsb,.xml",
|
||||
"accept": ".csv",
|
||||
})
|
||||
|
||||
def clean_leads_file(self):
|
||||
|
|
|
|||
208
leads/tests.py
208
leads/tests.py
|
|
@ -1,14 +1,16 @@
|
|||
from django.test import TestCase, Client
|
||||
from cases.models import Case
|
||||
from leads.models import Lead
|
||||
from common.models import User, Comment, Attachments, APISettings
|
||||
from accounts.models import Account, Tags
|
||||
from leads.tasks import *
|
||||
from leads.forms import *
|
||||
from django.urls import reverse
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from accounts.models import Account, Tags
|
||||
from cases.models import Case
|
||||
from common.models import APISettings, Attachments, Comment, User
|
||||
from leads.forms import *
|
||||
from leads.models import Lead
|
||||
from leads.tasks import *
|
||||
from teams.models import Teams
|
||||
|
||||
|
||||
class TestLeadModel(object):
|
||||
|
||||
|
|
@ -32,7 +34,8 @@ class TestLeadModel(object):
|
|||
first_name="janeLead2",
|
||||
username='janeLead2',
|
||||
email='janeLead2@example.com',
|
||||
role="USER")
|
||||
role="USER",
|
||||
has_sales_access=True)
|
||||
self.user2.set_password('password')
|
||||
self.user2.save()
|
||||
|
||||
|
|
@ -40,7 +43,8 @@ class TestLeadModel(object):
|
|||
first_name="janeLead3",
|
||||
username='janeLead3',
|
||||
email='janeLead3@example.com',
|
||||
role="USER")
|
||||
role="USER",
|
||||
has_sales_access=True)
|
||||
self.user3.set_password('password')
|
||||
self.user3.save()
|
||||
|
||||
|
|
@ -82,6 +86,7 @@ class TestLeadModel(object):
|
|||
self.lead1 = Lead.objects.create(
|
||||
title="jane doe lead", created_by=self.user3)
|
||||
self.lead.assigned_to.add(self.user)
|
||||
self.lead.assigned_to.add(self.user2)
|
||||
self.lead.assigned_to.add(self.user3)
|
||||
self.case = Case.objects.create(
|
||||
name="john case", case_type="Problem",
|
||||
|
|
@ -513,7 +518,7 @@ class TestLeadDetailView1(TestLeadModel, TestCase):
|
|||
def test_lead_detail_view(self):
|
||||
self.client.login(username='janeLead2@example.com', password='password')
|
||||
resp = self.client.get(reverse('leads:view_lead', args=(self.lead.id,)))
|
||||
self.assertEqual(resp.status_code, 403)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.client.logout()
|
||||
|
||||
|
||||
|
|
@ -536,25 +541,170 @@ class TestCommentAddResponse(TestLeadModel, TestCase):
|
|||
self.assertJSONEqual(force_text(response.content), {"error": ["This field is required."]})
|
||||
|
||||
|
||||
# # class TestAddAttachmentView(TestLeadModel, TestCase):
|
||||
def test_file_upload_for_leads(self):
|
||||
self.client.logout()
|
||||
self.client.login(email='johnLead@example.com', password="password")
|
||||
|
||||
# # def test_convert_lead_view(self):
|
||||
# # response = self.client.post(reverse('leads:add_attachment'),{})
|
||||
# # self.assertEqual(response.status_code, 200)
|
||||
# # self.assertJSONEqual(
|
||||
# # str(response.content, encoding='utf8'),
|
||||
# # {'status': 'success'}
|
||||
# # )
|
||||
file_content = [
|
||||
'title,first name,last name,website,phone,email,address\n',
|
||||
'lead1,john,doe,www.example.com,911234567890,user1@email.com,address for lead1\n',
|
||||
'lead2,jane,doe,www.website.com,911234567891,user2@email.com,address for lead2\n',
|
||||
'lead3,joe,doe,www.test.com,911234567892,user3@email.com,address for lead3\n',
|
||||
'lead4,john,doe,www.sample.com,911234567893,user4@email.com,address for lead4\n',
|
||||
]
|
||||
file_content = bytes(''.join(file_content), 'utf-8')
|
||||
data = {'leads_file': SimpleUploadedFile('file name', file_content)}
|
||||
response = self.client.post(reverse('leads:upload_lead_csv_file'), data)
|
||||
self.assertEqual(201, response.status_code)
|
||||
|
||||
file_content_missing_headers = [
|
||||
',first name,last name,website,phone,,address\n',
|
||||
'lead1,john,doe,www.example.com,911234567890,user1@email.com,address for lead1\n',
|
||||
'lead2,jane,doe,www.website.com,911234567891,user2@email.com,address for lead2\n',
|
||||
'lead3,joe,doe,www.test.com,911234567892,user3@email.com,address for lead3\n',
|
||||
'lead4,john,doe,www.sample.com,911234567893,user4@email.com,address for lead4\n',
|
||||
]
|
||||
file_content_missing_headers = bytes(''.join(file_content_missing_headers), 'utf-8')
|
||||
data = {'leads_file': SimpleUploadedFile('file name', file_content_missing_headers)}
|
||||
response = self.client.post(reverse('leads:upload_lead_csv_file'), data)
|
||||
|
||||
# # class TestAddLeadViewFormError(TestLeadModel, TestCase):
|
||||
# for invalid lead titles
|
||||
file_content = [
|
||||
'title,first name,last name,website,phone,email,address\n',
|
||||
',john,doe,www.example.com,911234567890,user1@email.com,address for lead1\n',
|
||||
',jane,doe,www.website.com,911234567891,user2@email.com,address for lead2\n',
|
||||
',joe,doe,www.test.com,911234567892,user3@email.com,address for lead3\n',
|
||||
',john,doe,www.sample.com,911234567893,user4@email.com,address for lead4\n',
|
||||
]
|
||||
file_content = bytes(''.join(file_content), 'utf-8')
|
||||
data = {'leads_file': SimpleUploadedFile('file name', file_content)}
|
||||
response = self.client.post(reverse('leads:upload_lead_csv_file'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
# # def test_add_lead_view_form_error(self):
|
||||
# # response = self.client.post(reverse('leads:add_lead'),{})
|
||||
# # # self.assertEqual(response.status_code, 200)
|
||||
# # print(response.json())
|
||||
# # print(dir(response))
|
||||
# # self.assertJSONEqual(
|
||||
# # str(response.json(), encoding='utf8'),
|
||||
# # {'status': 'success'}
|
||||
# # )
|
||||
# for invalid email
|
||||
file_content = [
|
||||
'title,first name,last name,website,phone,email,address\n',
|
||||
'lead1,john,doe,www.example.com,911234567890,user1@email.com,address for lead1\n',
|
||||
'lead2,jane,doe,www.website.com,911234567891,user2@email.com,address for lead2\n',
|
||||
'lead3,joe,doe,www.test.com,911234567892,useremail.com,address for lead3\n',
|
||||
'lead4,john,doe,www.sample.com,911234567893,user4@email,address for lead4\n',
|
||||
]
|
||||
file_content = bytes(''.join(file_content), 'utf-8')
|
||||
data = {'leads_file': SimpleUploadedFile('file name', file_content)}
|
||||
response = self.client.post(reverse('leads:upload_lead_csv_file'), data)
|
||||
self.assertEqual(201, response.status_code)
|
||||
|
||||
# for invalid file type
|
||||
data = {'leads_file': b''}
|
||||
response = self.client.post(reverse('leads:upload_lead_csv_file'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
file_content_missing_headers = [
|
||||
',first name,last name,website,phone,,address\n',
|
||||
'lead1,john,doe,www.example.com,911234567890,user1@email,address for lead1\n',
|
||||
'lead2,jane,doe,www.website.com,911234567891,user2@email.com,address for lead2\n',
|
||||
'lead3,joe,doe,www.test.com,911234567892,user3@email.com,address for lead3\n',
|
||||
'lead4,john,doe,www.sample.com,911234567893,user4@email,address for lead4\n',
|
||||
]
|
||||
file_content_missing_headers = bytes(''.join(file_content_missing_headers), 'utf-8')
|
||||
data = {'leads_file': SimpleUploadedFile('file name', file_content_missing_headers)}
|
||||
response = self.client.post(reverse('leads:upload_lead_csv_file'), data)
|
||||
|
||||
response = self.client.post(reverse('leads:list') + '?tag=0', {'city': 'city'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.teams_leads = Teams.objects.create(name='teams leads')
|
||||
self.teams_leads.users.add(self.user1)
|
||||
response = self.client.post(reverse('leads:add_lead'), {'title': 'new lead title', 'teams': [self.teams_leads.id, ],
|
||||
'savenewform':'true'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = self.client.post(reverse('leads:add_lead'), {'title': ''})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeLead3@example.com', password="password")
|
||||
|
||||
response = self.client.get(reverse('leads:view_lead', args=(self.lead_1.id,)))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
response = self.client.get(reverse('leads:view_lead', args=(self.lead1.id,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeLead2@example.com', password="password")
|
||||
self.lead_user = Lead.objects.create(title="jane doe lead 2", created_by=self.user2)
|
||||
response = self.client.get(reverse('leads:view_lead', args=(self.lead_user.id,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='johnLead@example.com', password="password")
|
||||
response = self.client.get(reverse('leads:edit_lead', args=(self.lead_1.id,)) + '?status=converted')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get(reverse('leads:edit_lead', args=(self.lead_1.id,)) + '?status=c')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get(reverse('leads:edit_lead', args=(self.lead_1.id,)) + '?status=converted')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.teams_leads.users.add(self.user3)
|
||||
data = {
|
||||
'title': self.lead_1.title,
|
||||
'teams': [self.teams_leads.id, ],
|
||||
'status':'converted',
|
||||
'account_name': 'lead_conversion',
|
||||
'email':'account@lead.com'
|
||||
}
|
||||
comment = Comment.objects.create(comment='comment text lead', lead=self.lead_1, commented_by=self.user)
|
||||
response = self.client.post(reverse('leads:edit_lead', args=(self.lead_1.id,)) + '?status=converted', data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeLead3@example.com', password="password")
|
||||
|
||||
response = self.client.get(reverse('leads:remove_lead', args=(self.lead_1.id,)))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='johnLead@example.com', password="password")
|
||||
self.lead.email = None
|
||||
self.lead.save()
|
||||
response = self.client.get(reverse('leads:leads_convert', args=(self.lead.id,)) + '?status=converted')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
self.tag_for_lead = Tags.objects.create(name='tag for lead')
|
||||
data = {
|
||||
'tags':'tag1 lead,tag2 lead,tag for lead'
|
||||
}
|
||||
response = self.client.post(reverse('leads:update_lead_tags', args=(self.lead.id,)), data)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeLead3@example.com', password="password")
|
||||
response = self.client.post(reverse('leads:update_lead_tags', args=(self.lead.id,)), data)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='johnLead@example.com', password="password")
|
||||
data = {
|
||||
'lead': self.lead.id,
|
||||
'tag': self.tag_for_lead.id
|
||||
}
|
||||
response = self.client.post(reverse('leads:remove_lead_tag'), data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeLead3@example.com', password="password")
|
||||
response = self.client.post(reverse('leads:remove_lead_tag'), data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get(reverse('leads:sample_lead_file'))
|
||||
sample_data = [
|
||||
'title,first name,last name,website,phone,email,address\n',
|
||||
'lead1,john,doe,www.example.com,911234567890,user1@email.com,address for lead1\n',
|
||||
'lead2,jane,doe,www.website.com,911234567891,user2@email.com,address for lead2\n',
|
||||
'lead3,joe,doe,www.test.com,911234567892,user3@email.com,address for lead3\n',
|
||||
'lead4,john,doe,www.sample.com,911234567893,user4@email.com,address for lead4\n',
|
||||
]
|
||||
content = bytes(''.join(sample_data), 'utf-8')
|
||||
self.assertEqual(response.content, content)
|
||||
|
|
@ -302,8 +302,6 @@ def update_lead(request, pk):
|
|||
users = []
|
||||
if request.user.role == 'ADMIN' or request.user.is_superuser:
|
||||
users = User.objects.filter(is_active=True).order_by('email')
|
||||
elif request.user.google.all():
|
||||
users = []
|
||||
else:
|
||||
users = User.objects.filter(role='ADMIN').order_by('email')
|
||||
status = request.GET.get('status', None)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
from django.test import TestCase
|
||||
from opportunity.models import Opportunity
|
||||
from accounts.models import Account, Tags
|
||||
from contacts.models import Contact
|
||||
from common.models import User, Comment, Attachments, Address
|
||||
from cases.models import Case
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from accounts.models import Account, Tags
|
||||
from cases.models import Case
|
||||
from common.models import Address, Attachments, Comment, User
|
||||
from contacts.models import Contact
|
||||
from opportunity.models import Opportunity
|
||||
from teams.models import Teams
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
|
||||
|
|
@ -29,6 +31,24 @@ class OpportunityModel(object):
|
|||
self.user1.set_password('password')
|
||||
self.user1.save()
|
||||
|
||||
self.user2 = User.objects.create(
|
||||
first_name="john doe",
|
||||
username='jane doe opportunity',
|
||||
email='janeDoeOpp@example.com',
|
||||
role="USER",
|
||||
has_sales_access=True)
|
||||
self.user2.set_password('password')
|
||||
self.user2.save()
|
||||
|
||||
self.user3 = User.objects.create(
|
||||
first_name="john doe",
|
||||
username='joe doe opportunity',
|
||||
email='joeDoeOpp@example.com',
|
||||
role="USER",
|
||||
has_sales_access=True)
|
||||
self.user3.set_password('password')
|
||||
self.user3.save()
|
||||
|
||||
self.address = Address.objects.create(
|
||||
street="", city="city name", postcode="1234",
|
||||
country='IN')
|
||||
|
|
@ -55,10 +75,23 @@ class OpportunityModel(object):
|
|||
self.opportunity = Opportunity.objects.create(
|
||||
name="jane opportunity", amount="478",
|
||||
stage="negotiation/review", lead_source="Call", probability="58",
|
||||
closed_on="2016-05-04",
|
||||
closed_on="2016-05-04", account=self.account,
|
||||
description="opportunity description",
|
||||
created_by=self.user)
|
||||
self.opportunity.assigned_to.add(self.user)
|
||||
|
||||
self.opportunity_1 = Opportunity.objects.create(
|
||||
name="new opportunity", amount="478",
|
||||
stage="negotiation/review", probability="92",
|
||||
closed_on="2016-05-04", description="opportunity description",
|
||||
created_by=self.user)
|
||||
|
||||
self.opportunity_2 = Opportunity.objects.create(
|
||||
name="joe opportunity", amount="478",
|
||||
stage="negotiation/review", probability="92",
|
||||
closed_on="2016-05-04", description="opportunity description",
|
||||
created_by=self.user3)
|
||||
|
||||
self.case = Case.objects.create(
|
||||
name="case name", case_type="Problem", status="New", account=self.account,
|
||||
priority="Low", description="case description",
|
||||
|
|
@ -467,4 +500,83 @@ class AttachmentTestCaseError(OpportunityModel, TestCase):
|
|||
def test_attachment_add(self):
|
||||
url = "/opportunities/attachment/add/"
|
||||
response = self.client.post(url, {'opportunityid': self.opportunity.id})
|
||||
self.assertJSONEqual(force_text(response.content),{"error": ["This field is required."]})
|
||||
self.assertJSONEqual(force_text(response.content),{"error": ["This field is required."]})
|
||||
|
||||
response = self.client.get(reverse('opportunity:list') + '?tag={}'.format(self.tag_1.id))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.post(reverse('opportunity:list') + '?tag={}'.format(self.tag_1.id), {
|
||||
'tag': self.tag_1.id,
|
||||
})
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.team_opp = Teams.objects.create(name='opp team')
|
||||
self.team_opp.users.add(self.user1.id)
|
||||
self.client.logout()
|
||||
self.client.login(email='janeOpp@example.com', password='password')
|
||||
data = {
|
||||
'name': 'opportunity teams',
|
||||
'stage': 'QUALIFICATION',
|
||||
'teams': self.team_opp.id,
|
||||
'savenewform':'true',
|
||||
}
|
||||
response = self.client.post(reverse('opportunity:save'), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.get(reverse('opportunity:save') + '?view_account={}'.format(self.account.id))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.get(reverse('opportunity:opp_view', args=(self.opportunity_1.id,)))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeDoeOpp@example.com', password='password')
|
||||
response = self.client.get(reverse('opportunity:opp_view', args=(self.opportunity.id,)))
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='joeDoeOpp@example.com', password='password')
|
||||
response = self.client.get(reverse('opportunity:opp_view', args=(self.opportunity_2.id,)))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeOpp@example.com', password='password')
|
||||
data = {
|
||||
'name': 'opportunity teams edit',
|
||||
'stage': 'QUALIFICATION',
|
||||
'teams': self.team_opp.id,
|
||||
'savenewform':'true',
|
||||
'from_account': self.account.id,
|
||||
}
|
||||
response = self.client.post(reverse('opportunity:opp_edit', args=(self.opportunity_2.id,)), data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
response = self.client.get(reverse('opportunity:opp_edit', args=(self.opportunity_2.id,)) + '?view_account={}'.format(self.account.id))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='joeDoeOpp@example.com', password='password')
|
||||
response = self.client.get(reverse('opportunity:opp_edit', args=(self.opportunity_1.id,)), data)
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeOpp@example.com', password='password')
|
||||
response = self.client.get(reverse('opportunity:opp_remove', args=(self.opportunity_1.id,)), {
|
||||
'pk':self.opportunity_1.id,
|
||||
}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='joeDoeOpp@example.com', password='password')
|
||||
response = self.client.post(reverse('opportunity:opp_remove', args=(self.opportunity.id,)) + '?view_account={}'.format(self.account.id), {
|
||||
'pk':self.opportunity.id})
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
self.client.logout()
|
||||
self.client.login(email='janeOpp@example.com', password='password')
|
||||
response = self.client.post(reverse('opportunity:opp_remove', args=(self.opportunity_2.id,)) + '?view_account={}'.format(self.account.id), {
|
||||
'pk':self.opportunity_2.id})
|
||||
self.assertEqual(302, response.status_code)
|
||||
|
||||
response = self.client.get(reverse('opportunity:contacts') + '?account={}'.format(self.account.id))
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,55 +1,19 @@
|
|||
amqp==2.5.0
|
||||
arrow==0.14.2
|
||||
atomicwrites==1.3.0
|
||||
attrs==19.1.0
|
||||
Babel==2.7.0
|
||||
billiard==3.6.0.0
|
||||
boto==2.49.0
|
||||
boto3==1.9.94
|
||||
botocore==1.12.196
|
||||
celery==4.3.0
|
||||
certifi==2019.6.16
|
||||
chardet==3.0.4
|
||||
codacy-coverage==1.3.11
|
||||
coverage==4.5.3
|
||||
cssselect==1.0.3
|
||||
Django==2.2.3
|
||||
django-appconf==1.0.3
|
||||
django-compressor==2.2
|
||||
django-phonenumber-field==2.2.0
|
||||
Django
|
||||
celery
|
||||
django-simple-pagination==1.3
|
||||
django-storages==1.7.1
|
||||
docutils==0.14
|
||||
et-xmlfile==1.0.1
|
||||
idna==2.8
|
||||
importlib-metadata==0.18
|
||||
jdcal==1.4.1
|
||||
jmespath==0.9.4
|
||||
kombu==4.6.3
|
||||
lxml==4.3.2
|
||||
more-itertools==7.2.0
|
||||
openpyxl==2.6.0
|
||||
pdfkit==0.6.1
|
||||
django-compressor
|
||||
django-phonenumber-field==2.2.0
|
||||
django-storages
|
||||
psycopg2-binary
|
||||
arrow
|
||||
phonenumbers==8.10.4
|
||||
Pillow==5.4.1
|
||||
pluggy==0.12.0
|
||||
psycopg2-binary==2.7.7
|
||||
py==1.8.0
|
||||
pytest==4.1.1
|
||||
pytest-cov==2.6.1
|
||||
pytest-django==3.4.5
|
||||
python-dateutil==2.8.0
|
||||
pytz==2018.9
|
||||
raven==6.10.0
|
||||
rcssmin==1.0.6
|
||||
redis==3.2.1
|
||||
requests==2.22.0
|
||||
rjsmin==1.0.12
|
||||
s3transfer==0.2.1
|
||||
six==1.12.0
|
||||
sorl-thumbnail==12.5.0
|
||||
sqlparse==0.3.0
|
||||
urllib3==1.25.3
|
||||
vine==1.3.0
|
||||
xlrd==1.2.0
|
||||
zipp==0.5.2
|
||||
requests
|
||||
boto3
|
||||
lxml
|
||||
cssselect
|
||||
openpyxl
|
||||
xlrd
|
||||
pdfkit
|
||||
redis
|
||||
coverage
|
||||
raven
|
||||
241
tasks/tests.py
241
tasks/tests.py
|
|
@ -1,3 +1,242 @@
|
|||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
# Create your tests here.
|
||||
from accounts.models import Account
|
||||
from common.models import User
|
||||
from tasks.models import Task
|
||||
from teams.models import Teams
|
||||
|
||||
|
||||
class TaskCreateTest(object):
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(
|
||||
first_name="johnTask", username='johnDoeTask', email='johnTask@example.com', role='ADMIN')
|
||||
self.user.set_password('password')
|
||||
self.user.save()
|
||||
|
||||
self.user1 = User.objects.create(
|
||||
first_name="janeTask",
|
||||
username='janeDoeTask',
|
||||
email='janeDoeTask@example.com',
|
||||
role="USER",
|
||||
has_sales_access=True)
|
||||
self.user1.set_password('password')
|
||||
self.user1.save()
|
||||
|
||||
self.user2 = User.objects.create(
|
||||
first_name="joeTask",
|
||||
username='joeTask',
|
||||
email='joeTask@example.com',
|
||||
role="USER",
|
||||
has_sales_access=True)
|
||||
self.user2.set_password('password')
|
||||
self.user2.save()
|
||||
|
||||
# self.user3 = User.objects.create(
|
||||
# first_name="joeTask",
|
||||
# username='joeDoeTask',
|
||||
# email='joeDoeTask@example.com',
|
||||
# role="USER",
|
||||
# has_sales_access=True)
|
||||
# self.user3.set_password('password')
|
||||
# self.user3.save()
|
||||
|
||||
self.team_dev = Teams.objects.create(name='task teams')
|
||||
self.team_dev.users.add(self.user2.id)
|
||||
|
||||
self.account = Account.objects.create(
|
||||
name="john task", email="johntasks@example.com", phone="123456789",
|
||||
billing_address_line="", billing_street="street name",
|
||||
billing_city="city name",
|
||||
billing_state="state", billing_postcode="1234",
|
||||
billing_country="US",
|
||||
website="www.example.como", created_by=self.user, status="open",
|
||||
industry="SOFTWARE", description="Testing")
|
||||
|
||||
self.task = Task.objects.create(
|
||||
title='first task', status='New', priority='Medium')
|
||||
self.task.created_by = self.user
|
||||
self.task.assigned_to.add(self.user1)
|
||||
self.task.account = self.account
|
||||
self.task.save()
|
||||
|
||||
self.account.assigned_to.add(self.user1)
|
||||
|
||||
self.task_1 = Task.objects.create(
|
||||
title='second task', status='New', priority='Medium')
|
||||
self.task_1.created_by = self.user
|
||||
self.task_1.assigned_to.add(self.user1)
|
||||
self.task_1.save()
|
||||
|
||||
self.task_2 = Task.objects.create(
|
||||
title='second task', status='New', priority='Medium')
|
||||
self.task_2.created_by = self.user1
|
||||
self.task_2.save()
|
||||
|
||||
|
||||
class TaskListTestCase(TaskCreateTest, TestCase):
|
||||
|
||||
def test_tasks_list(self):
|
||||
self.client.login(email='johnTask@example.com', password='password')
|
||||
response = self.client.get(reverse('tasks:tasks_list'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = {
|
||||
'task_title': 'title',
|
||||
'status': 'New',
|
||||
'priority': 'Medium'
|
||||
}
|
||||
response = self.client.post(reverse('tasks:tasks_list'), data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.login(email='janeDoeTask@example.com', password='password')
|
||||
response = self.client.get(reverse('tasks:tasks_list'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.post(reverse('tasks:tasks_list'), data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TaskCreateTestCase(TaskCreateTest, TestCase):
|
||||
|
||||
def test_task_create(self):
|
||||
self.client.login(email='johnTask@example.com', password='password')
|
||||
response = self.client.get(reverse('tasks:task_create'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.login(email='janeDoeTask@example.com', password='password')
|
||||
response = self.client.get(reverse('tasks:task_create'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.login(email='johnTask@example.com', password='password')
|
||||
data = {
|
||||
'title': '',
|
||||
'status': 'New',
|
||||
'priority': 'Medium'
|
||||
}
|
||||
response = self.client.post(reverse('tasks:task_create'), data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = {
|
||||
'title': 'task title',
|
||||
'status': 'New',
|
||||
'priority': 'Medium',
|
||||
'teams': self.team_dev.id,
|
||||
'from_account': self.account.id,
|
||||
}
|
||||
response = self.client.post(reverse('tasks:task_create'), data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TaskDetailTestCase(TaskCreateTest, TestCase):
|
||||
|
||||
def test_task_detail(self):
|
||||
self.client.login(email='johnTask@example.com', password='password')
|
||||
response = self.client.get(
|
||||
reverse('tasks:task_detail', args=(self.task.id,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('tasks:task_detail', args=(self.task_1.id,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.login(email='janeDoeTask@example.com', password='password')
|
||||
response = self.client.get(
|
||||
reverse('tasks:task_detail', args=(self.task.id,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('tasks:task_detail', args=(self.task_2.id,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.login(email='joeTask@example.com', password='password')
|
||||
response = self.client.get(
|
||||
reverse('tasks:task_detail', args=(self.task.id,)))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
|
||||
class TaskEditTestCase(TaskCreateTest, TestCase):
|
||||
|
||||
def test_task_Edit(self):
|
||||
self.client.login(email='johnTask@example.com', password='password')
|
||||
response = self.client.get(
|
||||
reverse('tasks:task_edit', args=(self.task.id,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.login(email='janeDoeTask@example.com', password='password')
|
||||
response = self.client.get(
|
||||
reverse('tasks:task_edit', args=(self.task.id,)))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('tasks:task_edit', args=(self.task_2.id,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.login(email='johnTask@example.com', password='password')
|
||||
data = {
|
||||
'title': '',
|
||||
'status': 'New',
|
||||
'priority': 'Medium'
|
||||
}
|
||||
response = self.client.post(
|
||||
reverse('tasks:task_edit', args=(self.task.id,)), data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = {
|
||||
'title': 'task title',
|
||||
'status': 'New',
|
||||
'priority': 'High',
|
||||
'teams': self.team_dev.id,
|
||||
'from_account': self.account.id,
|
||||
}
|
||||
response = self.client.post(
|
||||
reverse('tasks:task_edit', args=(self.task.id,)), data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TaskDeleteTestCase(TaskCreateTest, TestCase):
|
||||
|
||||
def test_task_delete(self):
|
||||
self.client.login(email='janeDoeTask@example.com', password='password')
|
||||
response = self.client.get(
|
||||
reverse('tasks:task_delete', args=(self.task.id,)))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
self.client.login(email='johnTask@example.com', password='password')
|
||||
response = self.client.get(
|
||||
reverse('tasks:task_delete', args=(self.task.id,)))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('tasks:task_delete', args=(self.task_1.id,)) + '?view_account={}'.format(self.account.id))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
|
||||
|
||||
# class AddCommentTestCase(TaskCreateTest, TestCase):
|
||||
|
||||
# def test_task_delete(self):
|
||||
|
||||
# self.client.login(email='johnTask@example.com', password='password')
|
||||
# data = {
|
||||
# 'comment': '',
|
||||
# 'task_id': self.task.id,
|
||||
# }
|
||||
# response = self.client.post(
|
||||
# reverse('tasks:add_comment'), data)
|
||||
# self.assertEqual(response.status_code, 200)
|
||||
|
||||
# data = {
|
||||
# 'comment': 'test comment',
|
||||
# 'task_id': self.task.id,
|
||||
# }
|
||||
# response = self.client.post(
|
||||
# reverse('tasks:add_comment'), data)
|
||||
# self.assertEqual(response.status_code, 200)
|
||||
|
||||
# self.client.login(email='janeDoeTask@example.com', password='password')
|
||||
# response = self.client.post(
|
||||
# reverse('tasks:add_comment'))
|
||||
# self.assertEqual(response.status_code, 200)
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ from django.db.models import Q
|
|||
from django.http import JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render, reverse
|
||||
from django.views.generic import (CreateView, DeleteView, DetailView, FormView,
|
||||
TemplateView, UpdateView, View)
|
||||
TemplateView, UpdateView, View)
|
||||
|
||||
from accounts.models import Account
|
||||
from common.access_decorators_mixins import (MarketingAccessRequiredMixin,
|
||||
SalesAccessRequiredMixin, marketing_access_required, sales_access_required)
|
||||
from common.models import Attachments, Comment, User
|
||||
from common.tasks import send_email_user_mentions
|
||||
from contacts.models import Contact
|
||||
|
|
@ -18,8 +20,6 @@ from tasks.celery_tasks import send_email
|
|||
from tasks.forms import TaskAttachmentForm, TaskCommentForm, TaskForm
|
||||
from tasks.models import Task
|
||||
from tasks.utils import *
|
||||
from common.access_decorators_mixins import (
|
||||
sales_access_required, marketing_access_required, SalesAccessRequiredMixin, MarketingAccessRequiredMixin)
|
||||
from teams.models import Teams
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Django CRM</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Muli" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css" />
|
||||
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
|
||||
|
|
@ -115,7 +115,6 @@
|
|||
{% if request.user.role == "ADMIN" or request.user.is_superuser %}
|
||||
<a class="dropdown-item" href="{% url 'common:users_list' %}">Users</a>
|
||||
<a class="dropdown-item" href="{% url 'common:api_settings' %}">Settings</a>
|
||||
<a class="dropdown-item" href="{% url 'marketing:list_all_emails_for_campaigns' %}">Contacts</a>
|
||||
{% endif %}
|
||||
{% if request.user.email != "admin@micropyramid.com" %}
|
||||
<a class="dropdown-item" href="/change-password/">Change Password</a>
|
||||
|
|
@ -143,7 +142,7 @@
|
|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js"></script>
|
||||
<script defer src="https://use.fontawesome.com/releases/v5.7.1/js/all.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.min.js"></script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue