added models contacts, contactlists, campaigns for marketing module & completed crud operations for email templates model in marketing, added contact list crud operations, sample csv file added for reference, updated actions icons in all list pages, fixed issue of getting closed leads in accounts create/edit pages (#215)

This commit is contained in:
nikhila05 2019-03-13 11:46:07 +05:30 committed by GitHub
parent 196a225578
commit 88ae667245
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
78 changed files with 3977 additions and 1779 deletions

1
.gitignore vendored
View file

@ -15,3 +15,4 @@ ref.css
ref.css.map
.coverage
crm/dev_settings\.py
env

View file

@ -1,6 +0,0 @@
from django.contrib import admin
from accounts.models import Account
# Register your models here.
admin.site.register(Account)

View file

@ -1,8 +1,8 @@
from django import forms
from .models import Account
from common.models import Comment, Attachments
from leads.models import *
from contacts.models import *
from leads.models import Lead
from contacts.models import Contact
from django.db.models import Q
@ -37,10 +37,10 @@ class AccountForm(forms.ModelForm):
self.fields["billing_country"].choices = [
("", "--Country--"), ] + list(self.fields["billing_country"].choices)[1:]
self.fields["lead"].queryset = Lead.objects.all(
).exclude(status='dead')
).exclude(status='closed')
if request_user:
self.fields["lead"].queryset = Lead.objects.filter(
Q(assigned_to__in=[request_user]) | Q(created_by=request_user)).exclude(status='dead')
Q(assigned_to__in=[request_user]) | Q(created_by=request_user)).exclude(status='closed')
self.fields["contacts"].queryset = Contact.objects.filter(
Q(assigned_to__in=[request_user]) | Q(created_by=request_user))

View file

@ -29,7 +29,9 @@ class Account(models.Model):
email = models.EmailField()
phone = PhoneNumberField(null=True)
industry = models.CharField(
_("Industry Type"), max_length=255, choices=INDCHOICES, blank=True, null=True)
_("Industry Type"),
max_length=255, choices=INDCHOICES,
blank=True, null=True)
# billing_address = models.ForeignKey(
# Address, related_name='account_billing_address', on_delete=models.CASCADE, blank=True, null=True)
# shipping_address = models.ForeignKey(
@ -49,14 +51,16 @@ class Account(models.Model):
website = models.URLField(_("Website"), blank=True, null=True)
description = models.TextField(blank=True, null=True)
created_by = models.ForeignKey(
User, related_name='account_created_by', on_delete=models.SET_NULL, null=True)
User, related_name='account_created_by',
on_delete=models.SET_NULL, null=True)
created_on = models.DateTimeField(_("Created on"), auto_now_add=True)
is_active = models.BooleanField(default=False)
tags = models.ManyToManyField(Tags, blank=True)
status = models.CharField(
choices=ACCOUNT_STATUS_CHOICE, max_length=64, default='open')
lead = models.ForeignKey(
'leads.Lead', related_name="account_leads", on_delete=models.SET_NULL, null=True)
'leads.Lead', related_name="account_leads",
on_delete=models.SET_NULL, null=True)
contact_name = models.CharField(pgettext_lazy(
"Name of Contact", "Contact Name"), max_length=120)
contacts = models.ManyToManyField(

View file

@ -2,6 +2,7 @@
{% load staticfiles %}
{% load paginate %}
{% block extralinks %}
{% load thumbnail %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
{% endblock %}
{% block content %}
@ -89,6 +90,7 @@
<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="10%">Tags</th>
@ -106,6 +108,15 @@
<tr id="account_details{{each_account.id}}" class="text-center">
<td scope="row">{{ forloop.counter }}</td>
<td>{% if each_account.name %}<a href="{% url 'accounts:view_account' each_account.id %}">{{ each_account.name }}</a>{% endif %}</td>
<td>
{% if each_account.created_by.profile_pic %}
{% thumbnail each_account.created_by.profile_pic "40x40" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" title="{{ each_account.created_by }}">
{% endthumbnail %}
{% else %}
<img src="{% static 'images/user.png' %}" alt="Micro profile pic" style="width: 40px;height: 40px;" />
{% endif %}
</td>
<td>{% if each_account.billing_city %}{{ each_account.billing_city }} {% else %} Not specified {% endif %}</td>
<td>{% if each_account.billing_state %}{{ each_account.billing_state }} {% else %} Not specified {% endif %}</td>
<td style="display: block;">

View file

@ -56,7 +56,12 @@
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="exampleInputEmail1" class="required">Leads{% if account_form.lead.field.required %}<span class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ account_form.lead }}
<select name="lead" class="form-control" placeholder="Lead" required="" id="id_lead">
<option value="" selected="">---------</option>
{% for lead in leads %}
<option value="{{lead.id}}" {% ifequal account_obj.lead.id lead.id %}selected{% endifequal %}>{{lead.first_name}} {{lead.last_name}}</option>
{% endfor %}
</select>
<span class="error" id="id__lead">{{ account_form.lead.errors }}</span>
</div>
</div>
@ -286,4 +291,4 @@
}
});
</script>
{% endblock js_block %}
{% endblock js_block %}

View file

@ -7,18 +7,21 @@ from common.models import User, Comment, Attachments
class AccountCreateTest(object):
def setUp(self):
self.user = User.objects.create(
first_name="uday", username='uday', email='u@mp.com', role='ADMIN')
self.user.set_password('uday2293')
first_name="mike", username='mike', email='u@mp.com', role='ADMIN')
self.user.set_password('mike2293')
self.user.save()
self.account = Account.objects.create(
name="Uday", email="udayteja@micropyramid.com", phone="8333855552",
billing_address_line="", billing_street="KPHB", billing_city="HYDERABAD",
billing_state="ANDHRA PRADESH", billing_postcode="500073", billing_country="IN",
website="www.uday.com", created_by=self.user,
name="mike", email="mike@micropyramid.com", phone="8333855552",
billing_address_line="", billing_street="KPHB",
billing_city="New York",
billing_state="usa", billing_postcode="500073",
billing_country="IN",
website="www.mike.com", created_by=self.user,
industry="SOFTWARE", description="Yes.. Testing Done")
self.case = Case.objects.create(
name="raghu", case_type="Problem", status="New", account=self.account,
name="raghu", case_type="Problem",
status="New", account=self.account,
priority="Low", description="something",
created_by=self.user, closed_on="2016-05-04")
self.comment = Comment.objects.create(
@ -29,25 +32,34 @@ class AccountCreateTest(object):
attachment='image.png', case=self.case,
created_by=self.user, account=self.account
)
self.client.login(email='u@mp.com', password='uday2293')
self.client.login(email='u@mp.com', password='mike2293')
class AccountsCreateTestCase(AccountCreateTest, TestCase):
def test_account_create_url(self):
response = self.client.get('/accounts/create/', {
'name': "Uday", 'email': "udayteja@micropyramid.com", 'phone': "",
'billing_address_line': "", 'billing_street': "KPHB", 'billing_city': "HYDERABAD",
'billing_state': "ANDHRA PRADESH", 'billing_postcode': "500073", 'billing_country': "IN",
'website': "www.uday.com",
'name': "mike", 'email': "mike@micropyramid.com",
'phone': "",
'billing_address_line': "",
'billing_street': "KPHB",
'billing_city': "New York",
'billing_state': "usa",
'billing_postcode': "500073",
'billing_country': "IN",
'website': "www.mike.com",
'industry': "SOFTWARE", 'description': "Yes.. Testing Done"})
self.assertEqual(response.status_code, 200)
def test_account_create_html(self):
response = self.client.get('/accounts/create/', {
'name': "Uday", 'email': "udayteja@micropyramid.com", 'phone': "",
'billing_address_line': "", 'billing_street': "KPHB", 'billing_city': "HYDERABAD",
'billing_state': "ANDHRA PRADESH", 'billing_postcode': "500073", 'billing_country': "IN",
'website': "www.uday.com",
'name': "mike", 'email': "mike@micropyramid.com", 'phone': "",
'billing_address_line': "",
'billing_street': "KPHB",
'billing_city': "New York",
'billing_state': "usa",
'billing_postcode': "500073",
'billing_country': "IN",
'website': "www.mike.com",
'industry': "SOFTWARE", 'description': "Yes.. Testing Done"})
self.assertTemplateUsed(response, 'create_account.html')
@ -64,8 +76,10 @@ class AccountsListTestCase(AccountCreateTest, TestCase):
self.account = Account.objects.all()
data = {'name': 'name', 'city': 'city',
'billing_address_line': "billing_address_line",
'billing_street': "billing_street", 'billing_city': "billing_city",
'billing_state': "billing_state", 'billing_postcode': "billing_postcode",
'billing_street': "billing_street",
'billing_city': "billing_city",
'billing_state': "billing_state",
'billing_postcode': "billing_postcode",
'billing_country': "billing_country"}
response = self.client.post('/accounts/list/', data)
self.assertEqual(response.status_code, 200)
@ -101,22 +115,36 @@ class AccountsRemoveTestCase(AccountCreateTest, TestCase):
class AccountsUpdateUrlTestCase(AccountCreateTest, TestCase):
def test_accounts_update(self):
response = self.client.get('/accounts/' + str(self.account.id) + '/edit/', {
'name': "Uday", 'email': "udayteja@micropyramid.com", 'phone': "8333855552",
'billing_address_line': "", 'billing_street': "KPHB", 'billing_city': "HYDERABAD",
'billing_state': "ANDHRA PRADESH", 'billing_postcode': "500073", 'billing_country': "IN",
'website': "www.uday.com",
'industry': "SOFTWARE", 'description': "Yes.. Testing Done"})
response = self.client.get(
'/accounts/' + str(self.account.id) + '/edit/', {
'name': "mike",
'email': "mike@micropyramid.com", 'phone': "8333855552",
'billing_address_line': "",
'billing_street': "KPHB",
'billing_city': "New York",
'billing_state': "usa",
'billing_postcode': "500073",
'billing_country': "IN",
'website': "www.mike.com",
'industry': "SOFTWARE",
'description': "Yes.. Testing Done"})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'create_account.html')
def test_accounts_update_post(self):
response = self.client.post('/accounts/' + str(self.account.id) + '/edit/', {
'name': "meghana", 'email': "meghana@micropyramid.com", 'phone': "9555333123",
'billing_address_line': "", 'billing_street': "KPHB", 'billing_city': "HYDERABAD",
'billing_state': "ANDHRA PRADESH", 'billing_postcode': "500073", 'billing_country': "IN",
'website': "www.meghana.com",
'industry': "SOFTWARE", 'description': "Yes.. Testing Done"})
response = self.client.post(
'/accounts/' + str(self.account.id) + '/edit/',
{
'name': "dolly", 'email': "dolly@micropyramid.com",
'phone': "9555333123",
'billing_address_line': "",
'billing_street': "KPHB",
'billing_city': "New York",
'billing_state': "usa",
'billing_postcode': "500073",
'billing_country': "IN",
'website': "www.dolly.com",
'industry': "SOFTWARE", 'description': "Yes.. Testing Done"})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'create_account.html')
@ -134,9 +162,15 @@ class AccountsUpdateUrlTestCase(AccountCreateTest, TestCase):
class AccountCreateEmptyFormTestCase(AccountCreateTest, TestCase):
def test_account_creation_invalid_data(self):
data = {'name': "", 'email': "", 'phone': "", 'website': "", 'industry': "", 'description': "",
'billing_address_line': "", 'billing_street': "KPHB", 'billing_city': "HYDERABAD",
'billing_state': "ANDHRA PRADESH", 'billing_postcode': "500073", 'billing_country': "IN"}
data = {'name': "", 'email': "", 'phone': "",
'website': "", 'industry': "",
'description': "",
'billing_address_line': "",
'billing_street': "KPHB",
'billing_city': "New York",
'billing_state': "usa",
'billing_postcode': "500073",
'billing_country': "IN"}
response = self.client.post('/accounts/create/', data)
self.assertEqual(response.status_code, 200)
@ -190,5 +224,6 @@ class AttachmentTestCase(AccountCreateTest, TestCase):
def test_attachment_delete(self):
response = self.client.post(
'/accounts/attachment/remove/', {'attachment_id': self.attachment.id})
'/accounts/attachment/remove/',
{'attachment_id': self.attachment.id})
self.assertEqual(response.status_code, 200)

View file

@ -12,12 +12,15 @@ urlpatterns = [
path('create/', CreateAccountView.as_view(), name='new_account'),
path('<int:pk>/view/', AccountDetailView.as_view(), name="view_account"),
path('<int:pk>/edit/', AccountUpdateView.as_view(), name="edit_account"),
path('<int:pk>/delete/', AccountDeleteView.as_view(), name="remove_account"),
path('<int:pk>/delete/', AccountDeleteView.as_view(),
name="remove_account"),
path('comment/add/', AddCommentView.as_view(), name="add_comment"),
path('comment/edit/', UpdateCommentView.as_view(), name="edit_comment"),
path('comment/remove/', DeleteCommentView.as_view(), name="remove_comment"),
path('comment/remove/', DeleteCommentView.as_view(),
name="remove_comment"),
path('attachment/add/', AddAttachmentView.as_view(), name="add_attachment"),
path('attachment/add/', AddAttachmentView.as_view(),
name="add_attachment"),
path('attachment/remove/', DeleteAttachmentsView.as_view(),
name="remove_attachment"),
]

View file

@ -1,16 +1,14 @@
import json
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMessage
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.shortcuts import get_object_or_404, redirect
from django.views.generic import (
CreateView, UpdateView, DetailView, TemplateView, View, DeleteView)
from accounts.forms import AccountForm, AccountCommentForm, AccountAttachmentForm
from accounts.forms import AccountForm, AccountCommentForm, \
AccountAttachmentForm
from accounts.models import Account, Tags
from common.models import User, Comment, Attachments
from common.utils import INDCHOICES, COUNTRIES, CURRENCY_CODES, CASE_TYPE, PRIORITY_CHOICE, STATUS_CHOICE
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
@ -91,7 +89,8 @@ class CreateAccountView(LoginRequiredMixin, CreateView):
def dispatch(self, request, *args, **kwargs):
self.users = User.objects.filter(is_active=True).order_by('email')
return super(CreateAccountView, self).dispatch(request, *args, **kwargs)
return super(
CreateAccountView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(CreateAccountView, self).get_form_kwargs()
@ -160,10 +159,12 @@ class CreateAccountView(LoginRequiredMixin, CreateView):
context["industries"] = INDCHOICES
context["countries"] = COUNTRIES
context["contact_count"] = Contact.objects.count()
context["lead_count"] = Lead.objects.exclude(status='dead').count()
context["leads"] = Lead.objects.exclude(
status__in=['converted', 'closed'])
context["lead_count"] = context["leads"].count()
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
context["lead_count"] = Lead.objects.filter(
Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user)).exclude(status='dead').count()
Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user)).exclude(status='closed').count()
return context
@ -178,18 +179,17 @@ class AccountDetailView(LoginRequiredMixin, DetailView):
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if self.request.user != account_record.created_by:
raise PermissionDenied
if (
comment_permission = True if (
self.request.user == account_record.created_by or
self.request.user.is_superuser or self.request.user.role == 'ADMIN'
):
comment_permission = True
else:
comment_permission = False
) else False
context.update({
"comments": account_record.accounts_comments.all(),
"attachments": account_record.account_attachment.all(),
"opportunity_list": Opportunity.objects.filter(account=account_record),
"opportunity_list": Opportunity.objects.filter(
account=account_record),
"contacts": account_record.contacts.all(),
"users": User.objects.filter(is_active=True).order_by('email'),
"cases": Case.objects.filter(account=account_record),
@ -212,7 +212,8 @@ class AccountUpdateView(LoginRequiredMixin, UpdateView):
def dispatch(self, request, *args, **kwargs):
self.users = User.objects.filter(is_active=True).order_by('email')
return super(AccountUpdateView, self).dispatch(request, *args, **kwargs)
return super(AccountUpdateView, self).dispatch(
request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(AccountUpdateView, self).get_form_kwargs()
@ -233,9 +234,6 @@ class AccountUpdateView(LoginRequiredMixin, UpdateView):
# Save Account
account_object = form.save(commit=False)
account_object.save()
all_members_list = []
account_object.tags.clear()
if self.request.POST.get('tags', ''):
tags = self.request.POST.get("tags")
@ -284,10 +282,12 @@ class AccountUpdateView(LoginRequiredMixin, UpdateView):
context["industries"] = INDCHOICES
context["countries"] = COUNTRIES
context["contact_count"] = Contact.objects.count()
context["lead_count"] = Lead.objects.exclude(status='dead').count()
context["leads"] = Lead.objects.exclude(
status__in=['converted', 'closed'])
context["lead_count"] = context["leads"].count()
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
context["lead_count"] = Lead.objects.filter(
Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user)).exclude(status='dead').count()
Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user)).exclude(status='closed').count()
return context
@ -322,7 +322,8 @@ class AddCommentView(LoginRequiredMixin, CreateView):
return self.form_valid(form)
return self.form_invalid(form)
data = {'error': "You don't have permission to comment for this account."}
data = {
'error': "You don't have permission to comment for this account."}
return JsonResponse(data)
def form_valid(self, form):
@ -392,7 +393,8 @@ class AddAttachmentView(LoginRequiredMixin, CreateView):
self.account = get_object_or_404(
Account, id=request.POST.get('accountid'))
if (
request.user == self.account.created_by or request.user.is_superuser or
request.user == self.account.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
@ -402,7 +404,8 @@ class AddAttachmentView(LoginRequiredMixin, CreateView):
return self.form_invalid(form)
data = {
'error': "You don't have permission to add attachment for this account."}
'error': "You don't have permission to add attachment \
for this account."}
return JsonResponse(data)
def form_valid(self, form):
@ -415,7 +418,8 @@ class AddAttachmentView(LoginRequiredMixin, CreateView):
"attachment_id": attachment.id,
"attachment": attachment.file_name,
"attachment_url": attachment.attachment.url,
"download_url": reverse('common:download_attachment', kwargs={'pk': attachment.id}),
"download_url": reverse('common:download_attachment',
kwargs={'pk': attachment.id}),
"attachment_display": attachment.get_file_type_display(),
"created_on": attachment.created_on,
"created_by": attachment.created_by.email
@ -431,12 +435,14 @@ class DeleteAttachmentsView(LoginRequiredMixin, View):
self.object = get_object_or_404(
Attachments, id=request.POST.get("attachment_id"))
if (
request.user == self.object.created_by or request.user.is_superuser or
request.user == self.object.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
self.object.delete()
data = {"acd": request.POST.get("attachment_id")}
return JsonResponse(data)
data = {'error': "You don't have permission to delete this attachment."}
data = {
'error': "You don't have permission to delete this attachment."}
return JsonResponse(data)

View file

@ -1,5 +0,0 @@
from django.contrib import admin
from cases.models import Case
admin.site.register(Case)

View file

@ -26,7 +26,8 @@ class Case(models.Model):
assigned_to = models.ManyToManyField(
User, related_name='case_assigned_users')
created_by = models.ForeignKey(
User, related_name='case_created_by', on_delete=models.SET_NULL, null=True)
User, related_name='case_created_by',
on_delete=models.SET_NULL, null=True)
created_on = models.DateTimeField(_("Created on"), auto_now_add=True)
is_active = models.BooleanField(default=False)
@ -39,30 +40,44 @@ class Case(models.Model):
def get_meetings(self):
content_type = ContentType.objects.get(app_label="cases", model="case")
return Event.objects.filter(
content_type=content_type, object_id=self.id, event_type="Meeting", status="Planned")
content_type=content_type,
object_id=self.id,
event_type="Meeting",
status="Planned")
def get_completed_meetings(self):
content_type = ContentType.objects.get(app_label="cases", model="case")
return Event.objects.filter(
content_type=content_type, object_id=self.id, event_type="Meeting").exclude(status="Planned")
content_type=content_type,
object_id=self.id,
event_type="Meeting").exclude(status="Planned")
def get_tasks(self):
content_type = ContentType.objects.get(app_label="cases", model="case")
return Event.objects.filter(content_type=content_type, object_id=self.id, event_type="Task", status="Planned")
return Event.objects.filter(content_type=content_type,
object_id=self.id,
event_type="Task",
status="Planned")
def get_completed_tasks(self):
content_type = ContentType.objects.get(app_label="cases", model="case")
return Event.objects.filter(
content_type=content_type, object_id=self.id, event_type="Task").exclude(status="Planned")
content_type=content_type,
object_id=self.id,
event_type="Task").exclude(status="Planned")
def get_calls(self):
content_type = ContentType.objects.get(app_label="cases", model="case")
return Event.objects.filter(content_type=content_type, object_id=self.id, event_type="Call", status="Planned")
return Event.objects.filter(content_type=content_type,
object_id=self.id, event_type="Call",
status="Planned")
def get_completed_calls(self):
content_type = ContentType.objects.get(app_label="cases", model="case")
return Event.objects.filter(
content_type=content_type, object_id=self.id, event_type="Call").exclude(status="Planned")
content_type=content_type,
object_id=self.id,
event_type="Call").exclude(status="Planned")
def get_assigned_user(self):
return User.objects.get(id=self.assigned_to.id)

View file

@ -123,7 +123,7 @@
{% if request.user == case.created_by or request.user.role == "ADMIN" or request.user.is_superuser %}
<a href="{% url 'cases:remove_case' case.id %}" class="btn btn-danger delete remove_case"><i class="fas fa-trash-alt"></i></a>
{% if case.status != 'Closed' %}
<a onclick="close_case({{case.id}}, '{% url 'cases:close_case' %}')" class="btn btn-warning"><i class="fas fa-times"></i> </a>
<a onclick="close_case({{case.id}}, '{% url 'cases:close_case' %}')" class="btn btn-info"><i class="fas fa-times"></i> </a>
{% endif %}
{% endif %}
</td>

View file

@ -10,29 +10,29 @@ class CaseCreation(object):
def setUp(self):
self.address = Address.objects.create(
street="6th phase",
city="hyderabad",
city="LosVegas",
postcode="506344",
country='IN')
country='US')
self.user = User.objects.create(
first_name="raghu",
username='raghu',
first_name="robert",
username='robert',
email='r@mp.com',
role="ADMIN")
self.user.set_password('raghu')
self.user.set_password('robert')
self.user.save()
self.client.login(email='r@mp.com', password='raghu')
self.client.login(email='r@mp.com', password='robert')
self.client.login(email='r@mp.com', password='raghu')
self.client.login(email='r@mp.com', password='robert')
self.account = Account.objects.create(
name="account",
email="account@gmail.com", phone="12345",
billing_address_line="",
billing_street="6th phase",
billing_city="hyderabad",
billing_city="LosVegas",
billing_postcode="506344",
billing_country='IN',
billing_country='US',
website="www.account.com", description="account",
created_by=self.user)
@ -44,7 +44,7 @@ class CaseCreation(object):
)
self.case = Case.objects.create(
name="raghu", case_type="Problem", status="New",
name="robert", case_type="Problem", status="New",
account=self.account,
priority="Low", description="something",
created_by=self.user, closed_on="2016-05-04")
@ -158,7 +158,7 @@ class CaseFormTestCase(CaseCreation, TestCase):
def test_case_creation_same_name(self):
response = self.client.post('/cases/create/',
{'name': 'raghu',
{'name': 'robert',
'case_type': 'type',
'status': 'status',
'account': self.account,

View file

@ -2,7 +2,8 @@ from django.urls import path
from cases.views import (
CasesListView, create_case, CaseDetailView, update_case, RemoveCaseView,
CloseCaseView, select_contact, GetCasesView, AddCommentView,
UpdateCommentView, DeleteCommentView, AddAttachmentView, DeleteAttachmentsView)
UpdateCommentView, DeleteCommentView,
AddAttachmentView, DeleteAttachmentsView)
app_name = 'cases'
@ -11,7 +12,8 @@ urlpatterns = [
path('create/', create_case, name='add_case'),
path('<int:pk>/view/', CaseDetailView.as_view(), name="view_case"),
path('<int:pk>/edit_case/', update_case, name="edit_case"),
path('<int:case_id>/remove/', RemoveCaseView.as_view(), name="remove_case"),
path('<int:case_id>/remove/',
RemoveCaseView.as_view(), name="remove_case"),
path('close_case/', CloseCaseView.as_view(), name="close_case"),
path('select_contacts/', select_contact, name="select_contacts"),
@ -19,9 +21,11 @@ urlpatterns = [
path('comment/add/', AddCommentView.as_view(), name="add_comment"),
path('comment/edit/', UpdateCommentView.as_view(), name="edit_comment"),
path('comment/remove/', DeleteCommentView.as_view(), name="remove_comment"),
path('comment/remove/',
DeleteCommentView.as_view(), name="remove_comment"),
path('attachment/add/', AddAttachmentView.as_view(), name="add_attachment"),
path('attachment/add/',
AddAttachmentView.as_view(), name="add_attachment"),
path('attachment/remove/', DeleteAttachmentsView.as_view(),
name="remove_attachment"),
]

View file

@ -6,7 +6,8 @@ from django.db.models import Q
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.views.generic import CreateView, DetailView, ListView, TemplateView, View
from django.views.generic import (CreateView, DetailView,
ListView, TemplateView, View)
from cases.models import Case
from cases.forms import CaseForm, CaseCommentForm, CaseAttachmentForm
@ -101,13 +102,15 @@ def create_case(request):
for assigned_to_user in assigned_to_list:
user = get_object_or_404(User, pk=assigned_to_user)
mail_subject = 'Assigned to case.'
message = render_to_string('assigned_to/cases_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'case': case
})
email = EmailMessage(mail_subject, message, to=[user.email])
message = render_to_string(
'assigned_to/cases_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'case': case
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
@ -126,29 +129,26 @@ def create_case(request):
success_url = reverse("cases:add_case")
if request.POST.get('from_account'):
from_account = request.POST.get('from_account')
success_url = reverse("accounts:view_account", pk=from_account)
success_url = reverse("accounts:view_account", kwargs={
'pk': from_account})
return JsonResponse({'error': False, "success_url": success_url})
else:
return JsonResponse({'error': True, 'errors': form.errors})
else:
context = {}
context["case_form"] = form
context["accounts"] = accounts
if request.GET.get('view_account'):
context['account'] = get_object_or_404(
Account, id=request.GET.get('view_account'))
context["contacts"] = contacts
context["users"] = users
context["case_types"] = CASE_TYPE
context["case_priority"] = PRIORITY_CHOICE
context["case_status"] = STATUS_CHOICE
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
context["contacts_list"] = [
int(i) for i in request.POST.getlist('contacts', []) if i]
return render(request, template_name, context)
return JsonResponse({'error': True, 'errors': form.errors})
context = {}
context["case_form"] = form
context["accounts"] = accounts
if request.GET.get('view_account'):
context['account'] = get_object_or_404(
Account, id=request.GET.get('view_account'))
context["contacts"] = contacts
context["users"] = users
context["case_types"] = CASE_TYPE
context["case_priority"] = PRIORITY_CHOICE
context["case_status"] = STATUS_CHOICE
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
context["contacts_list"] = [
int(i) for i in request.POST.getlist('contacts', []) if i]
return render(request, template_name, context)
class CaseDetailView(LoginRequiredMixin, DetailView):
@ -162,7 +162,8 @@ class CaseDetailView(LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
context = super(CaseDetailView, self).get_context_data(**kwargs)
user_assgn_list = [assigned_to.id for assigned_to in context['object'].assigned_to.all()]
user_assgn_list = [
assigned_to.id for assigned_to in context['object'].assigned_to.all()]
if self.request.user == context['object'].created_by:
user_assgn_list.append(self.request.user.id)
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
@ -183,7 +184,6 @@ class CaseDetailView(LoginRequiredMixin, DetailView):
def update_case(request, pk):
case_object = Case.objects.filter(pk=pk).first()
users = User.objects.filter(is_active=True).order_by('email')
accounts = Account.objects.filter(status="open")
contacts = Contact.objects.all()
if request.user.role != "ADMIN" and not request.user.is_superuser:
@ -191,14 +191,17 @@ def update_case(request, pk):
created_by=request.user)
contacts = Contact.objects.filter(
Q(assigned_to__in=[request.user]) | Q(created_by=request.user))
kwargs_data = {"assigned_to": users, "account": accounts, "contacts": contacts}
kwargs_data = {
"assigned_to": User.objects.filter(is_active=True).order_by('email'),
"account": accounts, "contacts": contacts}
form = CaseForm(instance=case_object, **kwargs_data)
template_name = "create_cases.html"
if request.POST:
form = CaseForm(request.POST, request.FILES, instance=case_object, **kwargs_data)
form = CaseForm(request.POST, request.FILES,
instance=case_object, **kwargs_data)
if form.is_valid():
assigned_to_ids = case_object.assigned_to.all().values_list('id', flat=True)
assigned_to_ids = case_object.assigned_to.all().values_list(
'id', flat=True)
case_obj = form.save(commit=False)
case_obj.contacts.clear()
case_obj.save()
@ -210,18 +213,20 @@ def update_case(request, pk):
assigned_form_users = form.cleaned_data.get(
'assigned_to').values_list('id', flat=True)
all_members_list = list(
set(list(assigned_form_users)) - set(list(assigned_to_ids)))
set(list(assigned_form_users)) -
set(list(assigned_to_ids)))
if len(all_members_list):
if all_members_list:
for assigned_to_user in all_members_list:
user = get_object_or_404(User, pk=assigned_to_user)
mail_subject = 'Assigned to case.'
message = render_to_string('assigned_to/cases_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'case': case_obj
})
message = render_to_string(
'assigned_to/cases_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'case': case_obj
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
@ -238,7 +243,8 @@ def update_case(request, pk):
success_url = reverse("cases:list")
if request.POST.get('from_account'):
from_account = request.POST.get('from_account')
success_url = reverse("accounts:view_account", pk=from_account)
success_url = reverse("accounts:view_account", kwargs={
'pk': from_account})
if request.FILES.get('case_attachment'):
attachment = Attachments()
attachment.created_by = request.user
@ -249,37 +255,33 @@ def update_case(request, pk):
attachment.save()
return JsonResponse({'error': False, 'success_url': success_url})
return JsonResponse({'error': True, 'errors': form.errors})
context = {}
context["case_obj"] = case_object
user_assgn_list = [
assgined_to.id for assgined_to in context["case_obj"].assigned_to.all()]
else:
return JsonResponse({'error': True, 'errors': form.errors})
if request.user == case_object.created_by:
user_assgn_list.append(request.user.id)
if request.user.role != "ADMIN" and not request.user.is_superuser:
if request.user.id not in user_assgn_list:
raise PermissionDenied
context["case_form"] = form
context["accounts"] = accounts
if request.GET.get('view_account'):
context['account'] = get_object_or_404(
Account, id=request.GET.get('view_account'))
context["contacts"] = contacts
context["users"] = kwargs_data['assigned_to']
context["case_types"] = CASE_TYPE
context["case_priority"] = PRIORITY_CHOICE
context["case_status"] = STATUS_CHOICE
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
context["contacts_list"] = [
int(i) for i in request.POST.getlist('contacts', []) if i]
else:
context = {}
context["case_obj"] = case_object
user_assgn_list = [
assgined_to.id for assgined_to in context["case_obj"].assigned_to.all()]
if request.user == case_object.created_by:
user_assgn_list.append(request.user.id)
if request.user.role != "ADMIN" and not request.user.is_superuser:
if request.user.id not in user_assgn_list:
raise PermissionDenied
context["case_form"] = form
context["accounts"] = accounts
if request.GET.get('view_account'):
context['account'] = get_object_or_404(
Account, id=request.GET.get('view_account'))
context["contacts"] = contacts
context["users"] = users
context["case_types"] = CASE_TYPE
context["case_priority"] = PRIORITY_CHOICE
context["case_status"] = STATUS_CHOICE
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
context["contacts_list"] = [
int(i) for i in request.POST.getlist('contacts', []) if i]
return render(request, template_name, context)
return render(request, "create_cases.html", context)
class RemoveCaseView(LoginRequiredMixin, View):
@ -291,18 +293,20 @@ class RemoveCaseView(LoginRequiredMixin, View):
account = request.GET.get('view_account')
return redirect("accounts:view_account", pk=account)
if (
self.request.user.role == "ADMIN" or self.request.user.is_superuser or
self.request.user.role == "ADMIN" or
self.request.user.is_superuser or
self.request.user == self.object.created_by
):
self.object.delete()
return redirect("cases:list")
else:
raise PermissionDenied
raise PermissionDenied
def post(self, request, *args, **kwargs):
case_id = kwargs.get("case_id")
self.object = get_object_or_404(Case, id=case_id)
if self.request.user.role == "ADMIN" or self.request.user.is_superuser or self.request.user == self.object.created_by:
if (self.request.user.role == "ADMIN" or
self.request.user.is_superuser or
self.request.user == self.object.created_by):
self.object.delete()
if request.is_ajax():
return JsonResponse({'error': False})
@ -310,8 +314,7 @@ class RemoveCaseView(LoginRequiredMixin, View):
Q(assigned_to__in=[request.user]) | Q(created_by=request.user)).distinct().count()
data = {"case_id": case_id, "count": count}
return JsonResponse(data)
else:
raise PermissionDenied
raise PermissionDenied
class CloseCaseView(LoginRequiredMixin, View):
@ -320,15 +323,15 @@ class CloseCaseView(LoginRequiredMixin, View):
case_id = request.POST.get("case_id")
self.object = get_object_or_404(Case, id=case_id)
if (
self.request.user.role == "ADMIN" or self.request.user.is_superuser or
self.request.user.role == "ADMIN" or
self.request.user.is_superuser or
self.request.user == self.object.created_by
):
self.object.status = "Closed"
self.object.save()
data = {'status': "Closed", "cid": case_id}
return JsonResponse(data)
else:
raise PermissionDenied
raise PermissionDenied
def select_contact(request):
@ -358,7 +361,8 @@ class AddCommentView(LoginRequiredMixin, CreateView):
self.case = get_object_or_404(Case, id=request.POST.get('caseid'))
if (
request.user in self.case.assigned_to.all() or
request.user == self.case.created_by or request.user.is_superuser or
request.user == self.case.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
@ -392,7 +396,8 @@ class UpdateCommentView(LoginRequiredMixin, View):
self.comment_obj = get_object_or_404(
Comment, id=request.POST.get("commentid"))
if (
request.user == self.comment_obj.commented_by or request.user.is_superuser or
request.user == self.comment_obj.commented_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = CaseCommentForm(request.POST, instance=self.comment_obj)
@ -422,7 +427,8 @@ class DeleteCommentView(LoginRequiredMixin, View):
self.object = get_object_or_404(
Comment, id=request.POST.get("comment_id"))
if (
request.user == self.object.commented_by or request.user.is_superuser or
request.user == self.object.commented_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
self.object.delete()
@ -443,7 +449,8 @@ class AddAttachmentView(LoginRequiredMixin, CreateView):
self.case = get_object_or_404(Case, id=request.POST.get('caseid'))
if (
request.user in self.case.assigned_to.all() or
request.user == self.case.created_by or request.user.is_superuser or
request.user == self.case.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
@ -453,7 +460,8 @@ class AddAttachmentView(LoginRequiredMixin, CreateView):
return self.form_invalid(form)
data = {"error": True,
"errors": "You don't have permission to add attachment for this case."}
"errors":
"You don't have permission to add attachment for this case."}
return JsonResponse(data)
def form_valid(self, form):
@ -468,7 +476,9 @@ class AddAttachmentView(LoginRequiredMixin, CreateView):
"attachment_id": attachment.id,
"attachment": attachment.file_name,
"attachment_url": attachment.attachment.url,
"download_url": reverse('common:download_attachment', kwargs={'pk': attachment.id}),
"download_url":
reverse('common:download_attachment',
kwargs={'pk': attachment.id}),
"created_on": attachment.created_on,
"created_by": attachment.created_by.email,
"message": "attachment Created",
@ -477,7 +487,8 @@ class AddAttachmentView(LoginRequiredMixin, CreateView):
})
def form_invalid(self, form):
return JsonResponse({"error": True, "errors": form['attachment'].errors})
return JsonResponse({"error": True,
"errors": form['attachment'].errors})
class DeleteAttachmentsView(LoginRequiredMixin, View):
@ -486,7 +497,8 @@ class DeleteAttachmentsView(LoginRequiredMixin, View):
self.object = get_object_or_404(
Attachments, id=request.POST.get("attachment_id"))
if (
request.user == self.object.created_by or request.user.is_superuser or
request.user == self.object.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
self.object.delete()
@ -495,5 +507,6 @@ class DeleteAttachmentsView(LoginRequiredMixin, View):
return JsonResponse(data)
data = {"error": True,
"errors": "You don't have permission to delete this attachment."}
"errors":
"You don't have permission to delete this attachment."}
return JsonResponse(data)

View file

@ -104,13 +104,15 @@ class UserForm(forms.ModelForm):
email = self.cleaned_data.get("email")
if self.instance.id:
if self.instance.email != email:
if not User.objects.filter(email=self.cleaned_data.get("email")).exists():
if not User.objects.filter(
email=self.cleaned_data.get("email")).exists():
return self.cleaned_data.get("email")
raise forms.ValidationError('Email already exists')
else:
return self.cleaned_data.get("email")
else:
if not User.objects.filter(email=self.cleaned_data.get("email")).exists():
if not User.objects.filter(
email=self.cleaned_data.get("email")).exists():
return self.cleaned_data.get("email")
raise forms.ValidationError('User already exists with this email')
@ -173,7 +175,8 @@ class PasswordResetEmailForm(PasswordResetForm):
def clean_email(self):
email = self.cleaned_data.get('email')
if not User.objects.filter(email__iexact=email, is_active=True).exists():
if not User.objects.filter(email__iexact=email,
is_active=True).exists():
raise forms.ValidationError("User doesn't exist with this Email")
return email
@ -216,10 +219,9 @@ def find_urls(string):
website_regex_port = "^https?://[A-Za-z0-9.-]+\.[A-Za-z]{2,63}:[0-9]{2,4}$"
url = re.findall(website_regex, string)
url_port = re.findall(website_regex_port, string)
if len(url) > 0 and url[0] != '':
if url and url[0] != '':
return url
else:
return url_port
return url_port
class APISettingsForm(forms.ModelForm):
@ -243,9 +245,11 @@ class APISettingsForm(forms.ModelForm):
def clean_website(self):
website = self.data.get('website')
if website and not (website.startswith('http://') or website.startswith('https://')):
if website and not (website.startswith('http://') or
website.startswith('https://')):
raise forms.ValidationError("Please provide valid schema")
if not len(find_urls(website)) > 0:
raise forms.ValidationError(
"Please provide a valid URL with schema and without trailing slash - Example: http://google.com")
"Please provide a valid URL with schema and without trailing \
slash - Example: http://google.com")
return website

View file

@ -3,10 +3,13 @@ import os
import time
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, UserManager
from django.contrib.auth.models import (AbstractBaseUser, PermissionsMixin,
UserManager)
from common.templatetags.common_tags import (
is_document_file_image, is_document_file_audio, is_document_file_video, is_document_file_pdf,
is_document_file_code, is_document_file_text, is_document_file_sheet, is_document_file_zip
is_document_file_image, is_document_file_audio,
is_document_file_video, is_document_file_pdf,
is_document_file_code, is_document_file_text,
is_document_file_sheet, is_document_file_zip
)
from common.utils import COUNTRIES, ROLES
@ -51,7 +54,7 @@ class User(AbstractBaseUser, PermissionsMixin):
full_name = self.email
return full_name
def __unicode__(self):
def __str__(self):
return self.email
class Meta:
@ -113,15 +116,25 @@ class Comment(models.Model):
commented_by = models.ForeignKey(
User, on_delete=models.CASCADE, blank=True, null=True)
account = models.ForeignKey(
'accounts.Account', blank=True, null=True, related_name="accounts_comments", on_delete=models.CASCADE)
lead = models.ForeignKey('leads.Lead', blank=True, null=True,
related_name="leads", on_delete=models.CASCADE)
'accounts.Account', blank=True, null=True,
related_name="accounts_comments",
on_delete=models.CASCADE)
lead = models.ForeignKey('leads.Lead',
blank=True, null=True,
related_name="leads",
on_delete=models.CASCADE)
opportunity = models.ForeignKey(
'opportunity.Opportunity', blank=True, null=True, related_name="opportunity_comments", on_delete=models.CASCADE)
'opportunity.Opportunity', blank=True,
null=True, related_name="opportunity_comments",
on_delete=models.CASCADE)
contact = models.ForeignKey(
'contacts.Contact', blank=True, null=True, related_name="contact_comments", on_delete=models.CASCADE)
'contacts.Contact', blank=True,
null=True, related_name="contact_comments",
on_delete=models.CASCADE)
user = models.ForeignKey(
'User', blank=True, null=True, related_name="user_comments", on_delete=models.CASCADE)
'User', blank=True, null=True,
related_name="user_comments",
on_delete=models.CASCADE)
def get_files(self):
return Comment_Files.objects.filter(comment_id=self)
@ -142,22 +155,30 @@ class Comment_Files(models.Model):
class Attachments(models.Model):
created_by = models.ForeignKey(
User, related_name='attachment_created_by', on_delete=models.SET_NULL, null=True)
User, related_name='attachment_created_by',
on_delete=models.SET_NULL, null=True)
file_name = models.CharField(max_length=60)
created_on = models.DateTimeField(_("Created on"), auto_now_add=True)
attachment = models.FileField(
max_length=1001, upload_to='attachments/%Y/%m/')
lead = models.ForeignKey(
'leads.Lead', null=True, blank=True, related_name='lead_attachment', on_delete=models.CASCADE)
'leads.Lead', null=True,
blank=True, related_name='lead_attachment',
on_delete=models.CASCADE)
account = models.ForeignKey(
'accounts.Account', null=True, blank=True, related_name='account_attachment', on_delete=models.CASCADE)
'accounts.Account', null=True, blank=True,
related_name='account_attachment', on_delete=models.CASCADE)
contact = models.ForeignKey(
'contacts.Contact', on_delete=models.CASCADE, related_name='contact_attachment', blank=True, null=True)
'contacts.Contact', on_delete=models.CASCADE,
related_name='contact_attachment',
blank=True, null=True)
opportunity = models.ForeignKey(
'opportunity.Opportunity', blank=True, null=True, on_delete=models.CASCADE,
'opportunity.Opportunity', blank=True,
null=True, on_delete=models.CASCADE,
related_name='opportunity_attachment')
case = models.ForeignKey(
'cases.Case', blank=True, null=True, on_delete=models.CASCADE, related_name='case_attachment')
'cases.Case', blank=True, null=True,
on_delete=models.CASCADE, related_name='case_attachment')
def file_type(self):
name_ext_list = self.attachment.url.split(".")
@ -185,8 +206,7 @@ class Attachments(models.Model):
def get_file_type_display(self):
if self.attachment:
return self.file_type()[1]
else:
return None
return None
def document_path(self, filename):
@ -204,7 +224,8 @@ class Document(models.Model):
title = models.CharField(max_length=1000, blank=True, null=True)
document_file = models.FileField(upload_to=document_path, max_length=5000)
created_by = models.ForeignKey(
User, related_name='document_uploaded', on_delete=models.SET_NULL, null=True)
User, related_name='document_uploaded',
on_delete=models.SET_NULL, null=True)
created_on = models.DateTimeField(auto_now_add=True)
status = models.CharField(
choices=DOCUMENT_STATUS_CHOICE, max_length=64, default='active')
@ -249,7 +270,8 @@ class APISettings(models.Model):
User, related_name='lead_assignee_users')
tags = models.ManyToManyField('accounts.Tags', blank=True)
created_by = models.ForeignKey(
User, related_name='settings_created_by', on_delete=models.SET_NULL, null=True)
User, related_name='settings_created_by',
on_delete=models.SET_NULL, null=True)
created_on = models.DateTimeField(auto_now_add=True)
def __str__(self):

View file

@ -183,4 +183,5 @@
$('#docs_filter').submit();
});
</script>
{% endblock js_block %}
{% endblock js_block %}

View file

@ -148,3 +148,4 @@
</script>
{% endblock js_block %}

View file

@ -85,4 +85,5 @@
window.location = $(this).attr('href')
});
</script>
{% endblock js_block %}
{% endblock js_block %}

View file

@ -8,13 +8,29 @@ from common.forms import *
class ObjectsCreation(object):
def setUp(self):
self.client = Client()
self.user = User.objects.create(first_name="admin", username='admin', email='admin@micropyramid.com',
is_staff=True, is_admin=True, is_superuser=True, is_active=True, role='ADMIN')
# self.user = User.objects.create(first_name="admin",
# username='admin',
# email='admin@micropyramid.com',
# is_staff=True,
# is_admin=True,
# is_superuser=True, is_active=True)
self.user = User.objects.create(first_name="admin",
username='admin',
email='admin@micropyramid.com',
is_staff=True,
is_admin=True,
is_superuser=True,
is_active=True, role='ADMIN')
self.user.set_password('admin123')
self.user.save()
self.user = User.objects.create(first_name="pavan", username='pavan', email='pavan@micropyramid.com',
is_staff=True, is_admin=True, is_superuser=True, is_active=False)
self.user.set_password('pavan123')
self.user = User.objects.create(first_name="paul",
username='paul',
email='paul@micropyramid.com',
is_staff=True, is_admin=True,
is_superuser=True, is_active=False)
self.user.set_password('paul123')
self.user.save()
# self.user
user_login = self.client.login(
@ -92,7 +108,7 @@ class ForgotPasswordViewTestCase(ObjectsCreation, TestCase):
class LoginViewTestCase(ObjectsCreation, TestCase):
# user2 = User.objects.create(first_name="pavan", username='pavan', email='pavan@micropyramid.com',
# user2 = User.objects.create(first_name="paul", username='paul', email='paul@micropyramid.com',
# is_staff=True, is_admin=True, is_superuser=True, is_active=False)
def test_login_post(self):
self.client.logout()
@ -133,20 +149,20 @@ class LoginViewTestCase(ObjectsCreation, TestCase):
class UserTestCase(ObjectsCreation, TestCase):
def test_user_create_url(self):
response = self.client.get('/users/create/', {
'first_name': 'meghana',
'last_name': "reddy",
'username': 'meghana',
'email': 'meghana@micropyramid.com',
'password': 'meghana123'})
'first_name': 'micheal',
'last_name': "clark",
'username': 'micheal',
'email': 'micheal@micropyramid.com',
'password': 'micheal123'})
self.assertEqual(response.status_code, 200)
def test_user_create_html(self):
response = self.client.get('/users/create/', {
'first_name': 'meghana',
'first_name': 'micheal',
'last_name': "",
'username': 'meghana',
'username': 'micheal',
'email': '',
'password': 'meghana123'})
'password': 'micheal123'})
self.assertTemplateUsed(response, 'create.html')
@ -158,7 +174,7 @@ class UserListTestCase(ObjectsCreation, TestCase):
response = self.client.get('/users/list/')
# get_img_url = self.users.filter()
get_user = User.objects.get(email='admin@micropyramid.com')
self.assertEqual(get_user.email, get_user.__unicode__())
self.assertEqual(get_user.email, get_user.__str__())
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'list.html')
@ -193,9 +209,11 @@ class UserUpdateTestCase(ObjectsCreation, TestCase):
def test_accounts_update_post(self):
response = self.client.post('/users/' + str(self.user.id) + '/edit/',
{'first_name': "meghana", 'user_name': 'meghana',
{'first_name': "micheal",
'user_name': 'micheal',
'email': "abc@micropyramid",
'role': "USER", 'is_superuser': False})
'role': "USER",
'is_superuser': False})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'create.html')
@ -226,6 +244,14 @@ class ProfileViewTestCase(ObjectsCreation, TestCase):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
# def test_context_data(self):
# user_login = self.client.login(
# username='admin@micropyramid.com', password='admin123')
# url = "/profile/"
# response = self.client.get(url)
# print(response, "hello")
# self.assertContains(response,'admin@micropyramid.com')
class UserDetailView(ObjectsCreation, TestCase):
def test_user_detail(self):
@ -246,11 +272,14 @@ class DocumentDetailView(ObjectsCreation, TestCase):
class CreateCommentFile(TestCase):
def test_invalid_user_form(self):
fields = ['email', 'first_name', 'last_name',
'username', 'role', 'profile_pic']
user1 = User.objects.create(username='teja',
first_name='teja',
last_name='reddy',
fields = ['email',
'first_name',
'last_name',
'username', 'role',
'profile_pic']
user1 = User.objects.create(username='robert',
first_name='robert',
last_name='clark',
email='tr@mp.com',
role='USER',
profile_pic="",
@ -261,7 +290,7 @@ class CreateCommentFile(TestCase):
'profile_pic': user1.profile_pic,
'password': user1.password}
form = UserForm(data=data)
userr = User.objects.get(username='teja')
userr = User.objects.get(username='robert')
# print(userr)
self.assertEqual(len(userr.password), 3)
self.assertFalse(form.is_valid())

View file

@ -2,11 +2,16 @@ from django.contrib.auth import views as auth_views
from django.urls import path
from common.views import (
HomeView, LoginView, ForgotPasswordView, LogoutView, ChangePasswordView, ProfileView,
UsersListView, CreateUserView, UpdateUserView, UserDetailView, UserDeleteView, PasswordResetView,
DocumentListView, document_create, document_update, DocumentDetailView, DocumentDeleteView,
download_document, change_user_status, download_attachment, add_comment, edit_comment, remove_comment,
api_settings, add_api_settings, view_api_settings, update_api_settings, delete_api_settings,
HomeView, LoginView, ForgotPasswordView, LogoutView,
ChangePasswordView, ProfileView,
UsersListView, CreateUserView, UpdateUserView, UserDetailView,
UserDeleteView, PasswordResetView,
DocumentListView, document_create, document_update,
DocumentDetailView, DocumentDeleteView,
download_document, change_user_status, download_attachment,
add_comment, edit_comment, remove_comment,
api_settings, add_api_settings, view_api_settings,
update_api_settings, delete_api_settings,
change_passsword_by_admin, google_login
)
from django.conf.urls.static import static
@ -19,9 +24,11 @@ app_name = 'common'
urlpatterns = [
path('', HomeView.as_view(), name='home'),
path('login/', LoginView.as_view(), name='login'),
path('forgot-password/', ForgotPasswordView.as_view(), name='forgot_password'),
path('forgot-password/',
ForgotPasswordView.as_view(), name='forgot_password'),
path('logout/', LogoutView.as_view(), name='logout'),
path('change-password/', ChangePasswordView.as_view(), name='change_password'),
path('change-password/',
ChangePasswordView.as_view(), name='change_password'),
path('profile/', ProfileView.as_view(), name='profile'),
# User views
@ -29,14 +36,17 @@ urlpatterns = [
path('users/create/', CreateUserView.as_view(), name='create_user'),
path('users/<int:pk>/edit/', UpdateUserView.as_view(), name="edit_user"),
path('users/<int:pk>/view/', UserDetailView.as_view(), name='view_user'),
path('users/<int:pk>/delete/', UserDeleteView.as_view(), name='remove_user'),
path('users/<int:pk>/delete/',
UserDeleteView.as_view(), name='remove_user'),
path(
'password-reset/', PasswordResetView.as_view(), name='password_reset'),
path('password-reset/done/', auth_views.PasswordResetDoneView.as_view(),
name='password_reset_done'),
path(
'reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
'reset/<uidb64>/<token>/',
auth_views.PasswordResetConfirmView.as_view(),
name='password_reset_confirm'),
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(),
name='password_reset_complete'),
@ -44,7 +54,8 @@ urlpatterns = [
path('documents/list/', DocumentListView.as_view(), name='doc_list'),
path('documents/create/', document_create, name='create_doc'),
path('documents/<int:pk>/edit/', document_update, name="edit_doc"),
path('documents/<int:pk>/view/', DocumentDetailView.as_view(), name='view_doc'),
path('documents/<int:pk>/view/',
DocumentDetailView.as_view(), name='view_doc'),
path('documents/<int:pk>/delete/',
DocumentDeleteView.as_view(), name='remove_doc'),
@ -56,7 +67,8 @@ urlpatterns = [
path('attachments/<int:pk>/download/',
download_attachment, name='download_attachment'),
path('user/status/<int:pk>/', change_user_status, name='change_user_status'),
path('user/status/<int:pk>/',
change_user_status, name='change_user_status'),
path('comment/add/', add_comment, name="add_comment"),
path('comment/<int:pk>/edit/', edit_comment, name="edit_comment"),
@ -65,7 +77,8 @@ urlpatterns = [
# settings
path('api/settings/', api_settings, name="api_settings"),
path('api/settings/add/', add_api_settings, name="add_api_settings"),
path('api/settings/<int:pk>/', view_api_settings, name="view_api_settings"),
path('api/settings/<int:pk>/',
view_api_settings, name="view_api_settings"),
path('api/settings/<int:pk>/update/',
update_api_settings, name="update_api_settings"),
path('api/settings/<int:pk>/delete/',

View file

@ -1,3 +1,4 @@
import pytz
from django.utils.translation import ugettext_lazy as _
@ -51,7 +52,7 @@ LEAD_STATUS = (
('in process', 'In Process'),
('converted', 'Converted'),
('recycled', 'Recycled'),
('dead', 'Closed')
('closed', 'Closed')
)
@ -579,3 +580,11 @@ def get_client_ip(request):
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def convert_to_custom_timezone(custom_date, custom_timezone, to_utc=False):
user_time_zone = pytz.timezone(custom_timezone)
if to_utc:
custom_date = user_time_zone.localize(custom_date.replace(tzinfo=None))
user_time_zone = pytz.UTC
return custom_date.astimezone(user_time_zone)

View file

@ -7,13 +7,20 @@ from django.db.models import Q
from django.core.mail import EmailMessage
from django.contrib.auth.hashers import check_password
from django.contrib.auth.mixins import LoginRequiredMixin, AccessMixin
from django.http import HttpResponseRedirect, JsonResponse, HttpResponse, Http404
from django.http import (HttpResponseRedirect,
JsonResponse, HttpResponse,
Http404)
from django.shortcuts import render, redirect, get_object_or_404
from django.views.generic import (
CreateView, UpdateView, DetailView, TemplateView, View, DeleteView)
from common.models import User, Document, Attachments, Comment, APISettings, Google
from common.models import (User, Document, Attachments,
Comment,
APISettings,
Google)
from common.forms import (
UserForm, LoginForm, ChangePasswordForm, PasswordResetEmailForm, DocumentForm, UserCommentForm,
UserForm, LoginForm,
ChangePasswordForm, PasswordResetEmailForm,
DocumentForm, UserCommentForm,
APISettingsForm
)
from django.contrib.auth.views import PasswordResetView
@ -26,6 +33,8 @@ from accounts.models import Account, Tags
from leads.models import Lead
from django.template.loader import render_to_string
from django.core.exceptions import PermissionDenied
import boto3
import botocore
def handler404(request, exception):
@ -45,7 +54,8 @@ class AdminRequiredMixin(AccessMixin):
if not request.user.role == "ADMIN":
if not request.user.is_superuser:
return self.handle_no_permission()
return super(AdminRequiredMixin, self).dispatch(request, *args, **kwargs)
return super(AdminRequiredMixin, self).dispatch(
request, *args, **kwargs)
class HomeView(LoginRequiredMixin, TemplateView):
@ -55,18 +65,21 @@ class HomeView(LoginRequiredMixin, TemplateView):
context = super(HomeView, self).get_context_data(**kwargs)
accounts = Account.objects.filter(status="open")
contacts = Contact.objects.all()
leads = Lead.objects.exclude(status='converted' and 'dead')
leads = Lead.objects.exclude(status='converted')
opportunities = Opportunity.objects.all()
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
pass
else:
accounts = accounts.filter(created_by=self.request.user.id)
contacts = contacts.filter(
Q(assigned_to__id__in=[self.request.user.id]) | Q(created_by=self.request.user.id))
Q(assigned_to__id__in=[self.request.user.id]) |
Q(created_by=self.request.user.id))
leads = leads.filter(
Q(assigned_to__id__in=[self.request.user.id]) | Q(created_by=self.request.user.id)).exclude(status='dead')
Q(assigned_to__id__in=[self.request.user.id]) |
Q(created_by=self.request.user.id)).exclude(status='closed')
opportunities = opportunities.filter(
Q(assigned_to__id__in=[self.request.user.id]) | Q(created_by=self.request.user.id))
Q(assigned_to__id__in=[self.request.user.id]) |
Q(created_by=self.request.user.id))
context["accounts"] = accounts
context["contacts_count"] = contacts.count()
@ -88,7 +101,8 @@ class ChangePasswordView(LoginRequiredMixin, TemplateView):
form = ChangePasswordForm(request.POST)
if form.is_valid():
user = request.user
if not check_password(request.POST.get('CurrentPassword'), user.password):
if not check_password(request.POST.get('CurrentPassword'),
user.password):
error = "Invalid old password"
else:
user.set_password(request.POST.get('Newpassword'))
@ -97,13 +111,16 @@ class ChangePasswordView(LoginRequiredMixin, TemplateView):
return HttpResponseRedirect('/')
else:
errors = form.errors
return render(request, "change_password.html", {'error': error, 'errors': errors, 'change_password_form': form})
return render(request, "change_password.html",
{'error': error, 'errors': errors,
'change_password_form': form})
class ProfileView(LoginRequiredMixin, TemplateView):
template_name = "profile.html"
def get_context_data(self, **kwargs):
context = super(ProfileView, self).get_context_data(**kwargs)
context["user_obj"] = self.request.user
return context
@ -138,41 +155,41 @@ class LoginView(TemplateView):
if user is not None:
login(request, user)
return HttpResponseRedirect('/')
else:
return render(request, "login.html", {
"ENABLE_GOOGLE_LOGIN": settings.ENABLE_GOOGLE_LOGIN,
"GP_CLIENT_SECRET": settings.GP_CLIENT_SECRET,
"GP_CLIENT_ID": settings.GP_CLIENT_ID,
"error": True,
"message": "Your username and password didn't match. Please try again."
})
else:
return render(request, "login.html", {
"ENABLE_GOOGLE_LOGIN": settings.ENABLE_GOOGLE_LOGIN,
"GP_CLIENT_SECRET": settings.GP_CLIENT_SECRET,
"GP_CLIENT_ID": settings.GP_CLIENT_ID,
"error": True,
"message": "Your Account is inactive. Please Contact Administrator"
"message":
"Your username and password didn't match. \
Please try again."
})
else:
return render(request, "login.html", {
"ENABLE_GOOGLE_LOGIN": settings.ENABLE_GOOGLE_LOGIN,
"GP_CLIENT_SECRET": settings.GP_CLIENT_SECRET,
"GP_CLIENT_ID": settings.GP_CLIENT_ID,
"error": True,
"message": "Your Account is not Found. Please Contact Administrator"
"message":
"Your Account is inactive. Please Contact Administrator"
})
else:
return render(request, "login.html", {
"ENABLE_GOOGLE_LOGIN": settings.ENABLE_GOOGLE_LOGIN,
"GP_CLIENT_SECRET": settings.GP_CLIENT_SECRET,
"GP_CLIENT_ID": settings.GP_CLIENT_ID,
# "error": True,
# "message": "Your username and password didn't match. Please try again."
"form": form
"error": True,
"message":
"Your Account is not Found. Please Contact Administrator"
})
return render(request, "login.html", {
"ENABLE_GOOGLE_LOGIN": settings.ENABLE_GOOGLE_LOGIN,
"GP_CLIENT_SECRET": settings.GP_CLIENT_SECRET,
"GP_CLIENT_ID": settings.GP_CLIENT_ID,
# "error": True,
# "message": "Your username and password didn't match. Please try again."
"form": form
})
class ForgotPasswordView(TemplateView):
template_name = "forgot_password.html"
@ -264,7 +281,8 @@ class UserDetailView(AdminRequiredMixin, DetailView):
users_data.append(assigned_dict)
context.update({
"user_obj": user_obj,
"opportunity_list": Opportunity.objects.filter(assigned_to=user_obj.id),
"opportunity_list":
Opportunity.objects.filter(assigned_to=user_obj.id),
"contacts": Contact.objects.filter(assigned_to=user_obj.id),
"cases": Case.objects.filter(assigned_to=user_obj.id),
# "accounts": Account.objects.filter(assigned_to=user_obj.id),
@ -282,14 +300,16 @@ class UpdateUserView(LoginRequiredMixin, UpdateView):
def form_valid(self, form):
user = form.save(commit=False)
if self.request.is_ajax():
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if (self.request.user.role != "ADMIN" and not
self.request.user.is_superuser):
if self.request.user.id != self.object.id:
data = {'error_403': True, 'error': True}
return JsonResponse(data)
if user.role == "USER":
user.is_superuser = False
user.save()
if self.request.user.role == "ADMIN" and self.request.user.is_superuser:
if (self.request.user.role == "ADMIN" and
self.request.user.is_superuser):
if self.request.is_ajax():
data = {'success_url': reverse_lazy(
'common:users_list'), 'error': False}
@ -349,17 +369,14 @@ def document_create(request):
data = {'success_url': reverse_lazy(
'common:doc_list'), 'error': False}
return JsonResponse(data)
else:
return JsonResponse({'error': True, 'errors': form.errors})
else:
context = {}
context["doc_form"] = form
context["users"] = users
context["sharedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
context["errors"] = form.errors
return render(request, template_name, context)
return JsonResponse({'error': True, 'errors': form.errors})
context = {}
context["doc_form"] = form
context["users"] = users
context["sharedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
context["errors"] = form.errors
return render(request, template_name, context)
class DocumentListView(LoginRequiredMixin, TemplateView):
@ -373,9 +390,12 @@ class DocumentListView(LoginRequiredMixin, TemplateView):
queryset = queryset
else:
if self.request.user.documents():
doc_ids = self.request.user.documents().values_list('id', flat=True)
doc_ids = self.request.user.documents().values_list('id',
flat=True)
shared_ids = queryset.filter(
Q(status='active') & Q(shared_to__id__in=[self.request.user.id])).values_list('id', flat=True)
Q(status='active') &
Q(shared_to__id__in=[self.request.user.id])).values_list(
'id', flat=True)
queryset = queryset.filter(
Q(id__in=doc_ids) | Q(id__in=shared_ids))
else:
@ -407,7 +427,8 @@ class DocumentListView(LoginRequiredMixin, TemplateView):
search = False
if (
self.request.POST.get('doc_name') or self.request.POST.get('status') or
self.request.POST.get('doc_name') or
self.request.POST.get('status') or
self.request.POST.get('shared_to')
):
search = True
@ -439,7 +460,8 @@ def document_update(request, pk):
document = Document.objects.filter(id=pk).first()
if request.POST:
form = DocumentForm(request.POST, request.FILES, instance=document, users=users)
form = DocumentForm(request.POST, request.FILES,
instance=document, users=users)
if form.is_valid():
doc = form.save(commit=False)
doc.save()
@ -451,21 +473,17 @@ def document_update(request, pk):
data = {'success_url': reverse_lazy(
'common:doc_list'), 'error': False}
return JsonResponse(data)
else:
return JsonResponse({'error': True, 'errors': form.errors})
else:
context = {}
context["doc_obj"] = document
context["doc_form"] = form
context["doc_file_name"] = context["doc_obj"].document_file.name.split(
"/")[-1]
context["users"] = users
context["sharedto_list"] = [
int(i) for i in request.POST.getlist('shared_to', []) if i]
context["errors"] = form.errors
return render(request, template_name, context)
return JsonResponse({'error': True, 'errors': form.errors})
context = {}
context["doc_obj"] = document
context["doc_form"] = form
context["doc_file_name"] = context["doc_obj"].document_file.name.split(
"/")[-1]
context["users"] = users
context["sharedto_list"] = [
int(i) for i in request.POST.getlist('shared_to', []) if i]
context["errors"] = form.errors
return render(request, template_name, context)
class UpdateDocumentView(LoginRequiredMixin, UpdateView):
@ -479,7 +497,8 @@ class UpdateDocumentView(LoginRequiredMixin, UpdateView):
raise PermissionDenied
self.users = User.objects.filter(is_active=True).order_by('email')
return super(UpdateDocumentView, self).dispatch(request, *args, **kwargs)
return super(UpdateDocumentView, self).dispatch(
request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(UpdateDocumentView, self).get_form_kwargs()
@ -526,10 +545,12 @@ class DocumentDetailView(LoginRequiredMixin, DetailView):
def dispatch(self, request, *args, **kwargs):
if not request.user.role == 'ADMIN':
if (not request.user == Document.objects.get(id=kwargs['pk']).created_by):
if (not request.user ==
Document.objects.get(id=kwargs['pk']).created_by):
raise PermissionDenied
return super(DocumentDetailView, self).dispatch(request, *args, **kwargs)
return super(DocumentDetailView, self).dispatch(
request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(DocumentDetailView, self).get_context_data(**kwargs)
@ -548,29 +569,82 @@ def download_document(request, pk):
if (not request.user == doc_obj.created_by and
request.user not in doc_obj.shared_to.all()):
raise PermissionDenied
path = doc_obj.document_file.path
file_path = os.path.join(settings.MEDIA_ROOT, path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(
fh.read(), content_type="application/vnd.ms-excel")
response['Content-Disposition'] = 'inline; filename=' + \
os.path.basename(file_path)
if settings.STORAGE_TYPE == "normal":
# print('no no no no')
path = doc_obj.document_file.path
file_path = os.path.join(settings.MEDIA_ROOT, path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(
fh.read(), content_type="application/vnd.ms-excel")
response['Content-Disposition'] = 'inline; filename=' + \
os.path.basename(file_path)
return response
else:
file_path = doc_obj.document_file
file_name = doc_obj.title
# print(file_path)
# print(file_name)
BUCKET_NAME = "django-crm-demo"
KEY = str(file_path)
s3 = boto3.resource('s3')
try:
s3.Bucket(BUCKET_NAME).download_file(KEY, file_name)
# print('got it')
with open(file_name, 'rb') as fh:
response = HttpResponse(
fh.read(), content_type="application/vnd.ms-excel")
response['Content-Disposition'] = 'inline; filename=' + \
os.path.basename(file_name)
os.remove(file_name)
return response
except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] == "404":
print("The object does not exist.")
else:
raise
return path
raise Http404
def download_attachment(request, pk):
attachment_obj = Attachments.objects.filter(id=pk).last()
path = attachment_obj.attachment.path
file_path = os.path.join(settings.MEDIA_ROOT, path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(
fh.read(), content_type="application/vnd.ms-excel")
response['Content-Disposition'] = 'inline; filename=' + \
os.path.basename(file_path)
return response
if attachment_obj:
if settings.STORAGE_TYPE == "normal":
path = attachment_obj.attachment.path
file_path = os.path.join(settings.MEDIA_ROOT, path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(
fh.read(), content_type="application/vnd.ms-excel")
response['Content-Disposition'] = 'inline; filename=' + \
os.path.basename(file_path)
return response
else:
file_path = attachment_obj.attachment
file_name = attachment_obj.file_name
# print(file_path)
# print(file_name)
BUCKET_NAME = "django-crm-demo"
KEY = str(file_path)
s3 = boto3.resource('s3')
try:
s3.Bucket(BUCKET_NAME).download_file(KEY, file_name)
with open(file_name, 'rb') as fh:
response = HttpResponse(
fh.read(), content_type="application/vnd.ms-excel")
response['Content-Disposition'] = 'inline; filename=' + \
os.path.basename(file_name)
os.remove(file_name)
return response
except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] == "404":
print("The object does not exist.")
else:
raise
# if file_path:
# print('yes tus pus')
raise Http404
@ -598,8 +672,7 @@ def add_comment(request):
"commented_on": comment.commented_on,
"commented_by": comment.commented_by.email
})
else:
return JsonResponse({"error": form.errors})
return JsonResponse({"error": form.errors})
def edit_comment(request, pk):
@ -614,8 +687,7 @@ def edit_comment(request, pk):
"comment_id": comment_obj.id,
"comment": comment_obj.comment,
})
else:
return JsonResponse({"error": form['comment'].errors})
return JsonResponse({"error": form['comment'].errors})
data = {'error': "You don't have permission to edit this comment."}
return JsonResponse(data)
@ -667,12 +739,9 @@ def add_api_settings(request):
success_url = reverse_lazy("common:add_api_settings")
data = {'success_url': success_url, 'error': False}
return JsonResponse(data)
else:
return JsonResponse({'error': True, 'errors': form.errors})
else:
data = {'form': form, "setting": api_settings,
'users': users, 'assign_to_list': assign_to_list}
return JsonResponse({'error': True, 'errors': form.errors})
data = {'form': form, "setting": api_settings,
'users': users, 'assign_to_list': assign_to_list}
return render(request, 'settings/create.html', data)
@ -714,14 +783,14 @@ def update_api_settings(request, pk):
success_url = reverse_lazy("common:add_api_settings")
data = {'success_url': success_url, 'error': False}
return JsonResponse(data)
else:
return JsonResponse({'error': True, 'errors': form.errors})
else:
data = {
'form': form, "setting": api_settings, 'users': users, 'assign_to_list': assign_to_list,
'assigned_to_list': json.dumps([setting.id for setting in api_settings.lead_assigned_to.all() if setting])
}
return JsonResponse({'error': True, 'errors': form.errors})
data = {
'form': form, "setting": api_settings,
'users': users, 'assign_to_list': assign_to_list,
'assigned_to_list':
json.dumps(
[setting.id for setting in api_settings.lead_assigned_to.all() if setting])
}
return render(request, 'settings/update.html', data)
@ -731,7 +800,8 @@ def delete_api_settings(request, pk):
api_settings.delete()
data = {"error": False, "response": "Successfully Deleted!"}
else:
data = {"error": True, "response": "Object Not Found!"}
data = {"error": True,
"response": "Object Not Found!"}
# return JsonResponse(data)
return redirect('common:api_settings')
@ -743,15 +813,16 @@ def change_passsword_by_admin(request):
user.set_password(request.POST.get("new_passwoord"))
user.save()
mail_subject = 'Crm Account Password Changed'
message = "<h3><b>hello</b> <i>" + user.username + "</i></h3><br><h2><p> <b>Your account password has been changed ! </b></p></h2>" + \
"<br> <p><b> New Password</b> : <b><i>" + \
message = "<h3><b>hello</b> <i>" + user.username +\
"</i></h3><br><h2><p> <b>Your account password has been changed !\
</b></p></h2>" \
+ "<br> <p><b> New Password</b> : <b><i>" + \
request.POST.get("new_passwoord") + "</i><br></p>"
email = EmailMessage(mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
return HttpResponseRedirect('/users/list/')
else:
raise PermissionDenied
raise PermissionDenied
def google_login(request):
@ -759,7 +830,8 @@ def google_login(request):
params = {
'grant_type': 'authorization_code',
'code': request.GET.get('code'),
'redirect_uri': 'http://' + request.META['HTTP_HOST'] + reverse('common:google_login'),
'redirect_uri': 'http://' +
request.META['HTTP_HOST'] + reverse('common:google_login'),
'client_id': settings.GP_CLIENT_ID,
'client_secret': settings.GP_CLIENT_SECRET
}
@ -807,7 +879,7 @@ def google_login(request):
role="USER"
)
google, created = Google.objects.get_or_create(user=user)
google, _ = Google.objects.get_or_create(user=user)
google.user = user
google.google_url = link
google.verified_email = verified_email
@ -827,16 +899,14 @@ def google_login(request):
if request.GET.get('state') != '1235dfghjkf123':
return HttpResponseRedirect(request.GET.get('state'))
else:
return HttpResponseRedirect(reverse("common:home"))
return HttpResponseRedirect(reverse("common:home"))
if request.GET.get('next'):
next_url = request.GET.get('next')
else:
if request.GET.get('next'):
next_url = request.GET.get('next')
else:
next_url = '1235dfghjkf123'
rty = "https://accounts.google.com/o/oauth2/auth?client_id=" + \
settings.GP_CLIENT_ID + "&response_type=code"
rty += "&scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&redirect_uri=" + \
'http://' + request.META['HTTP_HOST'] + \
reverse('common:google_login') + "&state=" + next_url
return HttpResponseRedirect(rty)
next_url = '1235dfghjkf123'
rty = "https://accounts.google.com/o/oauth2/auth?client_id=" + \
settings.GP_CLIENT_ID + "&response_type=code"
rty += "&scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&redirect_uri=" \
+ 'http://' + request.META['HTTP_HOST'] + \
reverse('common:google_login') + "&state=" + next_url
return HttpResponseRedirect(rty)

View file

@ -24,7 +24,9 @@ class ContactForm(forms.ModelForm):
class Meta:
model = Contact
fields = (
'assigned_to', 'first_name', 'last_name', 'email', 'phone', 'address', 'description'
'assigned_to', 'first_name',
'last_name', 'email',
'phone', 'address', 'description'
)

View file

@ -11,12 +11,14 @@ class Contact(models.Model):
email = models.EmailField(unique=True)
phone = PhoneNumberField(null=True, unique=True)
address = models.ForeignKey(
Address, related_name='adress_contacts', on_delete=models.CASCADE, blank=True, null=True)
Address, related_name='adress_contacts',
on_delete=models.CASCADE, blank=True, null=True)
description = models.TextField(blank=True, null=True)
assigned_to = models.ManyToManyField(
User, related_name='contact_assigned_users')
created_by = models.ForeignKey(
User, related_name='contact_created_by', on_delete=models.SET_NULL, null=True)
User, related_name='contact_created_by',
on_delete=models.SET_NULL, null=True)
created_on = models.DateTimeField(_("Created on"), auto_now_add=True)
is_active = models.BooleanField(default=False)

View file

@ -10,16 +10,16 @@ class ContactObjectsCreation(object):
def setUp(self):
self.client = Client()
self.user = User.objects.create(
first_name="navaneetha",
username='navaneetha',
first_name="Nathan",
username='Nathan',
email="n@mp.com",
role="ADMIN")
self.user.set_password('navi123')
self.user.save()
self.address = Address.objects.create(
street="kphb 5th phase",
city="hyd",
state="telanagana",
street="5th phase",
city="Orlando",
state="Florida",
postcode=502279, country="AD")
self.contact = Contact.objects.create(
@ -31,7 +31,7 @@ class ContactObjectsCreation(object):
created_by=self.user)
self.contact.assigned_to.add(self.user)
self.case = Case.objects.create(
name="raghu",
name="lucy",
case_type="Problem",
status="New",
priority="Low",
@ -52,6 +52,9 @@ class ContactObjectsCreation(object):
class ContactObjectsCreation_Count(ContactObjectsCreation, TestCase):
def test_contact_object_creation(self):
c = Contact.objects.count()
con = Contact.objects.filter(id=self.contact.id)
# print(self.contact.first_name)
self.assertEqual(str(con.last()), self.contact.first_name)
self.assertEqual(c, 1)
def test_address_object_creation(self):
@ -76,7 +79,7 @@ class ContactViewsTestCase(ContactObjectsCreation, TestCase):
def test_contacts_create(self):
response = self.client.post('/contacts/create/', {
'first_name': 'contact',
'last_name': 'reddy',
'last_name': 'george',
'email': 'meg@gmail.com',
'phone': '+917898901234',
'address': self.address.id,
@ -96,6 +99,11 @@ class ContactViewsTestCase(ContactObjectsCreation, TestCase):
response = self.client.get(reverse("contacts:list"))
self.assertEqual(response.status_code, 200)
def test_contacts_delete_get(self):
response = self.client.get(
'/contacts/' + str(self.contact.id) + '/delete/')
self.assertEqual(response.status_code, 302)
def test_contacts_delete_location_checking(self):
response = self.client.post(
'/contacts/' + str(self.contact.id) + '/delete/')
@ -104,7 +112,7 @@ class ContactViewsTestCase(ContactObjectsCreation, TestCase):
def test_contacts_edit(self):
response = self.client.post(
'/contacts/' + str(self.contact.id) + '/edit/', {
'name': 'priya',
'name': 'Preston',
'email': 'contact@gmail.com',
'phone': '12345',
'pk': self.contact.id,
@ -114,7 +122,7 @@ class ContactViewsTestCase(ContactObjectsCreation, TestCase):
def test_contacts_edit_html(self):
response = self.client.post(
'/contacts/' + str(self.contact.id) + '/edit/', {
'name': 'priya',
'name': 'Preston',
'email': 'contact@gmail.com',
'phone': '12345',
'pk': self.contact.id,
@ -149,7 +157,8 @@ class ContactsListTestCase(ContactObjectsCreation, TestCase):
def test_contacts_list_queryset(self):
data = {'fist_name': 'contact',
'city': "hyd", 'phone': '12345', 'email': "contact@gmail.com"}
'city': "Orlando", 'phone': '12345',
'email': "contact@gmail.com"}
response = self.client.post('/contacts/list/', data)
self.assertEqual(response.status_code, 200)
@ -162,16 +171,32 @@ class CommentTestCase(ContactObjectsCreation, TestCase):
'/contacts/comment/add/', {'contactid': self.contact.id})
self.assertEqual(response.status_code, 200)
# def test_GetContactsView(self):
# response = self.client.get('/get/list/')
# # self.assertEqual(response.status_code, 200)
# self.assertIsNone(response.context['contacts'])
def test_comment_edit(self):
response = self.client.post(
'/contacts/comment/edit/', {'commentid': self.comment.id})
self.assertEqual(response.status_code, 200)
resp = self.client.post(
'/contacts/comment/edit/',
{'commentid': self.comment.id, 'comment': 'hello123'})
self.assertEqual(resp.status_code, 200)
def test_comment_delete(self):
response = self.client.post(
'/contacts/comment/remove/', {'comment_id': self.comment.id})
self.assertEqual(response.status_code, 200)
def test_form_valid(self):
response = self.client.post(
'/contacts/comment/add/', {'contactid': self.contact.id,
'comment': 'hello'})
# print(response , "response")
self.assertEqual(response.status_code, 200)
class AttachmentTestCase(ContactObjectsCreation, TestCase):
def test_attachment_add(self):

View file

@ -1,7 +1,9 @@
from django.urls import path
from contacts.views import (
ContactsListView, CreateContactView, ContactDetailView, UpdateContactView, RemoveContactView,
GetContactsView, AddCommentView, UpdateCommentView, DeleteCommentView, AddAttachmentsView, DeleteAttachmentsView)
ContactsListView, CreateContactView, ContactDetailView,
UpdateContactView, RemoveContactView,
GetContactsView, AddCommentView, UpdateCommentView,
DeleteCommentView, AddAttachmentsView, DeleteAttachmentsView)
app_name = 'contacts'
@ -11,15 +13,21 @@ urlpatterns = [
path('create/', CreateContactView.as_view(), name='add_contact'),
path('<int:pk>/view/', ContactDetailView.as_view(), name="view_contact"),
path('<int:pk>/edit/', UpdateContactView.as_view(), name="edit_contact"),
path('<int:pk>/delete/', RemoveContactView.as_view(), name="remove_contact"),
path('<int:pk>/delete/',
RemoveContactView.as_view(),
name="remove_contact"),
path('get/list/', GetContactsView.as_view(), name="get_contacts"),
path('comment/add/', AddCommentView.as_view(), name="add_comment"),
path('comment/edit/', UpdateCommentView.as_view(), name="edit_comment"),
path('comment/remove/', DeleteCommentView.as_view(), name="remove_comment"),
path('comment/remove/',
DeleteCommentView.as_view(),
name="remove_comment"),
path('attachment/add/', AddAttachmentsView.as_view(), name="add_attachment"),
path('attachment/add/',
AddAttachmentsView.as_view(),
name="add_attachment"),
path('attachment/remove/', DeleteAttachmentsView.as_view(),
name="remove_attachment"),
]

View file

@ -12,7 +12,8 @@ from common.models import User, Comment, Attachments
from common.forms import BillingAddressForm
from common.utils import COUNTRIES
from contacts.models import Contact
from contacts.forms import ContactForm, ContactCommentForm, ContactAttachmentForm
from contacts.forms import (ContactForm,
ContactCommentForm, ContactAttachmentForm)
from accounts.models import Account
from django.core.exceptions import PermissionDenied
from django.db.models import Q
@ -25,9 +26,11 @@ class ContactsListView(LoginRequiredMixin, TemplateView):
def get_queryset(self):
queryset = self.model.objects.all()
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if (self.request.user.role != "ADMIN" and not
self.request.user.is_superuser):
queryset = queryset.filter(
Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user))
Q(assigned_to__in=[self.request.user]) |
Q(created_by=self.request.user))
request_post = self.request.POST
if request_post:
@ -58,8 +61,10 @@ class ContactsListView(LoginRequiredMixin, TemplateView):
int(i) for i in self.request.POST.getlist('assigned_to', []) if i]
search = False
if (
self.request.POST.get('first_name') or self.request.POST.get('city') or
self.request.POST.get('phone') or self.request.POST.get(
self.request.POST.get('first_name') or
self.request.POST.get('city') or
self.request.POST.get('phone') or
self.request.POST.get(
'email') or self.request.POST.get('assigned_to')
):
search = True
@ -78,7 +83,8 @@ class CreateContactView(LoginRequiredMixin, CreateView):
def dispatch(self, request, *args, **kwargs):
self.users = User.objects.filter(is_active=True).order_by('email')
return super(CreateContactView, self).dispatch(request, *args, **kwargs)
return super(CreateContactView, self).dispatch(
request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(CreateContactView, self).get_form_kwargs()
@ -96,7 +102,8 @@ class CreateContactView(LoginRequiredMixin, CreateView):
contact_obj.created_by = self.request.user
contact_obj.save()
if self.request.GET.get('view_account', None):
if Account.objects.filter(id=int(self.request.GET.get('view_account'))).exists():
if Account.objects.filter(
id=int(self.request.GET.get('view_account'))).exists():
Account.objects.get(id=int(self.request.GET.get(
'view_account'))).contacts.add(contact_obj)
return self.form_valid(form)
@ -113,12 +120,13 @@ class CreateContactView(LoginRequiredMixin, CreateView):
for assigned_to_user in assigned_to_list:
user = get_object_or_404(User, pk=assigned_to_user)
mail_subject = 'Assigned to contact.'
message = render_to_string('assigned_to/contact_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': self.request.scheme,
'contact': contact_obj
})
message = render_to_string(
'assigned_to/contact_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': self.request.scheme,
'contact': contact_obj
})
email = EmailMessage(mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
@ -176,10 +184,12 @@ class ContactDetailView(LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
context = super(ContactDetailView, self).get_context_data(**kwargs)
user_assgn_list = [assigned_to.id for assigned_to in context['object'].assigned_to.all()]
user_assgn_list = [
assigned_to.id for assigned_to in context['object'].assigned_to.all()]
if self.request.user == context['object'].created_by:
user_assgn_list.append(self.request.user.id)
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if (self.request.user.role != "ADMIN" and not
self.request.user.is_superuser):
if self.request.user.id not in user_assgn_list:
raise PermissionDenied
assigned_data = []
@ -189,8 +199,10 @@ class ContactDetailView(LoginRequiredMixin, DetailView):
assigned_dict['name'] = each.email
assigned_data.append(assigned_dict)
context.update({"comments": context["contact_record"].contact_comments.all(),
'attachments': context["contact_record"].contact_attachment.all(),
context.update({"comments":
context["contact_record"].contact_comments.all(),
'attachments':
context["contact_record"].contact_attachment.all(),
"assigned_data": json.dumps(assigned_data)
})
return context
@ -203,7 +215,8 @@ class UpdateContactView(LoginRequiredMixin, UpdateView):
def dispatch(self, request, *args, **kwargs):
self.users = User.objects.filter(is_active=True).order_by('email')
return super(UpdateContactView, self).dispatch(request, *args, **kwargs)
return super(UpdateContactView, self).dispatch(
request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(UpdateContactView, self).get_form_kwargs()
@ -224,7 +237,8 @@ class UpdateContactView(LoginRequiredMixin, UpdateView):
return self.form_invalid(form)
def form_valid(self, form):
assigned_to_ids = self.get_object().assigned_to.all().values_list('id', flat=True)
assigned_to_ids = self.get_object().assigned_to.all().values_list(
'id', flat=True)
contact_obj = form.save(commit=False)
all_members_list = []
@ -234,16 +248,17 @@ class UpdateContactView(LoginRequiredMixin, UpdateView):
'assigned_to').values_list('id', flat=True)
all_members_list = list(
set(list(assigned_form_users)) - set(list(assigned_to_ids)))
if len(all_members_list):
if all_members_list:
for assigned_to_user in all_members_list:
user = get_object_or_404(User, pk=assigned_to_user)
mail_subject = 'Assigned to contact.'
message = render_to_string('assigned_to/contact_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': self.request.scheme,
'contact': contact_obj
})
message = render_to_string(
'assigned_to/contact_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': self.request.scheme,
'contact': contact_obj
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
@ -288,7 +303,8 @@ class UpdateContactView(LoginRequiredMixin, UpdateView):
assigned_to.id for assigned_to in context["contact_obj"].assigned_to.all()]
if self.request.user == context['contact_obj'].created_by:
user_assgn_list.append(self.request.user.id)
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if (self.request.user.role != "ADMIN" and not
self.request.user.is_superuser):
if self.request.user.id not in user_assgn_list:
raise PermissionDenied
context["address_obj"] = self.object.address
@ -317,7 +333,9 @@ class RemoveContactView(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
contact_id = kwargs.get("pk")
self.object = get_object_or_404(Contact, id=contact_id)
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser and self.request.user != self.object.created_by:
if (self.request.user.role != "ADMIN" and not
self.request.user.is_superuser and
self.request.user != self.object.created_by):
raise PermissionDenied
else:
if self.object.address_id:
@ -339,7 +357,8 @@ class AddCommentView(LoginRequiredMixin, CreateView):
Contact, id=request.POST.get('contactid'))
if (
request.user in self.contact.assigned_to.all() or
request.user == self.contact.created_by or request.user.is_superuser or
request.user == self.contact.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
@ -430,7 +449,8 @@ class AddAttachmentsView(LoginRequiredMixin, CreateView):
Contact, id=request.POST.get('contactid'))
if (
request.user in self.contact.assigned_to.all() or
request.user == self.contact.created_by or request.user.is_superuser or
request.user == self.contact.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
@ -453,7 +473,8 @@ class AddAttachmentsView(LoginRequiredMixin, CreateView):
"attachment": attachment.file_name,
"attachment_url": attachment.attachment.url,
"created_on": attachment.created_on,
"download_url": reverse('common:download_attachment', kwargs={'pk': attachment.id}),
"download_url": reverse('common:download_attachment',
kwargs={'pk': attachment.id}),
"attachment_display": attachment.get_file_type_display(),
"created_by": attachment.created_by.email
})
@ -468,12 +489,14 @@ class DeleteAttachmentsView(LoginRequiredMixin, View):
self.object = get_object_or_404(
Attachments, id=request.POST.get("attachment_id"))
if (
request.user == self.object.created_by or request.user.is_superuser or
request.user == self.object.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
self.object.delete()
data = {"aid": request.POST.get("attachment_id")}
return JsonResponse(data)
data = {'error': "You don't have permission to delete this attachment."}
data = {'error':
"You don't have permission to delete this attachment."}
return JsonResponse(data)

View file

@ -7,7 +7,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = 'mwx@&97%!$fx_*zgj(2ygi^(s=oh5j(cqb$=+-mkd9scbt!0v0'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
DEBUG = True
ALLOWED_HOSTS = ['*']
@ -36,6 +36,7 @@ INSTALLED_APPS = [
'sorl.thumbnail',
'phonenumber_field',
'storages',
'marketing',
]
MIDDLEWARE = [

View file

@ -11,7 +11,8 @@ urlpatterns = [
path('accounts/', include('accounts.urls', namespace="accounts")),
path('leads/', include('leads.urls', namespace="leads")),
path('contacts/', include('contacts.urls', namespace="contacts")),
path('opportunities/', include('opportunity.urls', namespace="opportunities")),
path('opportunities/',
include('opportunity.urls', namespace="opportunities")),
path('cases/', include('cases.urls', namespace="cases")),
path('emails/', include('emails.urls', namespace="emails")),
# path('planner/', include('planner.urls', namespace="planner")),

View file

@ -14,7 +14,7 @@ class Email(models.Model):
status = models.CharField(max_length=200, default="sent")
important = models.BooleanField(max_length=10, default=False)
def __unicode__(self):
def __str__(self):
return self.subject
class Meta:

View file

@ -9,11 +9,15 @@ class UserCreation(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create(
first_name="navaneetha", username='navaneetha', email="n@mp.com", role="ADMIN")
first_name="nicole",
username='nicole',
email="n@mp.com", role="ADMIN")
self.user.set_password('navi123')
self.user.save()
self.email = Email.objects.create(
from_email="admin@micropyramid.com", to_email="meghana@micropyramid.com", subject="wish", message="haii",
from_email="admin@micropyramid.com",
to_email="nike@micropyramid.com",
subject="wish", message="haii",
important=False)
self.client.login(username='n@mp.com', password='navi123')
@ -24,7 +28,7 @@ class EmailSentEdit(UserCreation, TestCase):
'to_email': "xyz@mp.com",
'subject': "hello hi",
'message': 'simple mail'})
print('yes')
# print('yes')
self.assertTrue(form.is_valid())
def test_edit_form_invalid(self):
@ -32,7 +36,7 @@ class EmailSentEdit(UserCreation, TestCase):
'to_email': "",
'subject': "hello hi",
'message': 'simple mail'})
print('yes2')
# print('yes2')
self.assertFalse(form.is_valid())
@ -44,6 +48,27 @@ class EmailListTestCase(UserCreation, TestCase):
class EmailTestCase(UserCreation, TestCase):
def test_email_compose(self):
url = "/emails/compose/"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_email_trash_get(self):
url = "/emails/email_trash_delete/" + str(self.email.pk) + "/"
# print(url)
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
def test_email_send_fail(self):
url = "/emails/compose/"
data = {
'from_email': "abc@micropyramid.com", 'to_email': "",
'subject': 'act', 'message': "Hello"
}
response = self.client.post(url, data)
self.assertEqual(response.status_code, 200)
# email_trash_delete/
def test_email_send(self):
url = "/emails/compose/"
data = {
@ -55,7 +80,7 @@ class EmailTestCase(UserCreation, TestCase):
# boo = Email.objects.get(important=True)
# print('yes')
self.assertFalse(get_email.important)
self.assertEqual(get_email.subject, get_email.__unicode__())
self.assertEqual(get_email.subject, get_email.__str__())
self.assertEqual(response.status_code, 302)
@ -69,6 +94,58 @@ class EmailTestCase(UserCreation, TestCase):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_email_draft(self):
url = "/emails/email_draft/"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_email_draft_delete(self):
url = "/emails/email_draft_delete/" + str(self.email.pk) + "/"
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
def test_email_delete(self):
url = "/emails/email_delete/" + str(self.email.pk) + "/"
response = self.client.get(url)
self.assertEqual(response.status_code, 302)
def test_email_view(self):
url = "/emails/email_view/" + str(self.email.pk) + "/"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_email_sent_edit_get(self):
url = "/emails/email_sent_edit/" + str(self.email.pk) + "/"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_email_sent_edit_post(self):
url = "/emails/email_sent_edit/" + str(self.email.pk) + "/"
data = {
'from_email': "abc@micropyramid.com", 'to_email': "meg@gmail.com",
'subject': 'act', 'message': "Hello"
}
data1 = {
'from_email': "abc@micropyramid.com", 'to_email': "",
'subject': 'act', 'message': "Hello"
}
response = self.client.post(url, data)
response1 = self.client.post(url, data1)
self.assertEqual(response.status_code, 302)
self.assertEqual(response1.status_code, 200)
def test_email_imp_list(self):
url = "/emails/email_imp_list/"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
# def test_email_move_to_trash(self):
# url = "/emails/email_move_to_trash/" + str(self.email.pk) + "/"
# response = self.client.get(url)
# self.assertEqual(response.status_code, 302)
# response = self.client.post(reverse('302'), {}, HTTP_REFERER=url)
# def test_email_trash_del(self):
# url = "/emails/trash_delete/"+str(self.email.pk)+"/$"
# print(url)

View file

@ -17,7 +17,8 @@ class LeadForm(forms.ModelForm):
self.fields['assigned_to'].required = False
for key, value in self.fields.items():
if key == 'phone':
value.widget.attrs['placeholder'] = 'Enter phone number with country code'
value.widget.attrs['placeholder'] =\
'Enter phone number with country code'
else:
value.widget.attrs['placeholder'] = value.label
@ -46,9 +47,12 @@ class LeadForm(forms.ModelForm):
class Meta:
model = Lead
fields = ('assigned_to', 'first_name', 'last_name', 'account_name', 'title',
'phone', 'email', 'status', 'source', 'website', 'description',
'address_line', 'street', 'city', 'state', 'postcode', 'country'
fields = ('assigned_to', 'first_name',
'last_name', 'account_name', 'title',
'phone', 'email', 'status', 'source',
'website', 'description',
'address_line', 'street',
'city', 'state', 'postcode', 'country'
)

View file

@ -0,0 +1,18 @@
# Generated by Django 2.1.7 on 2019-03-06 06:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('leads', '0006_auto_20190218_1217'),
]
operations = [
migrations.AlterField(
model_name='lead',
name='status',
field=models.CharField(blank=True, choices=[('assigned', 'Assigned'), ('in process', 'In Process'), ('converted', 'Converted'), ('recycled', 'Recycled'), ('closed', 'Closed')], max_length=255, null=True, verbose_name='Status of Lead'),
),
]

View file

@ -3,7 +3,8 @@ from django.utils.translation import pgettext_lazy
from django.utils.translation import ugettext_lazy as _
from common.models import User
from common.utils import LEAD_STATUS, LEAD_SOURCE, COUNTRIES, return_complete_address
from common.utils import (LEAD_STATUS, LEAD_SOURCE,
COUNTRIES, return_complete_address)
from phonenumber_field.modelfields import PhoneNumberField
from accounts.models import Tags
from contacts.models import Contact
@ -11,15 +12,19 @@ from contacts.models import Contact
class Lead(models.Model):
title = models.CharField(
pgettext_lazy("Treatment Pronouns for the customer", "Title"), max_length=64, blank=True, null=True)
pgettext_lazy("Treatment Pronouns for the customer",
"Title"), max_length=64, blank=True, null=True)
first_name = models.CharField(("First name"), max_length=255)
last_name = models.CharField(("Last name"), max_length=255)
email = models.EmailField(null=True, blank=True)
phone = PhoneNumberField(null=True, blank=True)
status = models.CharField(
_("Status of Lead"), max_length=255, blank=True, null=True, choices=LEAD_STATUS)
_("Status of Lead"),
max_length=255, blank=True,
null=True, choices=LEAD_STATUS)
source = models.CharField(
_("Source of Lead"), max_length=255, blank=True, null=True, choices=LEAD_SOURCE)
_("Source of Lead"), max_length=255,
blank=True, null=True, choices=LEAD_SOURCE)
address_line = models.CharField(
_("Address"), max_length=255, blank=True, null=True)
street = models.CharField(
@ -37,9 +42,12 @@ class Lead(models.Model):
User, related_name='lead_assigned_users')
account_name = models.CharField(max_length=255, null=True, blank=True)
opportunity_amount = models.DecimalField(
_("Opportunity Amount"), decimal_places=2, max_digits=12, blank=True, null=True)
_("Opportunity Amount"),
decimal_places=2, max_digits=12,
blank=True, null=True)
created_by = models.ForeignKey(
User, related_name='lead_created_by', on_delete=models.SET_NULL, null=True)
User, related_name='lead_created_by',
on_delete=models.SET_NULL, null=True)
created_on = models.DateTimeField(_("Created on"), auto_now_add=True)
is_active = models.BooleanField(default=False)
enquery_type = models.CharField(max_length=255, blank=True, null=True)

View file

@ -13,7 +13,9 @@ def get_rendered_html(template_name, context={}):
@task
def send_email(subject, html_content, text_content=None, from_email=None, recipients=[], attachments=[], bcc=[], cc=[]):
def send_email(subject, html_content,
text_content=None, from_email=None,
recipients=[], attachments=[], bcc=[], cc=[]):
# send email to user with attachment
if not from_email:
from_email = settings.DEFAULT_FROM_EMAIL

View file

@ -114,11 +114,12 @@
<tr>
<th width="5%">ID</th>
<th width="10%">Name</th>
<th width="8%">Created By</th>
<th width="10%">Source</th>
<th width="10%">Status</th>
<th width="20%">Assigned User</th>
<th width="20%">Tags</th>
<th width="15%">Created On</th>
<th width="15%">Tags</th>
<th width="10%">Created On</th>
<th width="10%">Actions</th>
</tr>
{% endif %}
@ -133,6 +134,15 @@
<tr class="text-center">
<td scope="row">{{ forloop.counter }}</td>
<td><a href="{% url 'leads:view_lead' lead.id %}">{{ lead.first_name }} {{ lead.last_name }}</a></td>
<td>
{% if lead.created_by.profile_pic %}
{% thumbnail lead.created_by.profile_pic "40x40" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" title="{{ lead.created_by }}">
{% endthumbnail %}
{% else %}
<img src="{% static 'images/user.png' %}" alt="Micro profile pic" style="width: 40px;height: 40px;"/>
{% endif %}
</td>
<td>{{ lead.source }}</td>
<td>{{ lead.get_status_display }}</td>
<td>

View file

@ -3,6 +3,8 @@ from cases.models import Case
from leads.models import Lead
from common.models import User, Comment, Attachments
from accounts.models import Account
from leads.tasks import *
from leads.forms import *
class TestLeadModel(object):
@ -10,20 +12,20 @@ class TestLeadModel(object):
self.client = Client()
self.user = User.objects.create(
username='uday', email='u@mp.com', role="ADMIN")
self.user.set_password('uday2293')
username='jorge', email='j@mp.com', role="ADMIN")
self.user.set_password('jorge2293')
self.user.save()
self.client.login(username='u@mp.com', password='uday2293')
self.client.login(username='j@mp.com', password='jorge2293')
self.account = Account.objects.create(name="account",
email="account@gmail.com",
phone="12345",
billing_address_line="",
billing_street="Gokul enclave colony",
billing_city="Hasthinapuram",
billing_state="Telangana",
billing_postcode="500079",
billing_street="Arcade enclave colony",
billing_city="NewTown",
billing_state="California",
billing_postcode="5009",
billing_country="AD",
website="account.com",
industry="IT",
@ -31,14 +33,14 @@ class TestLeadModel(object):
created_by=self.user)
self.lead = Lead.objects.create(title="LeadCreation",
first_name="anjali",
first_name="Alisa",
last_name="k",
email="anjalikotha1993@gmail.com",
email="Alisak1993@gmail.com",
address_line="",
street="Gokul enclave colony",
city="Hasthinapuram",
state="Telangana",
postcode="500079",
street="Arcade enclave colony",
city="NewTown",
state="California",
postcode="5079",
country="AD",
website="www.gmail.com",
status="assigned",
@ -47,25 +49,29 @@ class TestLeadModel(object):
description="Iam an Lead",
created_by=self.user)
self.case = Case.objects.create(
name="raghu", case_type="Problem", status="New", account=self.account,
name="Rose", case_type="Problem",
status="New",
account=self.account,
priority="Low", description="something",
created_by=self.user, closed_on="2016-05-04")
self.comment = Comment.objects.create(
comment='testikd', case=self.case, commented_by=self.user)
self.attachment = Attachments.objects.create(
attachment='image.png', case=self.case, created_by=self.user, account=self.account)
attachment='image.png',
case=self.case, created_by=self.user,
account=self.account)
class LeadsPostrequestTestCase(TestLeadModel, TestCase):
def test_valid_postrequesttestcase_date(self):
data = {'title': 'LeadCreation', 'first_name': "kotha",
'last_name': "anjali", 'email': "anjalikotha1993@gmail.com",
data = {'title': 'LeadCreation', 'first_name': "micky",
'last_name': "Alisa", 'email': "Alisamicky1993@gmail.com",
'account': self.account,
'address_line': "",
'street': "Gokul enclave colony",
'city': "Hasthinapuram",
'state': "Telangana",
'postcode': "500079",
'street': "Arcade enclave colony",
'city': "NewTown",
'state': "California",
'postcode': "579",
'country': "AD",
'website': "www.gmail.com", "status ": "assigned",
"source": "Call",
@ -76,9 +82,10 @@ class LeadsPostrequestTestCase(TestLeadModel, TestCase):
def test_leads_list(self):
self.lead = Lead.objects.all()
get_name = Lead.objects.get(first_name='anjali')
get_name = Lead.objects.get(first_name='Alisa')
# print(get_name.first_name+get_name.last_name,str(get_name))
self.assertEqual(get_name.first_name + get_name.last_name, str(get_name))
self.assertEqual(get_name.first_name +
get_name.last_name, str(get_name))
response = self.client.get('/leads/list/')
self.assertEqual(response.status_code, 200)
@ -91,14 +98,14 @@ class LeadsCreateUrlTestCase(TestLeadModel, TestCase):
def test_leads_create_url(self):
response = self.client.post('/leads/create/', {
'title': 'LeadCreation',
'first_name': "kotha",
'email': "anjalikotha1993@gmail.com",
'first_name': "micky",
'email': "Alisamicky1993@gmail.com",
'account': self.account,
'address_line': "",
'street': "Gokul enclave colony",
'city': "Hasthinapuram",
'state': "Telangana",
'postcode': "500079",
'street': "Arcade enclave colony",
'city': "NewTown",
'state': "California",
'postcode': "579",
'country': "AD",
'website': "www.gmail.com",
"status": "assigned",
@ -110,11 +117,14 @@ class LeadsCreateUrlTestCase(TestLeadModel, TestCase):
def test_leads_create_html(self):
response = self.client.post('/leads/create/', {
'title': 'LeadCreation', 'name': "kotha", 'email': "anjalikotha1993@gmail.com", 'account': self.account,
'address_line': "", 'street': "Gokul enclave colony", 'city': "Hasthinapuram",
'state': "Telangana", 'postcode': "500079", 'country': "AD",
'title': 'LeadCreation', 'name': "micky",
'email': "Alisamicky1993@gmail.com", 'account': self.account,
'address_line': "",
'street': "Arcade enclave colony", 'city': "NewTown",
'state': "California", 'postcode': "579", 'country': "AD",
'website': "www.gmail.com", 'status': "assigned",
"source": "Call", 'opportunity_amount': "700", 'description': "Iam an Lead", 'created_by': self.user})
"source": "Call", 'opportunity_amount': "700",
'description': "Iam an Lead", 'created_by': self.user})
# self.assertTemplateUsed(response, 'create_lead.html')
self.assertEqual(response.status_code, 200)
@ -129,14 +139,14 @@ class LeadsViewTestCase(TestLeadModel, TestCase):
def test_leads_view(self):
Lead.objects.create(title="LeadCreationbylead",
first_name="anjali",
first_name="Alisa",
last_name="k",
email="srilathakotha1993@gmail.com",
email="Alisa@gmail.com",
address_line="",
street="Gokul enclave colony",
city="Hasthinapuram",
state="Telangana",
postcode="500079",
street="Arcade enclave colony",
city="NewTown",
state="California",
postcode="579",
country="AD",
website="www.gmail.com",
status='converted',
@ -158,8 +168,9 @@ class LeadListTestCase(TestLeadModel, TestCase):
def test_leads_list_queryset(self):
self.lead = Lead.objects.all()
data = {'fist_name': "meghana", 'last_name': "reddy",
'city': "hyd", 'email': "contact@gmail.com", 'status': "Assigned"}
data = {'fist_name': "marphy", 'last_name': "racheal",
'city': "hyd", 'email': "contact@gmail.com",
'status': "Assigned"}
response = self.client.post('/leads/list/', data)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'leads.html')
@ -189,21 +200,24 @@ class UpdateLeadTestCase(TestLeadModel, TestCase):
def test_update_lead(self):
url = '/leads/' + str(self.lead.id) + '/edit/'
data = {
'title': "Creation", 'first_name': "meghana", 'last_name': "k",
'title': "Creation", 'first_name': "marphy", 'last_name': "k",
'email': "meg@gmail.com", 'account': self.account,
'address_line': "", 'street': "Gokul enclave colony", 'city': "Hasthinapuram",
'state': "Telangana", 'postcode': "500079", 'country': "AD",
'phone': "+917894563452", 'website': "www.gmail.com", 'status': "assigned",
'source': "", 'opportunity_amount': "700", 'description': "Iam an Lead", 'created_by': self.user}
'address_line': "",
'street': "Arcade enclave colony", 'city': "NewTown",
'state': "California",
'postcode': "579", 'country': "AD",
'phone': "+917894563452",
'website': "www.gmail.com", 'status': None,
'source': "", 'opportunity_amount': "700",
'description': "Iam an Lead", 'created_by': self.user}
response = self.client.post(url, data)
# self.assertEqual(response.status_code, 302)
self.assertEqual(response.status_code, 200)
class LeadDetailTestCase(TestLeadModel, TestCase):
def test_lead_detail(self):
url = '/leads/' + str(self.lead.id) + '/view/'
response = self.client.get(url)
response = self.client.get(url, {'status': ''})
self.assertEqual(response.status_code, 200)
@ -235,3 +249,23 @@ class AttachmentTestCase(TestLeadModel, TestCase):
'/leads/attachment/remove/', {'attachment_id': self.attachment.id})
self.assertEqual(response.status_code, 200)
class TestTemplates(TestLeadModel, TestCase):
def test_lead_list_view(self):
resp = self.client.post('/leads/list/', {'name': 'trevor',
'tag': "123",
'source': "call",
'assigned_to': '1',
'tab_status': 'Open'})
self.assertEqual(resp.status_code, 200)
# print(resp.name)
# self.assertTrue(resp.name)
# def test_lead_from_site(self):
# resp = self.client.get()
class TestConvertLeadView(TestLeadModel, TestCase):
def test_get_fun(self):
resp = self.client.get('/leads/' + str(self.lead.id) + '/convert/')
self.assertEqual(resp.status_code, 302)

View file

@ -1,6 +1,7 @@
from django.urls import path
from leads.views import (
LeadListView, create_lead, LeadDetailView, update_lead, DeleteLeadView, convert_lead,
LeadListView, create_lead, LeadDetailView,
update_lead, DeleteLeadView, convert_lead,
GetLeadsView, AddCommentView, UpdateCommentView, DeleteCommentView,
AddAttachmentsView, DeleteAttachmentsView, create_lead_from_site
)
@ -24,9 +25,11 @@ urlpatterns = [
path('comment/add/', AddCommentView.as_view(), name="add_comment"),
path('comment/edit/', UpdateCommentView.as_view(), name="edit_comment"),
path('comment/remove/', DeleteCommentView.as_view(), name="remove_comment"),
path('comment/remove/',
DeleteCommentView.as_view(), name="remove_comment"),
path('attachment/add/', AddAttachmentsView.as_view(), name="add_attachment"),
path('attachment/add/',
AddAttachmentsView.as_view(), name="add_attachment"),
path('attachment/remove/', DeleteAttachmentsView.as_view(),
name="remove_attachment"),
]

View file

@ -9,11 +9,11 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.urls import reverse
from django.views.generic import (
CreateView, UpdateView, DetailView, ListView, TemplateView, View)
CreateView, DetailView, ListView, TemplateView, View)
from accounts.models import Account, Tags
from common.models import User, Comment, Attachments, APISettings
from common.utils import LEAD_STATUS, LEAD_SOURCE, COUNTRIES, get_client_ip
from common.utils import LEAD_STATUS, LEAD_SOURCE, COUNTRIES
from common import status
from contacts.models import Contact
from leads.models import Lead
@ -31,9 +31,11 @@ class LeadListView(LoginRequiredMixin, TemplateView):
def get_queryset(self):
queryset = self.model.objects.all().exclude(status='converted')
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if (self.request.user.role != "ADMIN" and not
self.request.user.is_superuser):
queryset = queryset.filter(
Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user))
Q(assigned_to__in=[self.request.user]) |
Q(created_by=self.request.user))
request_post = self.request.POST
if request_post:
@ -61,8 +63,8 @@ class LeadListView(LoginRequiredMixin, TemplateView):
def get_context_data(self, **kwargs):
context = super(LeadListView, self).get_context_data(**kwargs)
# context["lead_obj"] = self.get_queryset()
open_leads = self.get_queryset().exclude(status='dead')
close_leads = self.get_queryset().filter(status='dead')
open_leads = self.get_queryset().exclude(status='closed')
close_leads = self.get_queryset().filter(status='closed')
context["status"] = LEAD_STATUS
context["open_leads"] = open_leads
context["close_leads"] = close_leads
@ -73,14 +75,13 @@ class LeadListView(LoginRequiredMixin, TemplateView):
context["assignedto_list"] = [
int(i) for i in self.request.POST.getlist('assigned_to', []) if i]
search = False
if (
search = True if (
self.request.POST.get('name') or self.request.POST.get('city') or
self.request.POST.get('email') or self.request.POST.get('tag') or
self.request.POST.get('status') or self.request.POST.get('source') or
self.request.POST.get('status') or
self.request.POST.get('source') or
self.request.POST.get('assigned_to')
):
search = True
) else False
context["search"] = search
@ -107,7 +108,6 @@ def create_lead(request):
if request.POST:
form = LeadForm(request.POST, request.FILES, assigned_to=users)
if form.is_valid():
lead_obj = form.save(commit=False)
lead_obj.created_by = request.user
lead_obj.save()
@ -128,12 +128,13 @@ def create_lead(request):
for assigned_to_user in assigned_to_list:
user = get_object_or_404(User, pk=assigned_to_user)
mail_subject = 'Assigned to lead.'
message = render_to_string('assigned_to/leads_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'lead': lead_obj
})
message = render_to_string(
'assigned_to/leads_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'lead': lead_obj
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
@ -171,12 +172,13 @@ def create_lead(request):
for assigned_to_user in assigned_to_list:
user = get_object_or_404(User, pk=assigned_to_user)
mail_subject = 'Assigned to account.'
message = render_to_string('assigned_to/account_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'account': account_object
})
message = render_to_string(
'assigned_to/account_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'account': account_object
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
@ -187,22 +189,18 @@ def create_lead(request):
if request.POST.get("savenewform"):
success_url = reverse("leads:add_lead")
return JsonResponse({'error': False, 'success_url': success_url})
return JsonResponse({'error': True, 'errors': form.errors})
context = {}
context["lead_form"] = form
context["accounts"] = Account.objects.filter(status="open")
context["users"] = users
context["countries"] = COUNTRIES
context["status"] = LEAD_STATUS
context["source"] = LEAD_SOURCE
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
else:
return JsonResponse({'error': True, 'errors': form.errors})
else:
context = {}
context["lead_form"] = form
context["accounts"] = Account.objects.filter(status="open")
context["users"] = users
context["countries"] = COUNTRIES
context["status"] = LEAD_STATUS
context["source"] = LEAD_SOURCE
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
return render(request, template_name, context)
return render(request, template_name, context)
class LeadDetailView(LoginRequiredMixin, DetailView):
@ -216,7 +214,8 @@ class LeadDetailView(LoginRequiredMixin, DetailView):
assigned_to.id for assigned_to in context['object'].assigned_to.all()]
if self.request.user == context['object'].created_by:
user_assgn_list.append(self.request.user.id)
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if (self.request.user.role != "ADMIN" and not
self.request.user.is_superuser):
if self.request.user.id not in user_assgn_list:
raise PermissionDenied
comments = Comment.objects.filter(
@ -244,8 +243,10 @@ class LeadDetailView(LoginRequiredMixin, DetailView):
assigned_data.append(assigned_dict)
context.update({
"attachments": attachments, "comments": comments, "status": LEAD_STATUS, "countries": COUNTRIES,
"reminder_form_set": reminder_form_set, "meetings": meetings, "calls": calls,
"attachments": attachments, "comments": comments,
"status": LEAD_STATUS, "countries": COUNTRIES,
"reminder_form_set": reminder_form_set,
"meetings": meetings, "calls": calls,
"assigned_data": json.dumps(assigned_data)})
return context
@ -266,7 +267,8 @@ def update_lead(request, pk):
if request.POST:
form = LeadForm(request.POST, request.FILES,
instance=lead_record, initial=initial, assigned_to=users)
instance=lead_record,
initial=initial, assigned_to=users)
if request.POST.get('status') == "converted":
form.fields['account_name'].required = True
@ -275,7 +277,8 @@ def update_lead(request, pk):
form.fields['account_name'].required = False
form.fields['email'].required = False
if form.is_valid():
assigned_to_ids = lead_record.assigned_to.all().values_list('id', flat=True)
assigned_to_ids = lead_record.assigned_to.all().values_list(
'id', flat=True)
lead_obj = form.save(commit=False)
lead_obj.save()
lead_obj.tags.clear()
@ -298,17 +301,19 @@ def update_lead(request, pk):
assigned_form_users = form.cleaned_data.get(
'assigned_to').values_list('id', flat=True)
all_members_list = list(
set(list(assigned_form_users)) - set(list(assigned_to_ids)))
if len(all_members_list):
set(list(assigned_form_users)) -
set(list(assigned_to_ids)))
if all_members_list:
for assigned_to_user in all_members_list:
user = get_object_or_404(User, pk=assigned_to_user)
mail_subject = 'Assigned to lead.'
message = render_to_string('assigned_to/leads_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'lead': lead_obj
})
message = render_to_string(
'assigned_to/leads_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'lead': lead_obj
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
@ -351,12 +356,13 @@ def update_lead(request, pk):
for assigned_to_user in assigned_to_list:
user = get_object_or_404(User, pk=assigned_to_user)
mail_subject = 'Assigned to account.'
message = render_to_string('assigned_to/account_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'account': account_object
})
message = render_to_string(
'assigned_to/account_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'account': account_object
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
@ -368,32 +374,28 @@ def update_lead(request, pk):
if status:
success_url = reverse('accounts:list')
return JsonResponse({'error': False, 'success_url': success_url})
return JsonResponse({'error': True, 'errors': form.errors})
context = {}
context["lead_obj"] = lead_record
user_assgn_list = [
assigned_to.id for assigned_to in lead_record.assigned_to.all()]
if request.user == lead_record.created_by:
user_assgn_list.append(request.user.id)
if request.user.role != "ADMIN" and not request.user.is_superuser:
if request.user.id not in user_assgn_list:
raise PermissionDenied
else:
return JsonResponse({'error': True, 'errors': form.errors})
context["lead_form"] = form
context["accounts"] = Account.objects.filter(status="open")
context["users"] = users
context["countries"] = COUNTRIES
context["status"] = LEAD_STATUS
context["source"] = LEAD_SOURCE
context["error"] = error
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
else:
context = {}
context["lead_obj"] = lead_record
user_assgn_list = [
assigned_to.id for assigned_to in lead_record.assigned_to.all()]
if request.user == lead_record.created_by:
user_assgn_list.append(request.user.id)
if request.user.role != "ADMIN" and not request.user.is_superuser:
if request.user.id not in user_assgn_list:
raise PermissionDenied
context["lead_form"] = form
context["accounts"] = Account.objects.filter(status="open")
context["users"] = users
context["countries"] = COUNTRIES
context["status"] = LEAD_STATUS
context["source"] = LEAD_SOURCE
context["error"] = error
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
return render(request, template_name, context)
return render(request, template_name, context)
class DeleteLeadView(LoginRequiredMixin, View):
@ -404,13 +406,13 @@ class DeleteLeadView(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
self.object = get_object_or_404(Lead, id=kwargs.get("pk"))
if (
self.request.user.role == "ADMIN" or self.request.user.is_superuser or
self.request.user.role == "ADMIN" or
self.request.user.is_superuser or
self.request.user == self.object.created_by
):
self.object.delete()
return redirect("leads:list")
else:
raise PermissionDenied
raise PermissionDenied
def convert_lead(request, pk):
@ -435,7 +437,8 @@ def convert_lead(request, pk):
account_object.contacts.add(*contacts_list)
account_object.save()
current_site = get_current_site(request)
for assigned_to_user in lead_obj.assigned_to.all().values_list('id', flat=True):
for assigned_to_user in lead_obj.assigned_to.all().values_list(
'id', flat=True):
user = get_object_or_404(User, pk=assigned_to_user)
mail_subject = 'Assigned to account.'
message = render_to_string('assigned_to/account_assigned.html', {
@ -450,7 +453,8 @@ def convert_lead(request, pk):
return redirect("accounts:list")
return HttpResponseRedirect(
reverse('leads:edit_lead', kwargs={'pk': lead_obj.id}) + '?status=converted')
reverse('leads:edit_lead', kwargs={
'pk': lead_obj.id}) + '?status=converted')
class AddCommentView(LoginRequiredMixin, CreateView):
@ -463,7 +467,8 @@ class AddCommentView(LoginRequiredMixin, CreateView):
self.lead = get_object_or_404(Lead, id=request.POST.get('leadid'))
if (
request.user in self.lead.assigned_to.all() or
request.user == self.lead.created_by or request.user.is_superuser or
request.user == self.lead.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
@ -537,11 +542,6 @@ class GetLeadsView(LoginRequiredMixin, ListView):
context_object_name = "leads"
template_name = "leads_list.html"
def get_context_data(self, **kwargs):
context = super(GetLeadsView, self).get_context_data(**kwargs)
context["leads"] = self.get_queryset()
return context
class AddAttachmentsView(LoginRequiredMixin, CreateView):
model = Attachments
@ -553,7 +553,8 @@ class AddAttachmentsView(LoginRequiredMixin, CreateView):
self.lead = get_object_or_404(Lead, id=request.POST.get('leadid'))
if (
request.user in self.lead.assigned_to.all() or
request.user == self.lead.created_by or request.user.is_superuser or
request.user == self.lead.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
@ -576,7 +577,8 @@ class AddAttachmentsView(LoginRequiredMixin, CreateView):
"attachment_url": attachment.attachment.url,
"created_on": attachment.created_on,
"created_by": attachment.created_by.email,
"download_url": reverse('common:download_attachment', kwargs={'pk': attachment.id}),
"download_url": reverse(
'common:download_attachment', kwargs={'pk': attachment.id}),
"attachment_display": attachment.get_file_type_display()
})
@ -590,14 +592,16 @@ class DeleteAttachmentsView(LoginRequiredMixin, View):
self.object = get_object_or_404(
Attachments, id=request.POST.get("attachment_id"))
if (
request.user == self.object.created_by or request.user.is_superuser or
request.user == self.object.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
self.object.delete()
data = {"aid": request.POST.get("attachment_id")}
return JsonResponse(data)
data = {'error': "You don't have permission to delete this attachment."}
data = {'error':
"You don't have permission to delete this attachment."}
return JsonResponse(data)
@ -606,21 +610,26 @@ def create_lead_from_site(request):
# ip_addres = get_client_ip(request)
# website_address = request.scheme + '://' + ip_addres
api_key = request.POST.get('apikey')
# api_setting = APISettings.objects.filter(website=website_address, apikey=api_key).first()
# api_setting = APISettings.objects.filter(
# website=website_address, apikey=api_key).first()
api_setting = APISettings.objects.filter(apikey=api_key).first()
if not api_setting:
return JsonResponse({
'error': True, 'message': "You don't have permission, please contact the admin!."},
'error': True,
'message':
"You don't have permission, please contact the admin!."},
status=status.HTTP_400_BAD_REQUEST)
if api_setting and request.POST.get("email") and request.POST.get("full_name"):
if (api_setting and request.POST.get("email") and
request.POST.get("full_name")):
# user = User.objects.filter(is_admin=True, is_active=True).first()
user = api_setting.created_by
lead = Lead.objects.create(
title=request.POST.get("full_name"),
status="assigned", source=api_setting.website,
description=request.POST.get("message"),
email=request.POST.get("email"), phone=request.POST.get("phone"),
email=request.POST.get("email"),
phone=request.POST.get("phone"),
is_active=True, created_by=user)
lead.assigned_to.add(user)
# Send Email to Assigned Users
@ -629,18 +638,19 @@ def create_lead_from_site(request):
# Create Contact
contact = Contact.objects.create(
first_name=request.POST.get("full_name"),
email=request.POST.get("email"), phone=request.POST.get("phone"),
email=request.POST.get("email"),
phone=request.POST.get("phone"),
description=request.POST.get("message"), created_by=user,
is_active=True)
contact.assigned_to.add(user)
lead.contacts.add(contact)
return JsonResponse({'error': False, 'message': "Lead Created sucessfully."},
return JsonResponse({'error': False,
'message': "Lead Created sucessfully."},
status=status.HTTP_201_CREATED)
return JsonResponse({'error': True, 'message': "In-valid data."},
status=status.HTTP_400_BAD_REQUEST)
else:
return JsonResponse({
'error': True, 'message': "In-valid request method."},
status=status.HTTP_400_BAD_REQUEST)
return JsonResponse({
'error': True, 'message': "In-valid request method."},
status=status.HTTP_400_BAD_REQUEST)

290
marketing/forms.py Normal file
View file

@ -0,0 +1,290 @@
import csv
import re
import json
import openpyxl
from django import forms
from common.models import User
from marketing.models import (
ContactList, Contact,
EmailTemplate, Campaign, Tag
)
def csv_doc_validate(document):
temp_row = []
reader = csv.reader((document.read().decode("utf-8")).splitlines())
# csv_headers = ["first name", "last name", "email"]
csv_headers = ["first name", "email"]
# required_headers = ["first name", "last name", "email"]
required_headers = ["first name", "email"]
for y_index, row in enumerate(reader):
each = {}
if y_index == 0:
csv_headers = [header_name.lower()
for header_name in row if header_name]
missing_headers = set(required_headers) - \
set([r.lower() for r in row])
if missing_headers:
missing_headers_str = ', '.join(missing_headers)
message = 'Missing headers: %s' % (missing_headers_str)
return {"error": True, "message": message}
continue
elif not ''.join(str(x) for x in row):
continue
else:
for x_index, cell_value in enumerate(row):
try:
csv_headers[x_index]
except IndexError:
continue
if csv_headers[x_index] in required_headers:
if not cell_value:
message = 'Missing required value %s for row %s' % (
csv_headers[x_index], y_index + 1)
return {"error": True, "message": message}
each[csv_headers[x_index]] = cell_value
temp_row.append(each)
return {"error": False, "validated_rows": temp_row}
def get_validated_rows(wb, sheet_name, validated_rows):
# headers = ["first name", "last name", "email"]
# required_headers = ["first name", "last name", "email"]
headers = ["first name", "email"]
required_headers = ["first name", "email"]
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 = []
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}
# elif x_index == 0:
# cell_value = is_valid_date_format(cell.value)
# if not cell_value:
# message = 'Invalid date format(format:yyyy-mm-dd) \
# %s for row %s in sheet %s' % (headers[x_index], y_index + 1, sheet_name)
# return {"error": True, "message": message}
temp_row.append(cell.value)
if len(temp_row) >= len(required_headers):
validated_rows.append(temp_row)
return validated_rows
def xls_doc_validate(document):
wb = openpyxl.load_workbook(document)
sheets = wb.get_sheet_names()
validated_rows = []
for sheet_name in sheets:
get_validated_rows(wb, sheet_name, validated_rows)
return {"error": False, "validated_rows": validated_rows}
def import_document_validator(document):
try:
# dialect = csv.Sniffer().sniff(document.read(1024).decode("ascii"))
document.seek(0, 0)
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"}
class ContactListForm(forms.ModelForm):
tags = forms.CharField(max_length=5000, required=False)
contacts_file = forms.FileField(required=False)
class Meta:
model = ContactList
fields = ["name"]
def __init__(self, *args, **kwargs):
super(ContactListForm, self).__init__(*args, **kwargs)
self.fields['contacts_file'].widget.attrs.update({
"accept": ".csv,.xls,.xlsx,.xlsm,.xlsb,.xml",
})
if self.instance is None:
self.fields['contacts_file'].required = True
if self.data.get('contacts_file'):
self.fields['contacts_file'].widget.attrs.update({
"accept": ".csv,.xls,.xlsx,.xlsm,.xlsb,.xml",
})
def clean_contacts_file(self):
document = self.cleaned_data.get("contacts_file")
if document:
data = import_document_validator(document)
if data.get("error"):
raise forms.ValidationError(data.get("message"))
else:
self.validated_rows = data.get("validated_rows")
return document
def clean_tags(self):
tags_data = self.data['tags'].split(",") if self.data['tags'] else []
if len(tags_data) > 0:
instance_tags = []
if self.instance and self.instance.id is not None:
instance_tags = list(
set(self.instance.tags.all().values_list(
'name', flat=True)))
for each in tags_data:
tags = Tag.objects.filter(name__iexact=each)
if instance_tags:
tags = tags.exclude(name__in=instance_tags)
if tags:
raise forms.ValidationError(
str(each) + ' Tag aleady existed with this name')
if bool(re.search(r"[~\!_.@#\$%\^&\*\(\)\+{}\":;'/\[\]]",
each)):
raise forms.ValidationError(
"Tags Should not contain special \
charcters except hyphens")
return self.data['tags']
raise forms.ValidationError("Enter Any tags")
def clean_visible_to(self):
visible_to_data = json.loads(self.data['visible_to'])
if len(visible_to_data) > 0:
instance_visible_to = []
if self.instance and self.instance.id is not None:
instance_visible_to = list(
set(self.instance.visible_to.all().values_list(
'email', flat=True)))
for each in visible_to_data:
visible_to = User.objects.filter(email=each)
if instance_visible_to:
visible_to = visible_to.exclude(
email__in=instance_visible_to)
if visible_to:
raise forms.ValidationError(
str(each) + ' User aleady existed')
return self.data['visible_to']
raise forms.ValidationError("Select any of the users")
class ContactForm(forms.ModelForm):
contact_list = forms.CharField(max_length=5000)
class Meta:
model = Contact
fields = ["name", "email", "contact_number"]
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
def clean_contact_list(self):
contact_list = self.cleaned_data.get("contact_list")
if not contact_list or contact_list == '[]' or\
json.loads(contact_list) == []:
raise forms.ValidationError(
"Please choose any of the Contact List")
else:
for each in json.loads(contact_list):
if not ContactList.objects.filter(id=each).first():
raise forms.ValidationError(
"Please choose a valid Contact List")
return contact_list
class ContactsCSVUploadForm(forms.Form):
contacts_file = forms.FileField()
contact_list = forms.CharField(max_length=5000)
def __init__(self, *args, **kwargs):
super(ContactsCSVUploadForm, self).__init__(*args, **kwargs)
self.fields['contacts_file'].widget.attrs.update({
"accept": ".csv,.xls,.xlsx,.xlsm,.xlsb,.xml",
})
def clean_contacts_file(self):
document = self.cleaned_data.get("contacts_file")
data = import_document_validator(document)
if data.get("error"):
raise forms.ValidationError(data.get("message"))
else:
self.validated_rows = data.get("validated_rows")
return document
def clean_contact_list(self):
contact_list = self.cleaned_data.get("contact_list")
if not contact_list or contact_list == '[]' or\
json.loads(contact_list) == []:
raise forms.ValidationError(
"Please choose any of the Contact List")
else:
for each in json.loads(contact_list):
if not ContactList.objects.filter(id=each).first():
raise forms.ValidationError(
"Please choose a valid Contact List")
return contact_list
class EmailTemplateForm(forms.ModelForm):
class Meta:
model = EmailTemplate
fields = ['title', 'subject', 'html']
class SendCampaignForm(forms.ModelForm):
schedule_later = forms.BooleanField(required=False)
reply_to_crm = forms.BooleanField(required=False)
timezone = forms.CharField(max_length=500, required=False)
schedule_date_time = forms.CharField(max_length=100, required=False)
reply_to_email = forms.EmailField(max_length=100, required=False)
from_email = forms.EmailField(max_length=100, required=True)
from_name = forms.CharField(max_length=100, required=True)
contact_list = forms.CharField(max_length=5000, required=True)
class Meta:
model = Campaign
fields = ['title', 'subject', 'html', 'email_template']
def __init__(self, *args, **kwargs):
super(SendCampaignForm, self).__init__(*args, **kwargs)
if self.data.get('schedule_later') and self.data['schedule_later'] == 'true':
self.fields['timezone'].required = True
self.fields['schedule_date_time'].required = True
if not self.data.get('reply_to_crm'):
self.fields['reply_to_email'].required = True
def clean_contact_list(self):
contact_list = self.cleaned_data.get("contact_list")
if not contact_list or contact_list == '[]' or \
json.loads(contact_list) == []:
raise forms.ValidationError(
"Please choose any of the Contact List")
else:
for each in json.loads(contact_list):
if not ContactList.objects.filter(id=each).first():
raise forms.ValidationError(
"Please choose a valid Contact List")
return contact_list

View file

@ -0,0 +1,178 @@
# Generated by Django 2.1.7 on 2019-02-25 13:21
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Campaign',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=5000)),
('created_on', models.DateTimeField(auto_now_add=True)),
('schedule_date_time', models.DateTimeField(blank=True, null=True)),
('reply_to_email', models.EmailField(blank=True, max_length=254, null=True)),
('subject', models.CharField(max_length=5000)),
('html', models.TextField()),
('html_processed', models.TextField(blank=True, default='')),
('from_email', models.EmailField(blank=True, max_length=254, null=True)),
('from_name', models.EmailField(blank=True, max_length=254, null=True)),
('sent', models.IntegerField(blank=True, default='0')),
('opens', models.IntegerField(blank=True, default='0')),
('opens_unique', models.IntegerField(blank=True, default='0')),
('bounced', models.IntegerField(default='0')),
('status', models.CharField(choices=[('Scheduled', 'Scheduled'), ('Cancelled', 'Cancelled'), ('Sending', 'Sending'), ('Preparing', 'Preparing'), ('Sent', 'Sent')], default='Preparing', max_length=20)),
],
),
migrations.CreateModel(
name='CampaignLinkClick',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ip_address', models.GenericIPAddressField()),
('created_on', models.DateTimeField(auto_now_add=True)),
('user_agent', models.CharField(blank=True, max_length=2000, null=True)),
('campaign', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='marketing.Campaign')),
],
),
migrations.CreateModel(
name='CampaignLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(auto_now_add=True)),
('message_id', models.CharField(blank=True, max_length=1000, null=True)),
('campaign', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='campaign_contacts', to='marketing.Campaign')),
],
),
migrations.CreateModel(
name='CampaignOpen',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ip_address', models.GenericIPAddressField()),
('created_on', models.DateTimeField(auto_now_add=True)),
('user_agent', models.CharField(blank=True, max_length=2000, null=True)),
('campaign', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='marketing.Campaign')),
],
),
migrations.CreateModel(
name='Contact',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(auto_now_add=True)),
('name', models.CharField(max_length=500)),
('email', models.EmailField(max_length=254)),
('contact_number', models.CharField(blank=True, max_length=20, null=True, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '+999999999'. Up to 20 digits allowed.", regex='^\\+?1?\\d{9,15}$')])),
('is_unsubscribed', models.BooleanField(default=False)),
('is_bounced', models.BooleanField(default=False)),
('company_name', models.CharField(blank=True, max_length=500, null=True)),
('last_name', models.CharField(blank=True, max_length=500, null=True)),
('city', models.CharField(blank=True, max_length=500, null=True)),
('state', models.CharField(blank=True, max_length=500, null=True)),
('contry', models.CharField(blank=True, max_length=500, null=True)),
],
),
migrations.CreateModel(
name='ContactList',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(auto_now_add=True)),
('name', models.CharField(max_length=500)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='marketing_contactlist', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='EmailTemplate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(auto_now_add=True)),
('title', models.CharField(max_length=5000)),
('subject', models.CharField(max_length=5000)),
('html', models.TextField()),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='marketing_emailtemplates', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Link',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('original', models.URLField(max_length=2100)),
('clicks', models.IntegerField(default='0')),
('unique', models.IntegerField(default='0')),
('campaign', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='marketing_links', to='marketing.Campaign')),
],
),
migrations.CreateModel(
name='Tag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=500)),
('color', models.CharField(default='#999999', max_length=20, verbose_name='color')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='marketing_tags', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='contactlist',
name='tags',
field=models.ManyToManyField(to='marketing.Tag'),
),
migrations.AddField(
model_name='contactlist',
name='visible_to',
field=models.ManyToManyField(related_name='contact_lists_visible_to', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='contact',
name='contact_list',
field=models.ManyToManyField(related_name='contacts', to='marketing.ContactList'),
),
migrations.AddField(
model_name='contact',
name='created_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='marketing_contacts_created_by', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='campaignopen',
name='contact',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='marketing.Contact'),
),
migrations.AddField(
model_name='campaignlog',
name='contact',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='marketing_campaign_logs', to='marketing.Contact'),
),
migrations.AddField(
model_name='campaignlinkclick',
name='contact',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='marketing.Contact'),
),
migrations.AddField(
model_name='campaignlinkclick',
name='link',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='marketing.Link'),
),
migrations.AddField(
model_name='campaign',
name='contact_lists',
field=models.ManyToManyField(related_name='campaigns', to='marketing.ContactList'),
),
migrations.AddField(
model_name='campaign',
name='created_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='marketing_campaigns_created_by', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='campaign',
name='email_template',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='marketing.EmailTemplate'),
),
]

View file

@ -0,0 +1,40 @@
# Generated by Django 2.1.7 on 2019-03-07 06:57
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('marketing', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='campaign',
name='updated_on',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='contact',
name='updated_on',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='contactlist',
name='updated_on',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='emailtemplate',
name='updated_on',
field=models.DateTimeField(auto_now=True),
),
migrations.AddField(
model_name='tag',
name='created_on',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]

View file

@ -0,0 +1,34 @@
# Generated by Django 2.1.7 on 2019-03-07 12:20
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('marketing', '0002_auto_20190307_1227'),
]
operations = [
migrations.CreateModel(
name='FailedContact',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_on', models.DateTimeField(auto_now_add=True)),
('name', models.CharField(blank=True, max_length=500, null=True)),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('contact_number', models.CharField(blank=True, max_length=20, null=True, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '+999999999'. Up to 20 digits allowed.", regex='^\\+?1?\\d{9,15}$')])),
('company_name', models.CharField(blank=True, max_length=500, null=True)),
('last_name', models.CharField(blank=True, max_length=500, null=True)),
('city', models.CharField(blank=True, max_length=500, null=True)),
('state', models.CharField(blank=True, max_length=500, null=True)),
('contry', models.CharField(blank=True, max_length=500, null=True)),
('contact_list', models.ManyToManyField(related_name='failed_contacts', to='marketing.ContactList')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='marketing_failed_contacts_created_by', to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -1,228 +1,57 @@
from datetime import datetime
from datetime import datetime, timedelta
from django.db import models
# from django.utils.translation import ugettext_lazy as _
from django.utils.timesince import timesince
from django.utils.translation import ugettext_lazy as _
from django.core.validators import RegexValidator
# from django.db.models import Sum
# from django.template.defaultfilters import slugify
from django.db.models import Sum
from django.template.defaultfilters import slugify
from common.models import User
class Tag(models.Model):
name = models.CharField(max_length=500)
color = models.CharField(max_length=20, default="#999999", verbose_name=_("color"))
created_by = models.ForeignKey(User, related_name="marketing_tags", null=True, on_delete=models.SET_NULL)
color = models.CharField(max_length=20,
default="#999999", verbose_name=_("color"))
created_by = models.ForeignKey(User,
related_name="marketing_tags",
null=True, on_delete=models.SET_NULL)
created_on = models.DateTimeField(auto_now_add=True)
@property
def created_by_user(self):
return self.created_by.serialized_data() if self.created_by else None
def attachment_url(self, filename):
file_extension = filename.split('.')[-1]
return "%s/Documents/%s" % (
self.company, "attachment_" + str(slugify(datetime.now())) + "." + file_extension)
# file_split = filename.split('.')
# file_extension = file_split[-1]
# path = "%s_%s_%s" % (file_split[0], self.profile.user.email, str(datetime.now()))
# return str(self.company.subdomain) + "/users/" + str(self.profile.user.email) + \
# "/docs/" + slugify(path) + "." + file_extension
class Document(models.Model):
title = models.CharField(max_length=155)
description = models.CharField(max_length=2500, blank=True, null=True)
document_file = models.FileField(upload_to=attachment_url)
created_by = models.ForeignKey(User, related_name="created_marketing_documents", null=True, on_delete=models.SET_NULL)
created_on = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.title
def is_document_file_image(self, ext):
image_ext_list = ['bmp', 'dds', 'gif', 'jpg', 'jpeg', 'png', 'psd', 'pspimage',
'tga', 'thm', 'tif', 'tiff', 'yuv']
return ext.lower() in image_ext_list
def is_document_file_audio(self, ext):
audio_ext_list = ['aif', 'iff', 'm3u', 'm4a', 'mid', 'mp3',
'mpa', 'wav', 'wma']
return ext.lower() in audio_ext_list
def is_document_file_video(self, ext):
video_ext_list = ['3g2', '3gp', 'asf', 'avi', 'flv', 'm4v', 'mov',
'mp4', 'mpg', 'rm', 'srt', 'swf', 'vob', 'wmv']
return ext.lower() in video_ext_list
def is_document_file_pdf(self, ext):
pdf_ext_list = ['indd', 'pct', 'pdf']
return ext.lower() in pdf_ext_list
def is_document_file_code(self, ext):
code_ext_list = ['aspx', 'json', 'jsp', 'do', 'htm', 'html', 'ser',
'php', 'jad', 'cfm', 'xml', 'js', 'pod', 'asp',
'atomsvc', 'rdf', 'pou', 'jsf', 'abs', 'pl', 'asm',
'srz', 'luac', 'cod', 'lib', 'arxml', 'bas', 'ejs',
'fs', 'hbs', 's', 'ss', 'cms', 'pyc', 'vcxproj',
'jse', 'smali', 'xla', 'lxk', 'pdb', 'src', 'cs',
'ipb', 'ave', 'mst', 'vls', 'rcc', 'sax', 'scr',
'dtd', 'axd', 'mrl', 'xsl', 'ino', 'spr', 'xsd',
'cgi', 'isa', 'ws', 'rss', 'dvb', 'nupkg', 'xlm',
'v4e', 'rss', 'prg', 'form', 'bat', 'mrc', 'asi',
'jdp', 'fmb', 'graphml', 'gcode', 'aia', 'py', 'atp',
'mzp', 'o', 'scs', 'mm', 'cpp', 'java', 'gypi', 'idb',
'txml', 'c', 'vip', 'tra', 'rc', 'action', 'vlx',
'asta', 'pyo', 'lua', 'gml', 'prl', 'rfs', 'cpb',
'sh', 'rbf', 'gp', 'phtml', 'bp', 'scb', 'sln', 'vbp',
'wbf', 'bdt', 'mac', 'rpy', 'eaf', 'mc', 'mwp', 'gnt',
'h', 'swift', 'e', 'styl', 'cxx', 'as', 'liquid',
'dep', 'fas', 'vbs', 'aps', 'vbe', 'lss', 'cmake',
'resx', 'csb', 'dpk', 'pdml', 'txx', 'dbg', 'jsa',
'sxs', 'sasf', 'pm', 'csx', 'r', 'wml', 'au3', 'stm',
'cls', 'cc', 'ins', 'jsc', 'dwp', 'rpg', 'arb', 'bml',
'inc', 'eld', 'sct', 'sm', 'wbt', 'csproj', 'tcz',
'html5', 'gbl', 'cmd', 'dlg', 'tpl', 'rbt', 'xcp',
'tpm', 'qry', 'mfa', 'ptx', 'lsp', 'pag', 'ebc',
'php3', 'cob', 'csc', 'pyt', 'dwt', 'rb', 'wsdl',
'lap', 'textile', 'sfx', 'x', 'a5r', 'dbp', 'pmp',
'ipr', 'fwx', 'pbl', 'vbw', 'phl', 'cbl', 'pas',
'mom', 'dbmdl', 'lol', 'wdl', 'ppam', 'plx', 'vb',
'cgx', 'lst', 'lmp', 'vd', 'bcp', 'thtml', 'scpt',
'isu', 'mrd', 'perl', 'dtx', 'f', 'wpk', 'ipf', 'ptl',
'luca', 'hx', 'uvproj', 'qvs', 'vba', 'xjb',
'appxupload', 'ti', 'svn-base', 'bsc', 'mak',
'vcproj', 'dsd', 'ksh', 'pyw', 'bxml', 'mo', 'irc',
'gcl', 'dbml', 'mlv', 'wsf', 'tcl', 'dqy', 'ssi',
'pbxproj', 'bal', 'trt', 'sal', 'hkp', 'vbi', 'dob',
'htc', 'p', 'ats', 'seam', 'loc', 'pli', 'rptproj',
'pxml', 'pkb', 'dpr', 'scss', 'dsb', 'bb', 'vbproj',
'ash', 'rml', 'nbk', 'nvi', 'lmv', 'mw', 'jl', 'dso',
'cba', 'jks', 'ary', 'run', 'vps', 'clm', 'brml',
'msha', 'mdp', 'tmh', 'rdf', 'jsx', 'sdl', 'ptxml',
'fxl', 'wmw', 'dcr', 'bcc', 'cbp', 'bmo', 'bsv',
'less', 'gss', 'ctl', 'rpyc', 'ascx', 'odc', 'wiki',
'obr', 'l', 'axs', 'bpr', 'ppa', 'rpo', 'sqlproj',
'smm', 'dsr', 'arq', 'din', 'jml', 'jsonp', 'ml',
'rc2', 'myapp', 'cla', 'xme', 'obj', 'jsdtscope',
'gyp', 'datasource', 'cp', 'rh', 'lpx', 'a2w', 'ctp',
'ulp', 'nt', 'script', 'bxl', 'gs', 'xslt', 'mg',
'pch', 'mhl', 'zpd', 'psm1', 'asz', 'm', 'jacl',
'pym', 'rws', 'acu', 'ssq', 'wxs', 'coffee', 'ncb',
'akt', 'pyx', 'zero', 'hs', 'mkb', 'tru', 'xul',
'mfl', 'sca', 'sbr', 'master', 'opv', 'matlab',
'sami', 'agc', 'slim', 'tea', 'pbl', 'm51', 'mec',
'asc', 'gch', 'enml', 'ino', 'kst', 'jade', 'dfb',
'ips', 'rgs', 'vbx', 'cspkg', 'ncx', 'brs', 'wfs',
'ifp', 'nse', 'xtx', 'j', 'cx', 'ps1', 'nas', 'mk',
'ccs', 'vrp', 'lnp', 'cml', 'c#', 'idl', 'exp', 'apb',
'nsi', 'asmx', 'tdo', 'pjt', 'fdt', 's5d', 'mvba',
'mf', 'odl', 'bzs', 'jardesc', 'tgml', 'moc', 'wxi',
'cpz', 'fsx', 'jav', 'ocb', 'agi', 'tec', 'txl',
'amw', 'mscr', 'dfd', 'dpd', 'pun', 'f95', 'vdproj',
'xsc', 'diff', 'wxl', 'dgml', 'airi', 'kmt', 'ksc',
'io', 'rbw', 'sas', 'vcp', 'resources', 'param',
'cg', 'hlsl', 'vssscc', 'bgm', 'xn', 'targets', 'sl',
'gsc', 'qs', 'owl', 'devpak', 'phps', 'hdf', 'pri',
'nbin', 'xaml', 's4e', 'scm', 'tk', 'poc', 'uix',
'clw', 'factorypath', 's43', 'awd', 'htr', 'php2',
'classpath', 'pickle', 'rob', 'msil', 'ebx', 'tsq',
'lml', 'f90', 'lds', 'vup', 'pbi', 'swt', 'vap', 'ig',
'pdo', 'frt', 'fcg', 'c++', 'xcl', 'dfn', 'aar',
'for', 're', 'twig', 'ebm', 'dhtml', 'hc', 'pro',
'ahk', 'rule', 'bsh', 'jcs', 'zrx', 'wsdd', 'csp',
'drc', 'appxsym']
return ext.lower() in code_ext_list
def is_document_file_text(self, ext):
text_ext_list = ['doc', 'docx', 'log', 'msg', 'odt', 'pages', 'rtf',
'tex', 'txt', 'wpd', 'wps']
return ext.lower() in text_ext_list
def is_document_file_sheet(self, ext):
sheet_ext_list = ['csv', 'xls', 'xlsx',
'xlsm', 'xlsb', 'xltx', 'xltm', 'xlt']
return ext.lower() in sheet_ext_list
def is_document_file_zip(self, ext):
ext_list = ['zip', '7Z', 'gz', 'rar', 'ZIPX', 'ACE', 'tar', ]
return ext.lower() in ext_list
def file_type(self):
name_ext_list = self.document_file.url.split(".")
if (len(name_ext_list) > 1):
ext = name_ext_list[int(len(name_ext_list) - 1)]
if self.is_document_file_audio(ext):
return ("audio", "fa fa-file-audio-o")
elif self.is_document_file_video(ext):
return ("video", "fa fa-file-video-o")
elif self.is_document_file_image(ext):
return ("image", "fa fa-file-image-o")
elif self.is_document_file_pdf(ext):
return ("pdf", "fa fa-file-pdf-o")
elif self.is_document_file_code(ext):
return ("code", "fa fa-file-code-o")
elif self.is_document_file_text(ext):
return ("text", "fa fa-file-text")
elif self.is_document_file_sheet(ext):
return ("sheet", "fa fa-file-excel-o")
elif self.is_document_file_zip(ext):
return ("zip", "fa fa-file-archive-o")
return ("file", "fa fa-file-o")
return ("file", "fa fa-file-o")
@property
def file_type_code(self):
return self.file_type()
@property
def document_file_path(self):
return self.document_file.url if self.document_file else None
@property
def document_file_name(self):
return self.document_file.name if self.document_file else None
return self.created_by if self.created_by else None
class EmailTemplate(models.Model):
created_by = models.ForeignKey(User, related_name="marketing_emailtemplates", null=True, on_delete=models.SET_NULL)
created_by = models.ForeignKey(
User, related_name="marketing_emailtemplates",
null=True, on_delete=models.SET_NULL)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
title = models.CharField(max_length=5000)
subject = models.CharField(max_length=5000)
html = models.TextField()
class Meta:
unique_together = (("title", "company"),)
@property
def created_by_user(self):
return self.created_by.serialized_data() if self.created_by else None
return self.created_by if self.created_by else None
class ContactList(models.Model):
created_by = models.ForeignKey(User, related_name="marketing_contactlist", null=True, on_delete=models.SET_NULL)
created_by = models.ForeignKey(
User, related_name="marketing_contactlist",
null=True, on_delete=models.SET_NULL)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
name = models.CharField(max_length=500)
tags = models.ManyToManyField(Tag)
is_public = models.BooleanField(default=False)
class Meta:
unique_together = (("name", "company"),)
# is_public = models.BooleanField(default=False)
visible_to = models.ManyToManyField(
User, related_name="contact_lists_visible_to")
@property
def created_by_user(self):
return self.created_by.serialized_data() if self.created_by else None
@property
def created_by_user_profile(self):
if self.created_by is not None:
profile = Profile.objects.filter(
user=self.created_by, company=self.company).last()
if profile is not None:
return Skinner().parse(profile, fields=[
'employee_name', 'userid', 'profile_pic_path', 'employee_id'])
else:
return self.created_by.serialized_data()
else:
return None
return self.created_by if self.created_by else None
@property
def created_on_format(self):
@ -230,16 +59,18 @@ class ContactList(models.Model):
@property
def created_on_since(self):
return created_since(self.created_on)
now = datetime.now()
difference = now.replace(tzinfo=None) - \
self.created_on.replace(tzinfo=None)
@property
def created_by_company(self):
return self.company.serialized_data() if self.company else None
if difference <= timedelta(minutes=1):
return 'just now'
return '%(time)s ago' % {
'time': timesince(self.created_on).split(', ')[0]}
@property
def tags_data(self):
return Skinner().parse(
self.tags.all(), fields=["id", "name", "color"])['data']
return self.tags.all()
@property
def no_of_contacts(self):
@ -260,21 +91,27 @@ class ContactList(models.Model):
@property
def no_of_clicks(self):
clicks = CampaignLog.objects.filter(
contact__contact_list__in=[self]).aggregate(Sum('no_of_clicks'))['no_of_clicks__sum']
contact__contact_list__in=[self]).aggregate(Sum(
'no_of_clicks'))['no_of_clicks__sum']
return clicks
class Contact(models.Model):
phone_regex = RegexValidator(
regex=r'^\+?1?\d{9,15}$',
message="Phone number must be entered in the format: '+999999999'. Up to 20 digits allowed."
message="Phone number must be entered in the format: '+999999999'. \
Up to 20 digits allowed."
)
created_by = models.ForeignKey(User, related_name="marketing_contacts_created_by", null=True, on_delete=models.SET_NULL)
created_by = models.ForeignKey(
User, related_name="marketing_contacts_created_by",
null=True, on_delete=models.SET_NULL)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
contact_list = models.ManyToManyField(ContactList, related_name="contacts")
name = models.CharField(max_length=500)
email = models.EmailField()
contact_number = models.CharField(validators=[phone_regex], max_length=20, blank=True, null=True)
contact_number = models.CharField(
validators=[phone_regex], max_length=20, blank=True, null=True)
is_unsubscribed = models.BooleanField(default=False)
is_bounced = models.BooleanField(default=False)
company_name = models.CharField(max_length=500, null=True, blank=True)
@ -283,8 +120,34 @@ class Contact(models.Model):
state = models.CharField(max_length=500, null=True, blank=True)
contry = models.CharField(max_length=500, null=True, blank=True)
class Meta:
unique_together = (("company", "email"),)
def __str__(self):
return self.email
class FailedContact(models.Model):
phone_regex = RegexValidator(
regex=r'^\+?1?\d{9,15}$',
message="Phone number must be entered in the format: '+999999999'.\
Up to 20 digits allowed."
)
created_by = models.ForeignKey(
User, related_name="marketing_failed_contacts_created_by", null=True,
on_delete=models.SET_NULL)
created_on = models.DateTimeField(auto_now_add=True)
contact_list = models.ManyToManyField(
ContactList, related_name="failed_contacts")
name = models.CharField(max_length=500, null=True, blank=True)
email = models.EmailField(null=True, blank=True)
contact_number = models.CharField(
validators=[phone_regex], max_length=20, blank=True, null=True)
company_name = models.CharField(max_length=500, null=True, blank=True)
last_name = models.CharField(max_length=500, null=True, blank=True)
city = models.CharField(max_length=500, null=True, blank=True)
state = models.CharField(max_length=500, null=True, blank=True)
contry = models.CharField(max_length=500, null=True, blank=True)
def __str__(self):
return self.email
class Campaign(models.Model):
@ -297,10 +160,15 @@ class Campaign(models.Model):
)
title = models.CharField(max_length=5000)
created_by = models.ForeignKey(User, related_name="marketing_campaigns_created_by", null=True, on_delete=models.SET_NULL)
created_by = models.ForeignKey(
User, related_name="marketing_campaigns_created_by",
null=True, on_delete=models.SET_NULL)
created_on = models.DateTimeField(auto_now_add=True)
contact_lists = models.ManyToManyField(ContactList, related_name="campaigns")
email_template = models.ForeignKey(EmailTemplate, blank=True, null=True, on_delete=models.SET_NULL)
updated_on = models.DateTimeField(auto_now=True)
contact_lists = models.ManyToManyField(
ContactList, related_name="campaigns")
email_template = models.ForeignKey(
EmailTemplate, blank=True, null=True, on_delete=models.SET_NULL)
schedule_date_time = models.DateTimeField(blank=True, null=True)
reply_to_email = models.EmailField(blank=True, null=True)
subject = models.CharField(max_length=5000)
@ -312,16 +180,39 @@ class Campaign(models.Model):
opens = models.IntegerField(default='0', blank=True)
opens_unique = models.IntegerField(default='0', blank=True)
bounced = models.IntegerField(default='0')
status = models.CharField(default="Preparing", choices=STATUS_CHOICES, max_length=20)
status = models.CharField(
default="Preparing", choices=STATUS_CHOICES, max_length=20)
@property
def no_of_unsubscribers(self):
unsubscribers = self.campaign_contacts.filter(contact__is_unsubscribed=True).count()
unsubscribers = self.campaign_contacts.filter(
contact__is_unsubscribed=True).count()
return unsubscribers
@property
def no_of_bounces(self):
bounces = self.campaign_contacts.filter(
contact__is_bounced=True).count()
return bounces
@property
def no_of_clicks(self):
clicks = self.marketing_links.aggregate(Sum('clicks'))['clicks__sum']
return clicks
@property
def no_of_sent_emails(self):
contacts = self.campaign_contacts.count()
return contacts
@property
def created_on_format(self):
return self.created_on.strftime('%b %d, %Y %I:%M %p')
class Link(models.Model):
campaign = models.ForeignKey(Campaign, related_name="marketing_links", on_delete=models.CASCADE)
campaign = models.ForeignKey(
Campaign, related_name="marketing_links", on_delete=models.CASCADE)
original = models.URLField(max_length=2100)
clicks = models.IntegerField(default='0')
unique = models.IntegerField(default='0')
@ -329,18 +220,23 @@ class Link(models.Model):
class CampaignLog(models.Model):
created_on = models.DateTimeField(auto_now_add=True)
campaign = models.ForeignKey(Campaign, related_name='campaign_contacts', on_delete=models.CASCADE)
contact = models.ForeignKey(Contact, related_name="marketing_campaign_logs", null=True, on_delete=models.SET_NULL)
campaign = models.ForeignKey(
Campaign, related_name='campaign_contacts', on_delete=models.CASCADE)
contact = models.ForeignKey(
Contact, related_name="marketing_campaign_logs",
null=True, on_delete=models.SET_NULL)
message_id = models.CharField(max_length=1000, null=True, blank=True)
class CampaignLinkClick(models.Model):
campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE)
link = models.ForeignKey(Link, blank=True, null=True, on_delete=models.CASCADE)
link = models.ForeignKey(
Link, blank=True, null=True, on_delete=models.CASCADE)
ip_address = models.GenericIPAddressField()
created_on = models.DateTimeField(auto_now_add=True)
user_agent = models.CharField(max_length=2000, blank=True, null=True)
contact = models.ForeignKey(Contact, blank=True, null=True, on_delete=models.CASCADE)
contact = models.ForeignKey(
Contact, blank=True, null=True, on_delete=models.CASCADE)
class CampaignOpen(models.Model):
@ -348,4 +244,5 @@ class CampaignOpen(models.Model):
ip_address = models.GenericIPAddressField()
created_on = models.DateTimeField(auto_now_add=True)
user_agent = models.CharField(max_length=2000, blank=True, null=True)
contact = models.ForeignKey(Contact, blank=True, null=True, on_delete=models.CASCADE)
contact = models.ForeignKey(
Contact, blank=True, null=True, on_delete=models.CASCADE)

View file

@ -1,4 +1,13 @@
import datetime
import hashlib
import pytz
import requests
from celery.task import task
from django.conf import settings
from django.core.mail import EmailMessage
from django.template import Template, Context
from common.utils import convert_to_custom_timezone
from marketing.models import Contact, ContactList, Campaign, CampaignLog
@task
@ -14,3 +23,152 @@ def campaign_open(request):
@task
def campaign_click(request):
pass
@task
def upload_csv_file(data, user, contact_lists):
for each in data:
contact = Contact.objects.filter(email=each['email']).first()
if not contact:
contact = Contact.objects.create(
email=each['email'], created_by_id=user,
name=each['first name'])
if each.get('company name', None):
contact.company_name = each['company name']
if each.get('last name', None):
contact.last_name = each['last name']
if each.get('city', None):
contact.city = each['city']
if each.get("state", None):
contact.state = each['state']
contact.save()
for contact_list in contact_lists:
contact.contact_list.add(ContactList.objects.get(id=int(contact_list)))
def send_campaign_mail(subject, content, from_email, to_email, bcc, reply_to, attachments):
msg = EmailMessage(
subject,
content,
from_email,
to_email,
bcc,
reply_to=reply_to,
)
for attachment in attachments:
msg.attach(*attachment)
msg.content_subtype = "html"
res = msg.send()
print (res)
def get_campaign_message_id(campaign):
hash_ = hashlib.md5()
hash_.update(
str(str(campaign.id) + str(campaign.campaign.created_by.id)).encode('utf-8') +
str(datetime.datetime.now()).encode('utf-8')
)
file_hash = hash_.hexdigest()
return file_hash
@task
def run_campaign(campaign):
try:
campaign = Campaign.objects.get(id=campaign)
subject = campaign.subject
# project_settings = Settings.objects.filter().first()
contacts = []
for each_list in campaign.contact_lists.all():
contacts = Contact.objects.filter(contact_list__in=[each_list])
# contacts = campaign.contacts.filter(Q(is_bounced=False) | Q(is_unsubscribed=False)).distinct()
default_html = campaign.html_processed
for each_contact in contacts:
html = default_html
campaign_log = CampaignLog.objects.create(contact=each_contact,
campaign=campaign)
if campaign.reply_to_email:
reply_to_email = campaign.reply_to_email
else:
message_id = get_campaign_message_id(campaign_log)
campaign_log.message_id = message_id
campaign_log.save()
domain_name = settings.SUBDOMAIN_NAME + '.' + project_settings.domain if project_settings else ''
# domain_name = 'micro.peeljobs.com'
if campaign.from_email is not None:
from_email = campaign.from_email
else:
from_email = campaign.created_by.email
reply_to_email = str(from_email) + ' <' + str(message_id + '@' + domain_name + '') + '>'
if not (each_contact.is_bounced or each_contact.is_unsubscribed):
# link = '<img src="https://micropyramid.localtunnel.me/track-email/' + \
# str(campaign_log.id) + '/" height="1" width="1"</img>'
domain_url = '%s%s' % (settings.SCHEME, settings.HOST_URL)
link = '<img src="' + domain_url + '/track-email/' + \
str(campaign_log.id) + '/contact/' + str(each_contact.id) + '/" height="1" width="1" />'
names_dict = {'company_name': each_contact.company_name if each_contact.company_name else '',
'last_name': each_contact.last_name if each_contact.last_name else '',
'city': each_contact.city if each_contact.city else '',
'state': each_contact.state if each_contact.state else '',
'first_name': each_contact.name,
'email': each_contact.email, 'email_id': each_contact.id}
html = Template(html).render(Context(names_dict))
mail_html = html + link
from_email = str(campaign.from_name) + "<" + str(campaign.from_email) + '>'
# text_content = re.sub(r'<(.*?)>', '', html)
# ReplyMail.objects.create(campaign_log=campaign_log,
# reply_from=campaign.created_by.email,
# text=text_content,
# sent_on=datetime.datetime.now())
send_campaign_mail(
subject, mail_html, from_email, [each_contact.email], [], [reply_to_email], [])
except Exception as e:
print (e)
pass
@task
def run_all_campaigns():
start_date = datetime.date.today()
campaigns = Campaign.objects.filter(schedule_date_time__date=start_date)
for each in campaigns:
run_campaign(each.id)
@task
def list_all_bounces_unsubscribes():
bounces = requests.get('https://api.sendgrid.com/api/bounces.get.json?api_user=' +
settings.EMAIL_HOST_USER + '&api_key=' + settings.EMAIL_HOST_PASSWORD)
for each in bounces.json():
contact = Contact.objects.filter(email=each['email']).first()
if contact:
contact.is_bounced = True
contact.save()
bounces = requests.get('https://api.sendgrid.com/api/unsubscribes.get.json?api_user=' +
settings.EMAIL_HOST_USER + '&api_key=' + settings.EMAIL_HOST_PASSWORD)
for each in bounces.json():
contact = Contact.objects.filter(email=each['email']).first()
if contact:
contact.is_unsubscribed = True
contact.save()
@task
def send_scheduled_campaigns():
from datetime import datetime
campaigns = Campaign.objects.filter(schedule_date_time__isnull=False)
for each in campaigns:
sent_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
sent_time = datetime.strptime(sent_time, '%Y-%m-%d %H:%M:%S')
local_tz = pytz.timezone('UTC')
schedule_date_time_hour = str(each.schedule_date_time.time()).replace(':00:00', '')
sent_time = local_tz.localize(sent_time)
sent_time = convert_to_custom_timezone(sent_time, each.timezone)
if (
str(each.schedule_date_time.date()) == str(sent_time.date()) and
str(schedule_date_time_hour) == str(sent_time.hour)
):
run_campaign(each)

View file

@ -1,3 +1,68 @@
from django.test import TestCase
from django.test import Client
from common.models import User
# from marketing.models import Tag, Document, ContactList, EmailTemplate, Contact, Campaign, CampaignLinkClick, CampaignLog, Link, CampaignOpen
# from .models import *
from marketing.views import *
# Create your tests here.
class TestMarketingModel(object):
def setUp(self):
self.client = Client()
self.user = User.objects.create(
username='jorge', email='j@mp.com', role="ADMIN")
self.user.set_password('jorge2293')
self.user.save()
self.client.login(username='j@mp.com', password='jorge2293')
class TestTemplates(TestMarketingModel, TestCase):
def test_templates(self):
url1 = '/m/'
url2 = '/m/cl/all/'
url3 = '/m/cl/edit_contact/'
url4 = '/m/cl/lists/'
url5 = '/m/cl/list/new/'
url6 = '/m/cl/list/cnew/'
url7 = '/m/cl/list/detail/'
url8 = '/m/et/list/'
url9 = '/m/et/new/'
# url10 = '/m/et/edit/'
# url11 = '/m/et/detail/'
url12 = '/m/cm/list/'
url13 = '/m/cm/new/'
# url14 = '/m/cm/edit/'
# url15 = '/m/cm/details/'
resp1 = self.client.get(url1)
resp2 = self.client.get(url2)
resp3 = self.client.get(url3)
resp4 = self.client.get(url4)
resp5 = self.client.get(url5)
resp6 = self.client.get(url6)
resp7 = self.client.get(url7)
resp8 = self.client.get(url8)
resp9 = self.client.get(url9)
# resp10 = self.client.get(url10)
# resp11 = self.client.get(url11)
resp12 = self.client.get(url12)
resp13 = self.client.get(url13)
# resp14 = self.client.get(url14)
# resp15 = self.client.get(url15)
self.assertEqual(resp1.status_code, 200)
self.assertEqual(resp2.status_code, 200)
self.assertEqual(resp3.status_code, 200)
self.assertEqual(resp4.status_code, 200)
self.assertEqual(resp5.status_code, 200)
self.assertEqual(resp6.status_code, 200)
self.assertEqual(resp7.status_code, 200)
self.assertEqual(resp8.status_code, 200)
self.assertEqual(resp9.status_code, 200)
# self.assertEqual(resp10.status_code, 200)
# self.assertEqual(resp11.status_code, 200)
self.assertEqual(resp12.status_code, 200)
self.assertEqual(resp13.status_code, 200)
# self.assertEqual(resp14.status_code, 200)
# self.assertEqual(resp15.status_code, 200)

View file

@ -1,8 +1,9 @@
from django.urls import path
from .views import (
dashboard, contact_lists, contacts_list, contact_list_new, contacts_list_new, contact_list_detail, edit_contact,
email_template_list, email_template_new, email_template_edit,
email_template_detail, campaign_list, campaign_new, campaign_edit, campaign_details
email_template_list, email_template_new, email_template_edit, email_template_delete,
email_template_detail, campaign_list, campaign_new, campaign_edit, campaign_details, campaign_delete,
edit_contact_list, delete_contact_list
)
app_name = 'marketing'
@ -14,16 +15,20 @@ urlpatterns = [
path('cl/edit_contact/', edit_contact, name='edit_contact'),
path('cl/lists/', contacts_list, name='contacts_list'),
path('cl/list/new/', contact_list_new, name='contact_list_new'),
path('cl/list/edit/<int:pk>/', edit_contact_list, name='edit_contact_list'),
path('cl/list/delete/<int:pk>/', delete_contact_list, name='delete_contact_list'),
path('cl/list/cnew/', contacts_list_new, name='contacts_list_new'),
path('cl/list/detail/', contact_list_detail, name='contact_list_detail'),
path('et/list/', email_template_list, name='email_template_list'),
path('et/new/', email_template_new, name='email_template_new'),
path('et/edit/', email_template_edit, name='email_template_edit'),
path('et/detail/', email_template_detail, name='email_template_detail'),
path('et/<int:pk>/edit/', email_template_edit, name='email_template_edit'),
path('et/<int:pk>/detail/', email_template_detail, name='email_template_detail'),
path('et/<int:pk>/delete/', email_template_delete, name='email_template_delete'),
path('cm/list/', campaign_list, name='campaign_list'),
path('cm/new/', campaign_new, name='campaign_new'),
path('cm/edit/', campaign_edit, name='campaign_edit'),
path('cm/details/', campaign_details, name='campaign_details')
path('cm/<int:pk>/edit/', campaign_edit, name='campaign_edit'),
path('cm/<int:pk>/details/', campaign_details, name='campaign_details'),
path('cm/<int:pk>/delete/', campaign_delete, name='campaign_delete')
]

View file

@ -1,61 +1,418 @@
from django.shortcuts import render
import json
import pytz
import lxml
import lxml.html
from lxml.cssselect import CSSSelector
from django.conf import Settings
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db.models import Q
from django.http.response import (JsonResponse,
HttpResponse, HttpResponseRedirect)
from django.shortcuts import render, get_object_or_404
from django.urls import reverse_lazy, reverse
from common import status
from marketing.models import (
Tag, ContactList, Contact, EmailTemplate, Campaign, CampaignLog, Link
)
from marketing.forms import ContactListForm, ContactForm, EmailTemplateForm, SendCampaignForm
from marketing.tasks import upload_csv_file, run_campaign
TIMEZONE_CHOICES = [(tz, tz) for tz in pytz.common_timezones]
def get_exact_match(query, m2m_field, ids):
# query = tasks_list.annotate(count=Count(m2m_field))\
# .filter(count=len(ids))
query = query
for _id in ids:
query = query.filter(**{m2m_field: _id})
return query
@login_required(login_url='/login')
def dashboard(request):
return render(request, 'marketing/dashboard.html')
@login_required(login_url='/login')
def contact_lists(request):
return render(request, 'marketing/lists/index.html')
tags = Tag.objects.all()
if (request.user.is_superuser):
queryset = ContactList.objects.all()
else:
queryset = ContactList.objects.filter(created_by=request.user)
if request.method == 'POST':
post_tags = request.POST.get('tags')
if request.POST.get('search'):
queryset = queryset.filter(
Q(name__icontains=request.POST['search']) |
Q(created_by__email__icontains=request.POST[
'search'])).distinct()
if post_tags:
filtered_list = json.loads(request.POST['tags'])
if len(filtered_list) > 1:
queryset = get_exact_match(queryset, 'tags', filtered_list)
else:
queryset = queryset.filter(tags__id__in=filtered_list)
per_page = request.GET.get("per_page", 10)
paginator = Paginator(queryset, per_page)
page = request.GET.get('page', 1)
try:
contact_lists = paginator.page(page)
except PageNotAnInteger:
contact_lists = paginator.page(1)
except EmptyPage:
contact_lists = paginator.page(paginator.num_pages)
data = {'contact_lists': contact_lists, 'tags': tags}
return render(request, 'marketing/lists/index.html', data)
@login_required(login_url='/login')
def contacts_list(request):
return render(request, 'marketing/lists/all.html')
contacts = Contact.objects.all()
data = {"contacts": contacts}
return render(request, 'marketing/lists/all.html', data)
@login_required(login_url='/login')
def contact_list_new(request):
return render(request, 'marketing/lists/new.html')
data = {}
if request.method == "POST":
form = ContactListForm(request.POST, request.FILES)
if form.is_valid():
instance = form.save(commit=False)
instance.created_by = request.user
instance.save()
tags = request.POST['tags'].split(
',') if request.POST['tags'] else []
for each in tags:
tag, _ = Tag.objects.get_or_create(
name=each, created_by=request.user)
instance.tags.add(tag)
if request.FILES.get('contacts_file'):
upload_csv_file.delay(
form.validated_rows, request.user.id, [instance.id])
return JsonResponse({'error': False,
'data': form.data},
status=status.HTTP_201_CREATED)
else:
# return JsonResponse({'error': True, 'errors': form.errors},
# status=status.HTTP_400_BAD_REQUEST)
return JsonResponse(
{'error': True, 'errors': form.errors},
status=status.HTTP_200_OK)
else:
return render(request, 'marketing/lists/new.html', data)
@login_required(login_url='/login')
def edit_contact_list(request, pk):
user = request.user
try:
contact_list = ContactList.objects.get(pk=pk)
except ContactList.DoesNotExist:
contact_list = ContactList.objects.none()
return JsonResponse({}, status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
# contact_lists = ContactList.objects.filter(company=request.company)
if (user.is_superuser):
contact_lists = ContactList.objects.all()
else:
contact_lists = ContactList.objects.filter(created_by=request.user)
data = {'contact_list': contact_list, 'contact_lists': contact_lists}
return render(request, 'marketing/lists/new.html', data)
else:
form = ContactListForm(
request.POST, request.FILES, instance=contact_list)
if form.is_valid():
instance = form.save()
instance.tags.clear()
tags = request.POST['tags'].split(
',') if request.POST['tags'] else []
for each in tags:
tag, _ = Tag.objects.get_or_create(
name=each, created_by=request.user)
instance.tags.add(tag)
if request.FILES.get('contacts_file'):
upload_csv_file.delay(
form.validated_rows, request.user.id, [instance.id])
return JsonResponse({'error': False,
'data': form.data}, status=status.HTTP_200_OK)
return JsonResponse({'error': True,
'errors': form.errors}, status=status.HTTP_200_OK)
@login_required(login_url='/login')
def view_contact_list(request, pk):
contact_list = get_object_or_404(ContactList, pk=pk)
contacts = Contact.objects.filter(contact_list__in=[contact_list])
if request.POST and request.POST.get("search"):
contacts = contacts.filter(
Q(name__icontains=request.POST['search']) |
Q(email__icontains=request.POST['search']))
per_page = request.GET.get("per_page", 10)
paginator = Paginator(contacts.order_by('id'), per_page)
page = request.GET.get('page', 1)
try:
contacts = paginator.page(page)
except PageNotAnInteger:
contacts = paginator.page(1)
except EmptyPage:
contacts = paginator.page(paginator.num_pages)
data = {'contact_list': contact_list, "contacts": contacts}
template_name = "marketing/lists/all.html"
return render(request, template_name, data)
@login_required(login_url='/login')
def delete_contact_list(request, pk):
try:
# ContactList.objects.get(pk=pk).delete()
redirect_to = reverse('marketing:contact_lists')
except ContactList.DoesNotExist:
redirect_to = reverse('marketing:contact_lists')
return HttpResponseRedirect(redirect_to)
@login_required(login_url='/login')
def contacts_list_new(request):
return render(request, 'marketing/lists/cnew.html')
if request.POST:
form = ContactForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.created_by = request.user
instance.save()
for each in json.loads(request.POST['contact_list']):
instance.contact_list.add(ContactList.objects.get(id=each))
# return JsonResponse(form.data, status=status.HTTP_201_CREATED)
return reverse('marketing:contact_list')
else:
data = {'error': True, 'errors': form.errors}
else:
if (request.user.is_superuser):
queryset = ContactList.objects.all()
else:
queryset = ContactList.objects.filter(created_by=request.user)
data = {"contact_list": queryset}
return render(request, 'marketing/lists/cnew.html', data)
@login_required(login_url='/login')
def edit_contact(request):
return render(request, 'marketing/lists/edit_contact.html')
@login_required(login_url='/login')
def contact_list_detail(request):
return render(request, 'marketing/lists/detail.html')
@login_required(login_url='/login')
def email_template_list(request):
return render(request, 'marketing/email_template/index.html')
if (request.user.is_admin or request.user.is_superuser):
queryset = EmailTemplate.objects.all()
else:
queryset = EmailTemplate.objects.filter(
created_by=request.user)
if request.method == 'POST':
queryset = queryset.filter(
Q(title__icontains=request.POST['search']) |
Q(subject__icontains=request.POST['search']) |
Q(created_by__email__icontains=request.POST['search']))
per_page = request.GET.get("per_page", 10)
paginator = Paginator(queryset.distinct(), per_page)
page = request.GET.get('page')
try:
email_templates = paginator.page(page)
except PageNotAnInteger:
email_templates = paginator.page(1)
except EmptyPage:
email_templates = paginator.page(paginator.num_pages)
data = {'email_templates': email_templates}
return render(request, 'marketing/email_template/index.html', data)
@login_required(login_url='/login')
def email_template_new(request):
return render(request, 'marketing/email_template/new.html')
if request.POST:
form = EmailTemplateForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.created_by = request.user
instance.save()
return JsonResponse({'error': False, 'data': form.data}, status=status.HTTP_201_CREATED)
else:
return JsonResponse({'error': True, 'errors': form.errors}, status=status.HTTP_200_OK)
else:
return render(request, 'marketing/email_template/new.html')
def email_template_edit(request):
return render(request, 'marketing/email_template/edit.html')
@login_required(login_url='/login')
def email_template_edit(request, pk):
email_template = get_object_or_404(EmailTemplate, pk=pk)
if request.method == 'GET':
data = {'email_template': email_template}
return render(request, 'marketing/email_template/new.html', data)
else:
form = EmailTemplateForm(request.POST, instance=email_template)
if form.is_valid():
instance = form.save(commit=False)
# instance.created_by = request.user
instance.save()
return JsonResponse({'error': False, 'data': form.data}, status=status.HTTP_201_CREATED)
return JsonResponse({'error': True, 'errors': form.errors}, status=status.HTTP_200_OK)
def email_template_detail(request):
return render(request, 'marketing/email_template/detail.html')
@login_required(login_url='/login')
def email_template_detail(request, pk):
queryset = get_object_or_404(EmailTemplate, id=pk)
data = {'email_template': queryset}
return render(request, 'marketing/email_template/details.html', data)
@login_required(login_url='/login')
def email_template_delete(request, pk):
try:
# EmailTemplate.objects.get(id=pk).delete()
redirect_to = reverse('marketing:email_template_list')
except ContactList.DoesNotExist:
redirect_to = reverse('marketing:email_template_list')
return HttpResponseRedirect(redirect_to)
@login_required(login_url='/login')
def campaign_list(request):
return render(request, 'marketing/campaign/index.html')
campaigns = Campaign.objects.all()
if request.method == 'POST':
campaigns = campaigns.filter(
Q(title__icontains=request.POST['search']) |
Q(created_by__email__icontains=request.POST['search'])
)
if request.POST['scheduled_on']:
campaigns = campaigns.filter(
schedule_date_time__date__lte=request.POST['scheduled_on'])
per_page = request.GET.get("per_page", 10) # Show 15 contacts per page
paginator = Paginator(campaigns.distinct().order_by('-created_on'), per_page)
page = request.GET.get('page')
try:
campaigns = paginator.page(page)
except PageNotAnInteger:
campaigns = paginator.page(1)
except EmptyPage:
campaigns = paginator.page(paginator.num_pages)
data = {'campaigns_list': campaigns}
return render(request, 'marketing/campaign/index.html', data)
@login_required(login_url='/login')
def campaign_new(request):
return render(request, 'marketing/campaign/new.html')
if request.method == 'GET':
if request.user.is_admin or request.user.is_superuser:
email_templates = EmailTemplate.objects.all()
else:
email_templates = EmailTemplate.objects.filter(
created_by=request.user)
if request.user.is_admin or request.user.is_superuser:
contact_lists = ContactList.objects.all()
else:
contact_lists = ContactList.objects.filter(
created_by=request.user)
# if request.GET.get('contact_list'):
# contacts = Contact.objects.filter(
# contact_list__id__in=json.loads(request.GET.get('contact_list'))).distinct()
data = {
'contact_lists': contact_lists, 'email_templates': email_templates, "timezones": TIMEZONE_CHOICES}
# return JsonResponse(data, status=status.HTTP_200_OK)
return render(request, 'marketing/campaign/new.html', data)
else:
print ("request.POST", request.POST)
form = SendCampaignForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.created_by = request.user
if request.POST['from_email']:
instance.from_email = request.POST['from_email']
if request.POST['from_name']:
instance.from_name = request.POST['from_name']
if request.POST['reply_to_email']:
instance.reply_to_email = request.POST['reply_to_email']
instance.save()
for each in json.loads(request.POST['contact_list']):
instance.contact_lists.add(ContactList.objects.get(id=each))
# contacts = Contact.objects.filter(contact_list__in=json.loads(request.POST['contact_list'])).distinct()
# for each_contact in contacts:
# instance.contacts.add(each_contact)
camp = instance
html = camp.html
dom = lxml.html.document_fromstring(html)
selAnchor = CSSSelector('a')
links = selAnchor(dom)
llist = []
# Extract links and unique links
for e in links:
llist.append(e.get('href'))
# get uniaue linnk
links = set(llist)
# Replace Links with new One
domain_url = '%s://%s' % (request.scheme, request.META['HTTP_HOST'])
if Link.objects.filter(campaign_id=camp.id):
Link.objects.filter(campaign_id=camp.id).delete()
for l in links:
link = Link.objects.create(campaign_id=camp.id, original=l)
html = html.replace(
'href="' + l + '"', 'href="' + domain_url + '/a/' + str(link.id) + '/e/{{email_id}}"')
camp.html_processed = html
camp.sent_status = "scheduled"
camp.save()
if request.POST['schedule_later'] == 'false':
run_campaign.delay(instance.id)
else:
schedule_date_time = request.POST.get('schedule_date_time', '')
# user_timezone = request.POST.get('timezone', '')
# if user_timezone and schedule_date_time:
# print ("schedule_date_time", schedule_date_time)
# print ("timezone", user_timezone)
# start_time_object = ddatetime.strptime(schedule_date_time, '%Y-%m-%d %H:%M:%S')
# schedule_date_time = convert_to_custom_timezone(start_time_object, user_timezone, to_utc=True)
# print ("After", schedule_date_time)
instance.schedule_date_time = schedule_date_time
# instance.timezone = request.POST['timezone']
instance.save()
return JsonResponse({'error': True, 'data': form.data}, status=status.HTTP_201_CREATED)
return JsonResponse({'error': True, 'errors': form.errors}, status=status.HTTP_200_OK)
@login_required(login_url='/login')
def campaign_edit(request):
return render(request, 'marketing/campaign/edit.html')
@login_required(login_url='/login')
def campaign_details(request):
return render(request, 'marketing/campaign/details.html')
@login_required(login_url='/login')
def campaign_delete(request):
return render(request, 'marketing/campaign/details.html')

View file

@ -12,15 +12,19 @@ class Opportunity(models.Model):
name = models.CharField(pgettext_lazy(
"Name of Opportunity", "Name"), max_length=64)
account = models.ForeignKey(
Account, related_name='opportunities', on_delete=models.CASCADE, blank=True, null=True)
Account, related_name='opportunities',
on_delete=models.CASCADE, blank=True, null=True)
stage = models.CharField(
pgettext_lazy("Stage of Opportunity", "Stage"), max_length=64, choices=STAGES)
pgettext_lazy("Stage of Opportunity",
"Stage"), max_length=64, choices=STAGES)
currency = models.CharField(
max_length=3, choices=CURRENCY_CODES, blank=True, null=True)
amount = models.DecimalField(
_("Opportunity Amount"), decimal_places=2, max_digits=12, blank=True, null=True)
_("Opportunity Amount"),
decimal_places=2, max_digits=12, blank=True, null=True)
lead_source = models.CharField(
_("Source of Lead"), max_length=255, choices=SOURCES, blank=True, null=True)
_("Source of Lead"), max_length=255,
choices=SOURCES, blank=True, null=True)
probability = models.IntegerField(default=0, blank=True, null=True)
contacts = models.ManyToManyField(Contact)
closed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
@ -30,7 +34,8 @@ class Opportunity(models.Model):
assigned_to = models.ManyToManyField(
User, related_name='opportunity_assigned_to')
created_by = models.ForeignKey(
User, related_name='opportunity_created_by', on_delete=models.SET_NULL, null=True)
User, related_name='opportunity_created_by',
on_delete=models.SET_NULL, null=True)
created_on = models.DateTimeField(_("Created on"), auto_now_add=True)
is_active = models.BooleanField(default=False)
tags = models.ManyToManyField(Tags, blank=True)

View file

@ -12,22 +12,24 @@ from cases.models import Case
class OpportunityModel(object):
def setUp(self):
self.user = User.objects.create(first_name="madhurima", username='madhurima', email="m@mp.com", role="ADMIN")
self.user = User.objects.create(
first_name="meghan", username='meghan',
email="m@mp.com", role="ADMIN")
self.user.set_password('madhu123')
self.user.save()
self.address = Address.objects.create(
street="kphb", city="hyderabad", postcode="584",
street="kphb", city="canada", postcode="584",
country='IN')
self.client.login(email='m@mp.com', password='madhu123')
self.account = Account.objects.create(
name="uday", email="uday@gmail.com",
name="john", email="john@gmail.com",
phone="58964",
billing_address_line="",
billing_street="kphb", billing_city="hyderabad",
billing_postcode="584", billing_country='IN',
billing_street="kphb", billing_city="canada",
billing_postcode="584", billing_country='US',
website="hello.com", industry="sw",
description="bgyyr", created_by=self.user)
@ -40,7 +42,7 @@ class OpportunityModel(object):
created_by=self.user)
self.opportunity = Opportunity.objects.create(
name="madhurima", amount="478",
name="meghan", amount="478",
stage="negotiation/review", lead_source="Call", probability="58",
closed_on="2016-05-04",
description="hgfdxc",
@ -58,7 +60,7 @@ class OpportunityModel(object):
class OpportunityCreateTestCase(OpportunityModel, TestCase):
def test_opportunity_create(self):
response = self.client.get('/opportunities/create/', {
'name': "madhurima", 'amount': "478",
'name': "meghan", 'amount': "478",
'stage': "NEGOTIATION/REVIEW",
'lead_source': "Call", 'probability': "58",
'closed_on': "2016-05-04", 'description': "hgfdxc"})
@ -66,14 +68,14 @@ class OpportunityCreateTestCase(OpportunityModel, TestCase):
def test_opportunity_create_post(self):
url = '/opportunities/create/'
data = {'name': "meghana", 'amount': "500", 'stage': "CLOSED WON"}
data = {'name': "micky", 'amount': "500", 'stage': "CLOSED WON"}
response = self.client.post(url, data)
# self.assertEqual(response.status_code, 302)
self.assertEqual(response.status_code, 200)
def test_opportunity_invalid(self):
url = '/opportunities/create/'
data = {'name': "meghana", 'amount': "", 'stage': ""}
data = {'name': "micky", 'amount': "", 'stage': ""}
response = self.client.post(url, data)
self.assertEqual(response.status_code, 200)
@ -81,12 +83,15 @@ class OpportunityCreateTestCase(OpportunityModel, TestCase):
class opportunityCreateTestCase(OpportunityModel, TestCase):
def test_view_opportunity(self):
response = self.client.get('/opportunities/' + str(self.opportunity.id) + '/view/')
response = self.client.get(
'/opportunities/' + str(self.opportunity.id) + '/view/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['opportunity_record'].id, self.opportunity.id)
self.assertEqual(
response.context['opportunity_record'].id, self.opportunity.id)
def test_del_opportunity_url(self):
response = self.client.get('/opportunities/' + str(self.opportunity.id) + '/delete/')
response = self.client.get(
'/opportunities/' + str(self.opportunity.id) + '/delete/')
self.assertEqual(response['location'], '/opportunities/list/')
def test_opportunity_delete(self):
@ -98,21 +103,25 @@ class opportunityCreateTestCase(OpportunityModel, TestCase):
class EditOpportunityTestCase(OpportunityModel, TestCase):
def test_edit_url(self):
response = self.client.get('/opportunities/' + str(self.opportunity.id) + '/edit/')
response = self.client.get(
'/opportunities/' + str(self.opportunity.id) + '/edit/')
self.assertEqual(response.status_code, 200)
def test_edit_opportunity(self):
response = self.client.get('/opportunities/' + str(self.opportunity.id) + '/edit/', {
'name': "madhurima", 'amount': "478",
'name': "meghan", 'amount': "478",
'stage': "negotiation/review",
'lead_source': "Call", 'probability': "58",
'closed_on': "2016-05-04", 'description': "hgfdxc"})
self.assertEqual(response.status_code, 200)
resp = self.client.post( '/opportunities/' + str(self.opportunity.id) + '/edit/', **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'})
self.assertEqual(resp.status_code, 200)
resp1 = self.client.post( '/opportunities/create/', **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'})
self.assertEqual(resp1.status_code, 200)
def test_update_opportunity(self):
url = '/opportunities/' + str(self.opportunity.id) + '/edit/'
data = {
'name': "madhurima", 'amount': "478", 'stage': "QUALIFICATION",
'name': "meghan", 'amount': "478", 'stage': "QUALIFICATION",
'probability': "58", 'closed_on': "2016-05-04", 'description': "hgfdxc"}
response = self.client.post(url, data)
# self.assertEqual(response.status_code, 302)
@ -137,14 +146,18 @@ class OpportunityListView(OpportunityModel, TestCase):
def test_opportunity_list_queryset(self):
self.account = Account.objects.all()
data = {
'name': 'madhurima', 'stage': 'city',
'name': 'meghan', 'stage': 'city',
'lead_source': 'Call', 'accounts': self.account}
response = self.client.post('/opportunities/list/', data)
get_opp_val = Opportunity.objects.get(lead_source='Call')
self.assertEqual(get_opp_val.lead_source, 'Call')
self.assertEqual(get_opp_val.name, str(get_opp_val))
get_contact = Contact.objects.get(last_name="s")
# get_account = Account.objects.get(name='john') # not done
# print(get_account.name,"wqieoruopwqeiruqrewiou")
# print(self.account.last())
self.assertEqual(get_contact.last_name, "s")
# self.assertEqual(get_account, self.account.last()) # not done
# self.assertEqual(get_opp_val.lead_source,'call')
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'opportunity.html')
@ -188,3 +201,12 @@ class AttachmentTestCase(OpportunityModel, TestCase):
# data = {'attachment_id':self.attachment.id}
# response = self.client.post(url,data)
# self.assertEqual(response.status_code, 200)
class TestGetOpportunitiesView(OpportunityModel, TestCase):
def test_get_page(self):
url = "/opportunities/get/list/"
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
self.assertIsNotNone(resp.context['opportunities'])

View file

@ -1,7 +1,10 @@
from django.urls import path
from opportunity.views import (
OpportunityListView, create_opportunity, OpportunityDetailView, update_opportunity,
DeleteOpportunityView, GetContactView, GetOpportunitiesView, AddCommentView,
OpportunityListView, create_opportunity,
OpportunityDetailView, update_opportunity,
DeleteOpportunityView, GetContactView,
GetOpportunitiesView,
AddCommentView,
UpdateCommentView, DeleteCommentView, AddAttachmentsView)
@ -13,16 +16,19 @@ urlpatterns = [
path('create/', create_opportunity, name='save'),
path('<int:pk>/view/', OpportunityDetailView.as_view(), name="opp_view"),
path('<int:pk>/edit/', update_opportunity, name="opp_edit"),
path('<int:pk>/delete/', DeleteOpportunityView.as_view(), name="opp_remove"),
path('<int:pk>/delete/',
DeleteOpportunityView.as_view(), name="opp_remove"),
path('contacts/', GetContactView.as_view(), name="contacts"),
path('get/list/', GetOpportunitiesView.as_view(), name="get_opportunity"),
path('comment/add/', AddCommentView.as_view(), name="add_comment"),
path('comment/edit/', UpdateCommentView.as_view(), name="edit_comment"),
path('comment/remove/', DeleteCommentView.as_view(), name="remove_comment"),
path('comment/remove/',
DeleteCommentView.as_view(), name="remove_comment"),
path('attachment/add/', AddAttachmentsView.as_view(), name="add_attachment"),
path('attachment/add/',
AddAttachmentsView.as_view(), name="add_attachment"),
path('attachment/remove/', DeleteCommentView.as_view(),
name="remove_attachment"),
]

View file

@ -5,12 +5,14 @@ from django.core.mail import EmailMessage
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.views.generic import CreateView, UpdateView, DetailView, ListView, TemplateView, View
from django.views.generic import (CreateView, DetailView,
ListView, TemplateView, View)
from accounts.models import Account, Tags
from common.models import User, Comment, Attachments
from common.utils import STAGES, SOURCES, CURRENCY_CODES
from contacts.models import Contact
from opportunity.forms import OpportunityForm, OpportunityCommentForm, OpportunityAttachmentForm
from opportunity.forms import (OpportunityForm, OpportunityCommentForm,
OpportunityAttachmentForm)
from opportunity.models import Opportunity
from django.urls import reverse
from django.db.models import Q
@ -23,10 +25,13 @@ class OpportunityListView(LoginRequiredMixin, TemplateView):
template_name = "opportunity.html"
def get_queryset(self):
queryset = self.model.objects.all().prefetch_related("contacts", "account")
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
queryset = self.model.objects.all().prefetch_related(
"contacts", "account")
if self.request.user.role != "ADMIN" and not \
self.request.user.is_superuser:
queryset = queryset.filter(
Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user.id))
Q(assigned_to__in=[self.request.user]) |
Q(created_by=self.request.user.id))
request_post = self.request.POST
if request_post:
@ -58,7 +63,8 @@ class OpportunityListView(LoginRequiredMixin, TemplateView):
search = False
if (
self.request.POST.get('name') or self.request.POST.get('stage') or
self.request.POST.get('lead_source') or self.request.POST.get('account') or
self.request.POST.get('lead_source') or
self.request.POST.get('account') or
self.request.POST.get('contacts')
):
search = True
@ -72,9 +78,6 @@ class OpportunityListView(LoginRequiredMixin, TemplateView):
def create_opportunity(request):
template_name = "create_opportunity.html"
users = User.objects.filter(is_active=True).order_by('email')
accounts = Account.objects.filter(status="open")
contacts = Contact.objects.all()
if request.user.role != "ADMIN" and not request.user.is_superuser:
@ -83,9 +86,9 @@ def create_opportunity(request):
contacts = Contact.objects.filter(
Q(assigned_to__in=[request.user]) | Q(created_by=request.user))
kwargs_data = {"assigned_to": users, "account": accounts, "contacts": contacts}
form = OpportunityForm(**kwargs_data)
kwargs_data = {
"assigned_to": User.objects.filter(is_active=True).order_by('email'),
"account": accounts, "contacts": contacts}
if request.POST:
form = OpportunityForm(request.POST, request.FILES, **kwargs_data)
if form.is_valid():
@ -102,13 +105,15 @@ def create_opportunity(request):
for assigned_to_user in assigned_to_list:
user = get_object_or_404(User, pk=assigned_to_user)
mail_subject = 'Assigned to opportunity.'
message = render_to_string('assigned_to/opportunity_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'opportunity': opportunity_obj
})
email = EmailMessage(mail_subject, message, to=[user.email])
message = render_to_string(
'assigned_to/opportunity_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'opportunity': opportunity_obj
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
if request.POST.getlist('contacts', []):
@ -138,31 +143,29 @@ def create_opportunity(request):
success_url = reverse("opportunities:save")
if request.POST.get('from_account'):
from_account = request.POST.get('from_account')
success_url = reverse("accounts:view_account", pk=from_account)
success_url = reverse("accounts:view_account", kwargs={
'pk': from_account})
print(success_url)
return JsonResponse({'error': False, 'success_url': success_url})
return JsonResponse({'error': True, 'errors': form.errors})
context = {}
context["opportunity_form"] = OpportunityForm(**kwargs_data)
else:
return JsonResponse({'error': True, 'errors': form.errors})
context["accounts"] = accounts
if request.GET.get('view_account'):
context['account'] = get_object_or_404(
Account, id=request.GET.get('view_account'))
context["contacts"] = contacts
context["users"] = kwargs_data['assigned_to']
context["currencies"] = CURRENCY_CODES
context["stages"] = STAGES
context["sources"] = SOURCES
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
else:
context = {}
context["opportunity_form"] = form
context["accounts"] = accounts
if request.GET.get('view_account'):
context['account'] = get_object_or_404(
Account, id=request.GET.get('view_account'))
context["contacts"] = contacts
context["users"] = users
context["currencies"] = CURRENCY_CODES
context["stages"] = STAGES
context["sources"] = SOURCES
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
context["contacts_list"] = [
int(i) for i in request.POST.getlist('contacts', []) if i]
return render(request, template_name, context)
context["contacts_list"] = [
int(i) for i in request.POST.getlist('contacts', []) if i]
return render(request, "create_opportunity.html", context)
class OpportunityDetailView(LoginRequiredMixin, DetailView):
@ -177,10 +180,13 @@ class OpportunityDetailView(LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
context = super(OpportunityDetailView, self).get_context_data(**kwargs)
user_assgn_list = [assigned_to.id for assigned_to in context['object'].assigned_to.all()]
user_assgn_list = [
assigned_to.id for assigned_to in
context['object'].assigned_to.all()]
if self.request.user == context['object'].created_by:
user_assgn_list.append(self.request.user.id)
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if self.request.user.role != "ADMIN" and not \
self.request.user.is_superuser:
if self.request.user.id not in user_assgn_list:
raise PermissionDenied
assigned_data = []
@ -192,15 +198,15 @@ class OpportunityDetailView(LoginRequiredMixin, DetailView):
comments = context["opportunity_record"].opportunity_comments.all()
context.update({
"comments": comments, 'attachments': context["opportunity_record"].opportunity_attachment.all(),
"comments": comments,
'attachments': context[
"opportunity_record"].opportunity_attachment.all(),
"assigned_data": json.dumps(assigned_data)})
return context
def update_opportunity(request, pk):
template_name = "create_opportunity.html"
opportunity_object = Opportunity.objects.filter(pk=pk).first()
users = User.objects.filter(is_active=True).order_by('email')
accounts = Account.objects.filter(status="open")
contacts = Contact.objects.all()
if request.user.role != "ADMIN" and not request.user.is_superuser:
@ -209,13 +215,18 @@ def update_opportunity(request, pk):
contacts = Contact.objects.filter(
Q(assigned_to__in=[request.user]) | Q(created_by=request.user))
kwargs_data = {"assigned_to": users, "account": accounts, "contacts": contacts}
kwargs_data = {
"assigned_to": User.objects.filter(is_active=True).order_by('email'),
"account": accounts, "contacts": contacts}
form = OpportunityForm(instance=opportunity_object, **kwargs_data)
if request.POST:
form = form = OpportunityForm(request.POST, request.FILES, instance=opportunity_object, **kwargs_data)
form = form = OpportunityForm(
request.POST, request.FILES,
instance=opportunity_object, **kwargs_data)
if form.is_valid():
assigned_to_ids = opportunity_object.assigned_to.all().values_list('id', flat=True)
assigned_to_ids = opportunity_object.assigned_to.all().values_list(
'id', flat=True)
opportunity_obj = form.save(commit=False)
if request.POST.get('stage') in ['CLOSED WON', 'CLOSED LOST']:
opportunity_obj.closed_by = request.user
@ -228,17 +239,19 @@ def update_opportunity(request, pk):
assigned_form_users = form.cleaned_data.get(
'assigned_to').values_list('id', flat=True)
all_members_list = list(
set(list(assigned_form_users)) - set(list(assigned_to_ids)))
if len(all_members_list):
set(list(assigned_form_users)) -
set(list(assigned_to_ids)))
if all_members_list:
for assigned_to_user in all_members_list:
user = get_object_or_404(User, pk=assigned_to_user)
mail_subject = 'Assigned to opportunity.'
message = render_to_string('assigned_to/opportunity_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'opportunity': opportunity_obj
})
message = render_to_string(
'assigned_to/opportunity_assigned.html', {
'user': user,
'domain': current_site.domain,
'protocol': request.scheme,
'opportunity': opportunity_obj
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
@ -276,37 +289,35 @@ def update_opportunity(request, pk):
success_url = reverse('opportunities:list')
if request.POST.get('from_account'):
from_account = request.POST.get('from_account')
success_url = reverse("accounts:view_account", pk=from_account)
success_url = reverse("accounts:view_account", kwargs={
'pk': from_account})
return JsonResponse({'error': False, 'success_url': success_url})
else:
return JsonResponse({'error': True, 'errors': form.errors})
else:
context = {}
context["opportunity_obj"] = opportunity_object
user_assgn_list = [
assigned_to.id for assigned_to in context["opportunity_obj"].assigned_to.all()]
if request.user == context['opportunity_obj'].created_by:
user_assgn_list.append(request.user.id)
if request.user.role != "ADMIN" and not request.user.is_superuser:
if request.user.id not in user_assgn_list:
raise PermissionDenied
context["opportunity_form"] = form
context["accounts"] = accounts
if request.GET.get('view_account'):
context['account'] = get_object_or_404(
Account, id=request.GET.get('view_account'))
context["contacts"] = contacts
context["users"] = users
context["currencies"] = CURRENCY_CODES
context["stages"] = STAGES
context["sources"] = SOURCES
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
context["contacts_list"] = [
int(i) for i in request.POST.getlist('contacts', []) if i]
return render(request, template_name, context)
return JsonResponse({'error': True, 'errors': form.errors})
context = {}
context["opportunity_obj"] = opportunity_object
user_assgn_list = [
assigned_to.id for assigned_to in
context["opportunity_obj"].assigned_to.all()]
if request.user == context['opportunity_obj'].created_by:
user_assgn_list.append(request.user.id)
if request.user.role != "ADMIN" and not request.user.is_superuser:
if request.user.id not in user_assgn_list:
raise PermissionDenied
context["opportunity_form"] = form
context["accounts"] = accounts
if request.GET.get('view_account'):
context['account'] = get_object_or_404(
Account, id=request.GET.get('view_account'))
context["contacts"] = contacts
context["users"] = kwargs_data['assigned_to']
context["currencies"] = CURRENCY_CODES
context["stages"] = STAGES
context["sources"] = SOURCES
context["assignedto_list"] = [
int(i) for i in request.POST.getlist('assigned_to', []) if i]
context["contacts_list"] = [
int(i) for i in request.POST.getlist('contacts', []) if i]
return render(request, "create_opportunity.html", context)
class DeleteOpportunityView(LoginRequiredMixin, View):
@ -316,7 +327,9 @@ class DeleteOpportunityView(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
self.object = get_object_or_404(Opportunity, id=kwargs.get("pk"))
if self.request.user.role == "ADMIN" or self.request.user.is_superuser or self.request.user == self.object.created_by:
if (self.request.user.role == "ADMIN" or
self.request.user.is_superuser or
self.request.user == self.object.created_by):
self.object.delete()
if request.is_ajax():
return JsonResponse({'error': False})
@ -326,8 +339,7 @@ class DeleteOpportunityView(LoginRequiredMixin, View):
return redirect("accounts:view_account", pk=account)
return redirect("opportunities:list")
else:
raise PermissionDenied
raise PermissionDenied
class GetContactView(LoginRequiredMixin, View):
@ -339,7 +351,8 @@ class GetContactView(LoginRequiredMixin, View):
contacts = account.contacts.all()
else:
contacts = Contact.objects.all()
data = {contact.pk: contact.first_name for contact in contacts.distinct()}
data = {contact.pk:
contact.first_name for contact in contacts.distinct()}
return JsonResponse(data)
@ -354,7 +367,8 @@ class AddCommentView(LoginRequiredMixin, CreateView):
Opportunity, id=request.POST.get('opportunityid'))
if (
request.user in self.opportunity.assigned_to.all() or
request.user == self.opportunity.created_by or request.user.is_superuser or
request.user == self.opportunity.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
@ -427,11 +441,6 @@ class GetOpportunitiesView(LoginRequiredMixin, ListView):
context_object_name = "opportunities"
template_name = "opportunities_list.html"
def get_context_data(self, **kwargs):
context = super(GetOpportunitiesView, self).get_context_data(**kwargs)
context["opportunities"] = self.get_queryset()
return context
class AddAttachmentsView(LoginRequiredMixin, CreateView):
model = Attachments
@ -444,7 +453,8 @@ class AddAttachmentsView(LoginRequiredMixin, CreateView):
Opportunity, id=request.POST.get('opportunityid'))
if (
request.user in self.opportunity.assigned_to.all() or
request.user == self.opportunity.created_by or request.user.is_superuser or
request.user == self.opportunity.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
@ -467,7 +477,8 @@ class AddAttachmentsView(LoginRequiredMixin, CreateView):
"attachment_url": attachment.attachment.url,
"created_on": attachment.created_on,
"created_by": attachment.created_by.email,
"download_url": reverse('common:download_attachment', kwargs={'pk': attachment.id}),
"download_url": reverse('common:download_attachment',
kwargs={'pk': attachment.id}),
"attachment_display": attachment.get_file_type_display()
})
@ -480,10 +491,13 @@ class DeleteAttachmentsView(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
self.object = get_object_or_404(
Attachments, id=request.POST.get("attachment_id"))
if (request.user == self.object.created_by or request.user.is_superuser or request.user.role == 'ADMIN'):
if (request.user == self.object.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'):
self.object.delete()
data = {"aid": request.POST.get("attachment_id")}
return JsonResponse(data)
data = {'error': "You don't have permission to delete this attachment."}
data = {
'error': "You don't have permission to delete this attachment."}
return JsonResponse(data)

View file

@ -57,8 +57,10 @@ class ReminderForm(forms.ModelForm):
model = Reminder
fields = '__all__'
reminder_type = forms.ChoiceField(choices=(('Email', 'Email'), ('Popup', 'Popup')),
widget=forms.Select(attrs={'class': 'form-control input-sm'}))
reminder_type = forms.ChoiceField(choices=(
('Email', 'Email'), ('Popup', 'Popup')),
widget=forms.Select(attrs={
'class': 'form-control input-sm'}))
reminder_time = forms.ChoiceField(choices=(("0", "on time"),
("60", "1m before"),
("120", "2m before"),
@ -71,7 +73,9 @@ class ReminderForm(forms.ModelForm):
("10800", "3h before"),
("18000", "5h before"),
("86400", "1d before")
), widget=forms.Select(attrs={'class': 'form-control input-sm'}))
),
widget=forms.Select(attrs={
'class': 'form-control input-sm'}))
# class MeetingEventForm(EventForm):

View file

@ -13,8 +13,9 @@ from common.utils import EVENT_PARENT_TYPE, EVENT_STATUS
class Reminder(models.Model):
reminder_type = models.CharField(max_length=5, blank=True,
null=True)
reminder_time = models.IntegerField(pgettext_lazy("time of the reminder to event in Seconds", "Reminder"),
blank=True, null=True)
reminder_time = models.IntegerField(pgettext_lazy(
"time of the reminder to event in Seconds", "Reminder"),
blank=True, null=True)
def __str__(self):
return self.reminder_type
@ -28,9 +29,13 @@ class Event(models.Model):
name = models.CharField(
pgettext_lazy("Name of the Event", "Event"),
max_length=64)
event_type = models.CharField(_("Type of the event"), max_length=7) # call meeting task
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True,
limit_choices_to=limit, choices=EVENT_PARENT_TYPE)
event_type = models.CharField(
_("Type of the event"), max_length=7) # call meeting task
content_type = models.ForeignKey(ContentType,
on_delete=models.CASCADE,
blank=True, null=True,
limit_choices_to=limit,
choices=EVENT_PARENT_TYPE)
object_id = models.PositiveIntegerField(blank=True, null=True)
parent = GenericForeignKey('content_type', 'object_id')
status = models.CharField(
@ -42,22 +47,28 @@ class Event(models.Model):
start_date = models.DateField(default=None)
close_date = models.DateField(default=None, null=True)
duration = models.IntegerField(pgettext_lazy("Duration of the Event in Seconds", "Durations"), default=None,
null=True) # not for tasks
duration = models.IntegerField(pgettext_lazy(
"Duration of the Event in Seconds", "Durations"), default=None,
null=True) # not for tasks
reminders = models.ManyToManyField(Reminder, blank=True)
priority = models.CharField(max_length=10, blank=True) # only for task
updated_on = models.DateTimeField(auto_now=True)
updated_by = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, blank=True, related_name='updated_user')
User, on_delete=models.SET_NULL, null=True,
blank=True, related_name='updated_user')
attendees_user = models.ManyToManyField(User, blank=True,
related_name='attendees_user')
attendees_contacts = models.ManyToManyField(Contact, blank=True,
related_name='attendees_contact')
related_name='attendees_contact'
)
attendees_leads = models.ManyToManyField(Lead, blank=True,
related_name='attendees_lead')
created_on = models.DateTimeField(_("Created on"), auto_now_add=True)
created_by = models.ForeignKey(User, related_name='event_created_by', on_delete=models.SET_NULL, null=True)
assigned_to = models.ManyToManyField(User, blank=True, related_name='event_assigned_users')
created_by = models.ForeignKey(
User, related_name='event_created_by',
on_delete=models.SET_NULL, null=True)
assigned_to = models.ManyToManyField(
User, blank=True, related_name='event_assigned_users')
description = models.TextField(blank=True, null=True)
is_active = models.BooleanField(default=False)

View file

@ -1,8 +1,8 @@
import datetime
from django.utils import timezone
from django.test import TestCase
from planner.models import Event
from django.contrib.auth.models import User
# import datetime
# from django.utils import timezone
# from django.test import TestCase
# from planner.models import Event
# from django.contrib.auth.models import User
# class EventObjects(object):

View file

@ -21,4 +21,6 @@ billiard==3.5.0.5
celery==4.2.1
kombu==4.3.0
vine==1.2.0
openpyxl==2.6.0
lxml==4.3.2
cssselect==1.0.3

View file

@ -1,16 +1,16 @@
@import "ref.scss";
*{
font-family: 'Roboto', sans-serif;
font-family: 'Quicksand', sans-serif;
}
body {
font-family: 'Roboto', sans-serif;
font-family: 'Quicksand', sans-serif;
padding: 0;
font-weight: 500;
margin: 0;
padding-top:60px;
font-size: 15px;
font-size: 14px;
background: #f7f7f9;
@media(min-width:$large){
font-size:18px;
font-size:17px;
}
@media(min-width: $mob_min) and (max-width: $smob_max){
padding-top: 50px;
@ -45,9 +45,9 @@ body {
font-weight: 600;
color: #333;
span {
color: $color_cyan_aqua_approx;
color: $sky_color;
&.create_head {
color: $color_screamin_green_approx;
color: $sky_color;
}
}
}
@ -128,21 +128,21 @@ a{
/* inner_pages starts here */
.main_body_bg_color{
/*.main_body_bg_color{
background: #fff;
}
}*/
.primary_btn{
background: $login_btn_bg;
color: #fff;
border:none;
font-size: 0.85rem;
font-weight: 600;
font-size: 0.9rem;
font-weight: 500;
text-transform: capitalize;
letter-spacing: initial;
display: inline-block;
border-radius: 3px;
padding: 5px 10px;
padding: 2px 10px;
line-height: 1.5rem;
bordeR: none;
cursor: pointer;
@ -162,11 +162,11 @@ a{
margin-right: 5px;
}
}
.seconadry_btn{
.secondary_btn{
@extend .primary_btn;
background: $dark_sky;
background: $blue;
&:hover{
background: darken($dark_sky, 10%);
background: darken($blue, 10%);
}
}
.tertiary_btn{
@ -487,6 +487,9 @@ $color_2: #000;
.table_container_row{
margin-top: 10px;
.card{
border: none;
border-radius: 0;
box-shadow: none;
.card-title{
padding:5px 10px !important;
background: #e5e5e5;
@ -523,20 +526,21 @@ $color_2: #000;
margin-top:0px;
}
}
}
.card-body{
.card-body{
padding:0px 0px;
table{
border: none;
margin-bottom: 0;
thead{
tr{
th{
color: #5b646d;
font-weight: 600;
/* text-transform: uppercase; */
color: #454545;
font-weight: 500;
font-size: 0.9rem;
border: none;
text-align: left;
padding:6px;
border-bottom: 1px solid darken(#f7f7f9, 2%);
}
}
}
@ -549,6 +553,7 @@ $color_2: #000;
text-align: left;
font-weight: 500;
padding:7px 5px;
border-bottom: 1px solid darken(#f7f7f9, 2%);
@media(min-width: $large){
font-size:1rem;
}
@ -570,25 +575,12 @@ $color_2: #000;
}
}
&.actions{
.btn{
border-radius: 2px;
font-size: 0.85rem;
padding:3px 5px;
font-weight: 600;
color: #fff;
margin-right:2px;
svg{
display: inline-block;
font-size: 0.85rem;
margin-right: 0px;
}
&:hover{
text-decoration: none;
}
}
.action{
svg{
margin-right: 4px;
a{
&.box{
@extend .box;
line-height: 1.5rem;
width: 1.65rem;
height: 1.65rem;
}
}
}
@ -597,6 +589,8 @@ $color_2: #000;
}
}
}
}
/* mail_menu starts here */
.mail_menu{
.panel{
@ -992,14 +986,12 @@ padding:0px !important;
font-size: 0.83rem;
}
pre{
font-family: 'Muli', sans-serif;
margin-bottom: 0.5rem;
a{
}
}
#comments_div{
font-family: 'Muli', sans-serif;
.list-group-item{
margin-bottom: 0.75rem;
border: none;
@ -1137,71 +1129,129 @@ pre{
}
/* marketing_pages */
.side_menu{
background: #404f88;
position: fixed;
top: 50px;
left: 0;
width: 250px;
height: 100%;
border: none;
.list-group-item{
background: #fff;
position: fixed;
left: 0;
width: 250px;
top: 0;
height: 100%;
border: none;
.logo_block{
a{
display: block;
border: none;
background: none;
padding:0.75rem 1rem;
color: #000;
font-size: 1.35rem;
font-weight: 800;
.green_color{
color: $green;
}
}
}
.profile_block{
margin-top: 0.5rem;
ul{
margin: 0;
padding: 0;
margin-bottom: 1rem;
.item{
display: block;
width: 100%;
h6{
&.heading{
font-size: 0.75rem;
letter-spacing: 0.02rem;
text-transform: uppercase;
color: #d5ddf9;
background: #526199;
font-weight: 600;
border: none;
line-height: 2rem;
padding-left: 1rem;
svg{
display: inline-block;
margin-right: 5px;
padding-left: 1rem;
li{
list-style: none;
display: inline-block;
margin-right: 3px;
a{
&.profile_pic{
img{
width: 2rem;
height: 2rem;
border-radius: 3px;
}
}
&.box{
@extend .box;
}
}
ul{
}
}
}
.menu_block{
.main_menu{
margin-top: 1.5rem;
padding: 0;
padding-left: 1rem;
li{
list-style: none;
a{
font-size: 0.83rem;
font-size: 400;
transition: 0.6s all ease-out;
svg{
display: inline-block;
margin-right: 0.5rem;
font-size: 1.1rem;
}
}
&.parent_menu{
margin: 0;
padding: 0;
li{
list-style: none;
a{
font-size: 0.85rem;
color: #fff;
font-weight: 600;
line-height: 1.6rem;
display: block;
padding: 0.2rem 0;
padding-left: 0.7rem;
&:hover{
background: #ed7354;
}
&.active{
background: #ed7354;
}
span{
background: #00a1ff;
border-radius: 3px;
display: inline-block;
font-size: 0.8rem;
padding: 0.1rem 0.4rem;
line-height: 1.3rem;
margin-right: 0.5rem;
}
svg{
display: inline-block;
margin-right: 0.4rem;
margin-bottom: 0.5rem;
list-style: none;
a{
display: inline-block;
color: $text_color;
padding-top: 0.35rem;
padding-bottom: 0.35rem;
transition: 0.6s all ease-out;
&.red{
color: $red;
}
&.green{
color: $green;
}
&.purple{
color: $purple;
}
}
.submenu{
padding-left: 1.6rem;
margin-top: 0.3rem;
position: relative;
&::before{
content: "";
height: 100%;
background: lighten($text_color, 40%);
width: 1px;
position: absolute;
left: 0.5rem;
}
li{
a{
transition: 0.6s all ease-out;
&.yellow{
svg{
color: $dark_yellow;
}
&:hover{
color: $dark_yellow;
}
}
&.d_green{
svg{
color: $d_green;
}
&:hover{
color: $d_green ;
}
}
&.sky_color{
svg{
color: $sky_color;
}
&:hover{
color: $sky_color;
}
}
}
}
}
@ -1209,13 +1259,21 @@ pre{
}
}
}
}
/* side_menu */
.marketing_pages{
margin-left: 250px;
margin-bottom: 1rem;
margin-top: 2rem;
}
.badge{
font-size: 85%;
width: auto !important;
height: auto !important;
font-size: 100%;
border-radius: 10rem;
display: inline-block;
padding: .05rem 1rem;
}
.a_color{
color: $text_color !important;
@ -1498,3 +1556,31 @@ pre{
font-size: 12px;
color: #a7a7a7a7;
}
.actions {
.edit, .delete {
svg {
path {
fill: white;
}
}
}
}
.btn-danger{
background-color: #ffc107;
border-color: #ffc107;
color: #fff!important;
}
.btn-success{
color: #fff!important;
background-color: #099cbe;
border-color: #099cbe;
}
.btn-info{
color: #fff!important;
background: #28a745;
border-color: #35a745;
}

67
static/css/mention.css Normal file
View file

@ -0,0 +1,67 @@
.mention-wrapper {
position: relative;
display: block;
width: 100%;
height: 100%; }
.mention-wrapper textarea {
color: rgba(0, 0, 0, 0.1); }
.mention-display {
color: rgba(0, 0, 0, 0.9);
position: absolute;
display: inline-block;
box-sizing: border-box;
pointer-events: none;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
border-radius: 4px;
word-break: break-word;
-moz-appearance: textfield-multiline;
-webkit-appearance: textarea;
font: medium -moz-fixed;
font: -webkit-small-control;
border: 3px solid transparent;
padding: 10px;
font-size: 16px; }
.mention-options {
position: absolute;
pointer-events: none;
opacity: 0;
color: rgba(0, 0, 0, 0.9);
width: 100%;
border: 3px solid transparent;
box-sizing: border-box; }
.mention-options.show {
pointer-events: initial;
opacity: 1;
z-index: 9999;
border: 2px solid #ddd;
background: #ddd; }
.mention-options .mention-option {
padding: 5px 10px;
background: #FFF;
position: relative;
cursor: pointer;
display: none; }
.mention-options .mention-option:hover:after, .mention-options .mention-option.active:after {
content: '';
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.1);
pointer-events: none;
cursor: pointer; }
.mention-options .mention-option.show {
display: block; }
/*# sourceMappingURL=mention.css.map */

View file

@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "AAAA,gBAAiB;EACf,QAAQ,EAAE,QAAQ;EAClB,OAAO,EAAE,KAAK;EACd,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;;AAGd,yBAA0B;EAAE,KAAK,EAAE,kBAAkB;;AAErD,gBAAiB;EACf,KAAK,EAAE,kBAAkB;EACzB,QAAQ,EAAE,QAAQ;EAClB,OAAO,EAAE,YAAY;EACrB,UAAU,EAAE,UAAU;EACtB,cAAc,EAAE,IAAI;EACpB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EACZ,GAAG,EAAE,GAAG;EACR,IAAI,EAAE,GAAG;EACT,aAAa,EAAE,GAAG;EAClB,UAAU,EAAE,UAAU;EACtB,eAAe,EAAE,mBAAmB;EACpC,kBAAkB,EAAE,QAAQ;EAC5B,IAAI,EAAE,iBAAiB;EACvB,IAAI,EAAE,qBAAqB;EAC3B,MAAM,EAAE,qBAAqB;EAC7B,OAAO,EAAE,IAAI;EACb,SAAS,EAAE,IAAI;;AAGjB,gBAAiB;EACf,QAAQ,EAAE,QAAQ;EAClB,cAAc,EAAE,IAAI;EACpB,OAAO,EAAE,CAAC;EACV,KAAK,EAAE,kBAAkB;EACzB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,qBAAqB;EAC7B,UAAU,EAAE,UAAU;;AAGxB,qBAAsB;EACpB,cAAc,EAAE,OAAO;EACvB,OAAO,EAAE,CAAC;EACV,OAAO,EAAE,IAAI;EACb,MAAM,EAAE,cAAc;EACtB,UAAU,EAAE,IAAI;;AAIlB,gCAAiC;EAC/B,OAAO,EAAE,QAAQ;EACjB,UAAU,EAAE,IAAI;EAChB,QAAQ,EAAE,QAAQ;EAClB,MAAM,EAAE,OAAO;EACf,OAAO,EAAE,IAAI;;AAGf,2FAA4F;EAC1F,OAAO,EAAE,EAAE;EACX,QAAQ,EAAE,QAAQ;EAClB,GAAG,EAAE,GAAG;EACR,IAAI,EAAE,GAAG;EACT,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EACZ,UAAU,EAAE,kBAAkB;EAC9B,cAAc,EAAE,IAAI;EACpB,MAAM,EAAE,OAAO;;AAGjB,qCAAsC;EAAE,OAAO,EAAE,KAAK",
"sources": ["mention.scss"],
"names": [],
"file": "mention.css"
}

View file

@ -12,11 +12,17 @@ $color_blue_dianne_approx: #273e48;
$color_cerise_red_approx: #e92064;
$login_btn_bg:#dc3545;
$info_color:#138496;
$green:#41B871;
$dark_sky:#099CBE;
$dark_yellow:#FEA124;
$inner_page_bg:#CDCDCD;
$green: #5cca64;
$green: #7ae847;
$d_green: #06ae90;
$red:#fb1e07;
$sky_color:#099CBE;
$green: #7ae847;
$blue: #1095fe;
$purple: #9414c9;
@ -38,6 +44,13 @@ $mob_min:240px;
.card{
border:1px solid #ccc;
}
.heading{
margin-bottom: 1rem;
span{
font-size: 1.35rem;
}
}
.buttons_row{
/* margin-top: 10px;
margin-bottom: 10px;*/
@ -98,3 +111,68 @@ select{
}
}
}
.box{
background: red;
display: inline-block;
width: 2rem;
height: 2rem;
line-height: 1.6rem;
border-radius: 3px;
text-align: center;
padding-top: 0.1rem;
transition: 0.6s all ease-out;
&.sky_blue{
background: lighten($sky_color, 55%);
color: $sky_color;
&:hover{
background: $sky_color;
}
}
&.red{
background: lighten($red, 45%);
color: $red;
&:hover{
background: $red;
}
}
&.green{
background: lighten($d_green, 55%);
color: $d_green;
&:hover{
background: $d_green;
}
}
&.l_green{
background: lighten($green, 55%);
color: $green;
&:hover{
background: $green;
}
}
&.purple{
background: lighten($purple, 50%);
color: $purple;
&:hover{
background: $purple;
}
}
&.yellow{
background: lighten($dark_yellow, 50%);
color: $dark_yellow;
&:hover{
background: $dark_yellow;
}
}
&.dark_grey{
background: lighten(#6c757d, 45%);
color: #6c757d;
&:hover{
background: #6c757d;
}
}
&:hover{
color: #fff;
}
}

View file

@ -0,0 +1,4 @@
company name,email,first name,last name,city,state
micropyramid,admin@micropyramid.com,Admin,MP,Hyderabad,Telangana
micropyramid,hr@micropyramid.com,HR,MP,Hyderabad,Telangana
micropyramid,contactus@micropyramid.com,Contact US,MP,Hyderabad,Telangana
1 company name email first name last name city state
2 micropyramid admin@micropyramid.com Admin MP Hyderabad Telangana
3 micropyramid hr@micropyramid.com HR MP Hyderabad Telangana
4 micropyramid contactus@micropyramid.com Contact US MP Hyderabad Telangana

BIN
static/images/busy.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -9,9 +9,10 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Django CRM</title>
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css" />
{% compress css %}
<link href="{% static 'css/main.scss' %}" type="text/x-scss" rel="stylesheet">
<link href="{% static 'css/mention.scss' %}" type="text/x-scss" rel="stylesheet">
@ -72,7 +73,7 @@
</header>
<!-- header ends here -->
<!-- main_container starts here -->
<div id="mainbody" class="main_container">
<div id="mainbody" class="main_container" style="padding-top:65px;">
<!-- breadcrumb starts here -->
{% block breadcrumb %}
{% endblock %}

View file

@ -4,26 +4,64 @@
<ul class="navbar-nav mr-auto">
</ul>
{% endblock %}
{% block header %}
{% endblock header %}
{% block content %}
<ul class="list-group side_menu mb-3">
<!-- <li class="list-group-item d-flex justify-content-between">
<div class="side_menu">
<div class="logo_block">
<a href="#">Django <span class="green_color text-uppercase">Crm</span></a>
</div>
<div class="profile_block">
<ul>
<li data-toggle="tooltip" data-placement="bottom" title="Santharao Nowpada"><a href="#" class="profile_pic"><img src="https://lh3.googleusercontent.com/-6gBVCn2WaAk/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rd7EWo94Fj0ep0DQa3tDjddczFC3g/s96-c-mo/photo.jpg" alt="profile picture"></a></li>
<li data-toggle="tooltip" data-placement="bottom" title="Users"><a href="#" class="sky_blue box"><i class="fas fa-users"></i></a></li>
<li data-toggle="tooltip" data-placement="bottom" title="Change Password"><a href="#" class="red box"><i class="fas fa-key"></i></a></li>
<li data-toggle="tooltip" data-placement="bottom" title="Profile"><a href="#" class="green box"><i class="fas fa-user"></i></a></li>
<li data-toggle="tooltip" data-placement="bottom" title="Logout"><a href="#" class="purple box"><i class="fas fa-sign-out-alt"></i></a></li>
</ul>
</div>
<div class="menu_block">
<ul class="main_menu">
<li class="parent_menu">
<a href="{% url 'marketing:contact_lists' %}" class="red"><i class="far fa-address-book"></i>Contacts</a>
<ul class="submenu">
<li><a href="{% url 'marketing:contacts_list' %}" class="yellow"><i class="fas fa-bars"></i>All Contacts</a></li>
<li><a href="{% url 'marketing:contacts_list_new' %}" class="yellow"><i class="far fa-plus-square"></i>Create Contact</a></li>
</ul>
</li>
<li class="parent_menu">
<a href="{% url 'marketing:email_template_list' %}" class="purple"><i class="fas fa-envelope-open"></i>Email Templates</a>
<ul class="submenu">
<li><a href="{% url 'marketing:email_template_list' %}" class="d_green"><i class="fas fa-bars"></i>All Templates</a></li>
<li><a href="{% url 'marketing:email_template_new' %}" class="d_green"><i class="far fa-plus-square"></i>Create Template</a></li>
</ul>
</li>
<li class="parent_menu">
<a href="{% url 'marketing:campaign_list' %}" class="green"><i class="fas fa-at"></i>Campaigns</a>
<ul class="submenu">
<li><a href="{% url 'marketing:campaign_list' %}" class="sky_color"><i class="fas fa-bars"></i>All Campaigns</a></li>
<li><a href="{% url 'marketing:campaign_new' %}" class="sky_color"><i class="far fa-plus-square"></i>Create Campaign</a></li>
</ul>
</li>
</ul>
</div>
</div>
<!-- <ul class="list-group side_menu mb-3">
<li class="logo list-group-item">
<div class="item">
<h6 class="heading"><i class="fas fa-users"></i>Groups</h6>
<ul>
<li><a href="/m/cl/all"><i class="fas fa-bars"></i>List</a></li>
<li><a href="/m/cl/list/new/"><i class="fas fa-plus"></i>Create List</a></li>
</ul>
<a href="#">Django <span class="green_color text-uppercase">Crm</span></a>
</div>
</li> -->
</li>
<li class="list-group-item d-flex justify-content-between">
<div class="item">
<h6 class="heading"><i class="fas fa-address-book"></i> Contacts</h6>
<ul>
<li><a href="/m/cl/all"><i class="fas fa-bars"></i>All</a></li>
<li><a href="/m/cl/lists/" class=""><i class="fas fa-bars"></i> Lists</a></li>
<!-- <li><a href="/m/cl/all/"><i class="fas fa-bars"></i>All Contacts</a></li> -->
<!-- <li><a href="/m/cl/list/new/"> <i class="fas fa-plus"></i>Create New Contact</a></li> -->
<li><a href="/m/cl/list/cnew/"> <i class="fas fa-plus"></i>Create New Contacts</a></li>
</ul>
</div>
@ -46,5 +84,5 @@
</ul>
</div>
</li>
</ul>
</ul> -->
{% endblock %}

View file

@ -60,7 +60,6 @@
</div>
</div>
</div>
<section class="table_det_section row marl">
<div class="col-md-12">
<div class="table_container_row no_mar_top">
@ -188,7 +187,7 @@
</div>
</div>
</div>
</div>
</div>
</section>
<section class="table_det_section row marl">
@ -273,7 +272,7 @@
</div>
</div>
</div>
</div>
</div>
</section>
<section class="table_det_section row marl">
@ -358,7 +357,7 @@
</div>
</div>
</div>
</div>
</div>
</section>
<section class="table_det_section row marl">
@ -443,7 +442,7 @@
</div>
</div>
</div>
</div>
</div>
</section>
<section class="table_det_section row marl">
@ -530,7 +529,7 @@
</div>
</div>
</div>
</div>
</div>
</section>
</div>
@ -538,7 +537,7 @@
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -2,145 +2,170 @@
{% load staticfiles %}
{% load paginate %}
{% block content %}
{{ block.super }}
{{ block.super }}
<div class="marketing_pages">
<div class="row marl">
<div class="col-md-12">
<div class="table_container_row no_mar_top">
<div class="card">
<div class="card-body">
<div class="card-title text-right">
<span class="float-left">Campaigns - 54</span>
<a href="/m/cm/list/new/" class="primary_btn">Add New Campaign</a>
</div>
<div class="table-responsive table-bordered ">
<table class="table">
<thead>
<tr>
<th width="25%">Campaign</th>
<th width="10%">Receipents</th>
<th width="20%">Sent</th>
<th width="15%">Unique Opens</th>
<th width="15%">Unique Clicks</th>
<th width="10%">Actions</th>
</tr>
</thead>
<tbody>
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row marl">
<div class="col-md-12">
<div class="table_container_row no_mar_top">
<div class="card">
<div class="card-body">
<div class="card-title text-right">
<span class="float-left">Campaigns - {{ campaigns_list|length }}</span>
<a href="{% url 'marketing:campaign_new' %}" class="primary_btn">Add New Campaign</a>
</div>
<div class="table-responsive table-bordered ">
<table class="table">
<thead>
<tr>
<th width="25%">Campaign</th>
<th width="10%">Receipents</th>
<th width="20%">Sent</th>
<th width="15%">Unique Opens</th>
<th width="15%">Unique Clicks</th>
<th width="10%">Actions</th>
</tr>
</thead>
<tbody>
{% for campaign in campaigns_list %}
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="{% url 'marketing:campaign_details' campaign.id %}">{{ campaign.title }} </a></td>
<td>5478</td>
<td>
{{ campaign.created_on }}
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="{% url 'marketing:campaign_edit' campaign.id %}" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="{% url 'marketing:campaign_delete' campaign.id %}" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6"> No records found!</td>
</tr>
{% endfor %}
<!--
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><span class="badge badge-primary">Primary</span> <a href="#">Sample scheduled newsletter </a></td>
<td>5478</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td>
<span class="badge badge-secondary">20.6%</span> 125 Opened
</td>
<td>
<span class="badge badge-secondary">15.5%</span> 78 Clicked
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
-->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -2,198 +2,284 @@
{% load staticfiles %}
{% load paginate %}
{% block content %}
{{ block.super }}
{{ block.super }}
<div class="marketing_pages">
<div class="row marl">
<div class="col-md-12">
<form id="add_form" method="POST" action='' 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">
{% if contact_obj %}Edit{% else %}Create{% endif %} Email Template
</div>
<form>
<div class="row marl">
<div class="col-md-3">
<div class="form-group">
<label>Select Template</label>
<select class="form-control">
<option value="one">First template</option>
<option value="two">Second template</option>
<option value="three">Third template</option>
<option value="four">Fourth template</option>
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label>Subject</label>
<input type="email" class="form-control" placeholder="Subject of this email">
<small class="form-text text-muted">Set a title for this campaign?</small>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label>From Name</label>
<input type="email" class="form-control" placeholder="hello@mp.com">
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label>From Email</label>
<input type="email" class="form-control" placeholder="Support@mp.com">
</div>
</div>
<div class="col-md-12">
<div class="row">
<div class="col-md-3">
<div class="form-group">
<label>Reply to email</label>
<input type="email" class="form-control" placeholder="replys@mp.com">
</div>
<div class="form-group">
<label>Query String</label>
<div class="well">
Optionally append a query string to all links in your email newsletter. A good use case is Google Analytics tracking. Don't include '?' in your query string.
</div>
<input type="email" class="form-control" placeholder="url">
</div>
<div class="form-group">
<label>Attachments</label>
<input type="file" class="form-control" >
<small class="form-text text-muted">Allowed file types: jpeg, jpg, gif, png, pdf, zip</small>
</div>
<div class="form-group">
<label>Track Opens</label>
<button type="button" class="btn btn-toggle" data-toggle="button" aria-pressed="false" autocomplete="off">
<div class="handle"></div>
</button>
</div>
<div class="form-group">
<label>Track Clicks</label>
<button type="button" class="btn btn-toggle" data-toggle="button" aria-pressed="false" autocomplete="off">
<div class="handle"></div>
</button>
</div><br>
<div class="form-group">
<button type="submit" class="primary_btn mar_btm">Save Campaign</button>
<button type="submit" class="seconadry_btn">Cancel</button>
</div>
</div>
<div class="col-md-9">
<div class="form-group">
<label>Plain Text</label>
<textarea class="form-control" rows="3"></textarea>
</div>
<div class="form-group">
<label>Html Code</label>
<div id="editor-container">
</div>
<span class="hint">Use the following tags in your subject, plain text or HTML code and they'll automatically be formatted when your campaign is sent. For web version and unsubscribe tags, you can style them with inline CSS.</span>
</div>
<div class="col-md-12 no-padding">
<div class="row help_container">
<div class="col-md-6">
<div class="heading">Essential tags (HTML only)</div>
<p class="desc">The following tags can only be used on the HTML version</p>
<ul>
<li>
<strong>Webversion link: </strong>
<code><webversion>View web version<webversion></code>
</li>
<li>
<strong>Unsubscribe link: </strong>
<code><unsubscribe>Unsubscribe here</unsubscribe></code>
</li>
</ul>
<div class="heading">Essential tags</div>
<p class="desc">The following tags can only be used on the HTML version</p>
<ul>
<li>
<strong>Webversion link: </strong>
<code><webversion>View web version<webversion></code>
</li>
<li>
<strong>Unsubscribe link: </strong>
<code><unsubscribe>Unsubscribe here</unsubscribe></code>
</li>
</ul>
</div>
<div class="col-md-6">
<div class="heading">Personalization tags</div>
<p class="desc">The following tags can be used on both Plain text or HTML version</p>
<ul>
<li>
<strong>Name: </strong>
<code>[Name,fallback=]</code>
</li>
<li>
<strong>Email: </strong>
<code>[Email]</code>
</li>
<li>
<strong>Two digit day of the month (eg. 01 to 31):</strong>
<code>[currentdaynumber]</code>
</li>
</ul>
<div class="heading">Essential tags</div>
<p class="desc">The following tags can only be used on the HTML version</p>
<ul>
<li>
<strong>Name: </strong>
<code>[Name,fallback=]</code>
</li>
<li>
<strong>Email: </strong>
<code>[Email]</code>
</li>
<li>
<strong>Two digit day of the month (eg. 01 to 31):</strong>
<code>[currentdaynumber]</code>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
<div class="row marl">
<div class="col-md-12">
<form id="add_form" method="POST" action='' 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">
Create Campaign
</div>
<form>
<div class="row marl">
<div class="col-md-12">
<div class="form-group">
<label>Title</label>
<input type="text" name="title" class="form-control" placeholder="Title of this campaign">
<small class="form-text text-muted">Set a title for this campaign?</small>
<span id="id_title"></span>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label>Select Template</label>
<select class="form-control" name="email_template">
<option value="">Select a template</option>
{% for email_template in email_templates %}
<option value="{{email_template.id}}">{{ email_template.title }}</option>
{% endfor %}
<!-- <option value="one">First template</option>
<option value="two">Second template</option>
<option value="three">Third template</option>
<option value="four">Fourth template</option> -->
</select>
<span id="id_email_template"></span>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label>Subject</label>
<input type="text" name="subject" class="form-control" placeholder="Subject of this Campaign">
<!-- <small class="form-text text-muted">Set a title for this campaign?</small> -->
<span id="id_subject"></span>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label>From Name</label>
<input type="text" class="form-control" placeholder="John Doe" name="from_name">
<span id="id_from_name"></span>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label>From Email</label>
<input type="email" class="form-control" placeholder="support@mp.com" name="from_email">
<span id="id_from_email"></span>
</div>
</div>
<div class="col-md-12">
<div class="row">
<div class="col-md-3">
<div class="form-group">
<label><input type="checkbox" name="reply_to_crm" value="true"> Reply To CRM</label>
<span id="id_reply_to_crm"></span>
</div>
<div class="form-group">
<label>Reply to email</label>
<input type="email" class="form-control" placeholder="replyto@mp.com" name="reply_to_email">
<span id="id_reply_to_email"></span>
</div>
<div class="form-group">
<label>Query String</label>
<div class="well">
Optionally append a query string to all links in your email newsletter. A good use case is Google Analytics tracking. Don't include '?' in your query string.
</div>
<input type="email" class="form-control" placeholder="url">
</div>
<div class="form-group">
<label>Attachments</label>
<input type="file" class="form-control" name="attachment">
<small class="form-text text-muted">Allowed file types: jpeg, jpg, gif, png, pdf, zip</small>
<span id="id_attachment"></span>
</div>
<div class="form-group">
<label>Track Opens</label>
<button type="button" class="btn btn-toggle" data-toggle="button" aria-pressed="false" autocomplete="off">
<div class="handle"></div>
</button>
</div>
<div class="form-group">
<label>Track Clicks</label>
<button type="button" class="btn btn-toggle" data-toggle="button" aria-pressed="false" autocomplete="off">
<div class="handle"></div>
</button>
</div><br>
<div class="form-group">
<button type="submit" class="primary_btn mar_btm save_btn">Save Campaign</button>
<a href="{% url 'marketing:campaign_list' %}" class="seconadry_btn">Cancel</a>
</div>
</div>
<div class="col-md-9">
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label><input type="checkbox" name="schedule_later" value="true"> Schedule Later</label>
<span id="id_schedule_later"></span>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Timezone</label>
<select name="timezone" id="timezone" class="form-control">
<option value="">Select a Timezone</option>
{% for timezone in timezones %}
<option value="{{timezone.0}}">{{timezone.1}}</option>
{% endfor %}
</select>
<span id="id_timezone"></span>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="">Schedule Date Time</label>
<input type="datetime" class="form-control" name="schedule_date_time" value="" placeholder="Schedule Date Time">
<span id="id_schedule_date_time"></span>
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label>Plain Text</label>
<textarea class="form-control" rows="3" name="plain_html"></textarea>
<span id="id_plain_html"></span>
</div>
<div class="form-group">
<label>Html Code</label>
<div id="editor-container">
</div>
<span class="hint">Use the following tags in your subject, plain text or HTML code and they'll automatically be formatted when your campaign is sent. For web version and unsubscribe tags, you can style them with inline CSS.</span>
<input type="hidden" name="html" id="html_content">
<span id="id_html"></span>
</div>
<div class="col-md-12 no-padding">
<div class="row help_container">
<div class="col-md-6">
<div class="heading">Essential tags (HTML only)</div>
<p class="desc">The following tags can only be used on the HTML version</p>
<ul>
<li>
<strong>Webversion link: </strong>
<code>
<webversion>View web version<webversion>
</code>
</li>
<li>
<strong>Unsubscribe link: </strong>
<code>
<unsubscribe>Unsubscribe here</unsubscribe>
</code>
</li>
</ul>
<div class="heading">Essential tags</div>
<p class="desc">The following tags can only be used on the HTML version</p>
<ul>
<li>
<strong>Webversion link: </strong>
<code>
<webversion>View web version<webversion>
</code>
</li>
<li>
<strong>Unsubscribe link: </strong>
<code>
<unsubscribe>Unsubscribe here</unsubscribe>
</code>
</li>
</ul>
</div>
<div class="col-md-6">
<div class="heading">Personalization tags</div>
<p class="desc">The following tags can be used on both Plain text or HTML version</p>
<ul>
<li>
<strong>Name: </strong>
<code>[Name,fallback=]</code>
</li>
<li>
<strong>Email: </strong>
<code>[Email]</code>
</li>
<li>
<strong>Two digit day of the month (eg. 01 to 31):</strong>
<code>[currentdaynumber]</code>
</li>
</ul>
<div class="heading">Essential tags</div>
<p class="desc">The following tags can only be used on the HTML version</p>
<ul>
<li>
<strong>Name: </strong>
<code>[Name,fallback=]</code>
</li>
<li>
<strong>Email: </strong>
<code>[Email]</code>
</li>
<li>
<strong>Two digit day of the month (eg. 01 to 31):</strong>
<code>[currentdaynumber]</code>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block js_block %}
<script>
var quill = new Quill('#editor-container', {
modules: {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline'],
['image', 'code-block']
]
},
placeholder: 'Compose an epic...',
theme: 'snow' // or 'bubble'
});
$('form').submit(function(){
alert($(this["options"]).val());
return false;
});
</script>
<script src="https://malsup.github.com/jquery.form.js"></script>
<script>
var quill = new Quill('#editor-container', {
modules: {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline'],
['image', 'code-block']
]
},
placeholder: 'Compose an epic...',
theme: 'snow' // or 'bubble'
});
quill.on('text-change', function() {
var justHtml = quill.root.innerHTML;
$('#html_content').val(justHtml);
});
$('.save_btn').click(function(e){
e.preventDefault();
$("form#add_form").submit()
})
$('form#add_form').ajaxForm({
type: 'POST',
dataType: 'json',
url: $(this).attr('action'),
data: $(this).serialize(),
success: function(data) {
console.log("data", data)
if (data.error == false) {
window.location = "{% url 'marketing:campaign_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 %}

View file

@ -1,4 +1,4 @@
w{% extends 'marketing/base.html' %}
{% extends 'marketing/base.html' %}
{% load staticfiles %}
{% load paginate %}
{% block content %}
@ -7,7 +7,7 @@ w{% extends 'marketing/base.html' %}
<div class="row marl">
<div class="col-md-12">
Dashboard page will come soon
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,20 @@
{% extends 'marketing/base.html' %}
{% load staticfiles %}
{% load paginate %}
{% block content %}
{{ block.super }}
<div class="marketing_pages">
<div class="row marl">
<div class="col-md-12">
<p>{{email_template.title}}</p>
<p>{{email_template.subject}}</p>
<span>{{email_template.html|safe}}</span>
</div>
</div>
</div>
{% endblock %}
{% block js_block %}
<script>
</script>
{% endblock js_block %}

View file

@ -2,7 +2,7 @@
{% load staticfiles %}
{% load paginate %}
{% block content %}
{{ block.super }}
{{ block.super }}
<div class="marketing_pages">
<div class="row marl">
<div class="col-md-12">
@ -10,8 +10,8 @@
<div class="card">
<div class="card-body">
<div class="card-title text-right">
<span class="float-left">Email Templates - 15</span>
<a href="/m/et/new/" class="primary_btn">Add New Email Template</a>
<span class="float-left">Email Templates - {{email_templates|length}}</span>
<a href="{% url 'marketing:email_template_new' %}" class="primary_btn">Add New Email Template</a>
</div>
<div class="table-responsive table-bordered ">
<table class="table">
@ -24,85 +24,92 @@
</tr>
</thead>
<tbody>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i> </a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr><tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
{% for email_template in email_templates %}
<tr style="text-align:center;">
<td scope="row"><a href="{% url 'marketing:email_template_detail' email_template.id %}">{{ email_template.title }}</a></td>
<td class="text-center"><a href="#" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="{% url 'marketing:email_template_detail' email_template.id %}" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="{% url 'marketing:email_template_edit' email_template.id %}" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="{% url 'marketing:email_template_delete' email_template.id %}" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4">No Records Found!</td>
</tr>
{% endfor %}
<!--
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i> </a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr><tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row"><a href="#">Sample scheduled Email template </a></td>
<td class="text-center"><a href="/mt/et/list/detail/" class="a_color" data-toggle="tooltip" data-placement="bottom" title="Create a new campaign with this template"><i class="fas fa-calendar-plus"></i></a></td>
<td><a href="/mt/et/list/detail/" class="a_color"><i class="fas fa-eye"></i> View Template</a></td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
-->
</tbody>
</table>
@ -110,7 +117,7 @@
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
@ -119,6 +126,6 @@
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
});
</script>
{% endblock js_block %}

View file

@ -1,48 +1,64 @@
{% extends 'marketing/base.html' %}
{% load staticfiles %}
{% load paginate %}
{% block content %}
{{ block.super }}
<div class="marketing_pages">
<div class="row marl">
<div class="col-md-12">
<form id="add_form" method="POST" action='' 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">
{% if contact_obj %}Edit{% else %}Create{% endif %} Email Template
</div>
<form>
<div class="row marl">
<div class="col-md-12">
<div class="form-group">
<input type="email" class="form-control" placeholder="Enter Template Name">
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label>Html Code</label>
<div id="editor-container">
</div>
</div>
</div>
<div class="col-md-12 text-center">
<button type="submit" class="primary_btn mar_btm">Save Template</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</form>
</div>
{{ block.super }}
<div class="marketing_pages">
<div class="row marl">
<div class="col-md-12">
<form id="add_form" method="POST" action='' 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">
{% if email_template %}Edit{% else %}Create{% endif %} Email Template
</div>
<form>
<div class="row marl">
<div class="col-md-12">
<div class="form-group">
<label for="">Title</label>
<input type="text" class="form-control" name="title" value="{% if email_template %}{{ email_template.title }}{% endif %}" placeholder="Enter Template Title">
</div>
<span id="id_title"></span>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="">Subject</label>
<input type="text" class="form-control" name="subject" value="{% if email_template %}{{ email_template.subject }}{% endif %}" placeholder="Enter Template Subject">
</div>
<span id="id_title"></span>
</div>
<div class="col-md-12">
<div class="form-group">
<label>HTML Code</label>
<div id="editor-container">{% if email_template %}{{ email_template.html|safe }}{% endif %}</div>
</div>
<input type="hidden" name="html" id="html_content">
<span id="id_html"></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 text-center">
<button type="submit" class="primary_btn mar_btm save_btn">Save Template</button>
<a href="{% url 'marketing:email_template_list' %}" class="primary_btn">Cancel</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="https://malsup.github.com/jquery.form.js"></script>
<script>
var quill = new Quill('#editor-container', {
modules: {
@ -53,7 +69,50 @@
]
},
placeholder: 'Compose an epic...',
theme: 'snow' // or 'bubble'
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);
});
$('.save_btn').click(function(e){
e.preventDefault();
$("form#add_form").submit()
})
$('form#add_form').ajaxForm({
type: 'POST',
dataType: 'json',
url: $(this).attr('action'),
data: $(this).serialize(),
success: function(data) {
console.log("data", data)
if (data.error == false) {
window.location = "{% url 'marketing:email_template_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 %}
{% endblock js_block %}

View file

@ -5,15 +5,17 @@
{{ block.super }}
<div class="marketing_pages">
<div class="row marl">
<div class="col-md-12">
<div class="heading text-right">
<span class="float-left">All Contacts - 22</span>
<a href="/m/cl/list/cnew/" class="secondary_btn">Add New Contact List</a>
</div>
</div>
<div class="col-md-12">
<div class="table_container_row no_mar_top">
<div class="card">
<div class="card-body">
<div class="card-title text-right">
<span class="float-left">Contacts List - 61</span>
<a href="/m/cl/list/cnew/" class="primary_btn">Add New List</a>
</div>
<div class="table-responsive table-bordered ">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
@ -27,105 +29,174 @@
</thead>
<tbody>
<tr style="text-align:center;">
<td scope="row">2</td>
<td scope="row">1</td>
<td>Micropyramid</td>
<td class="text-center">23</td>
<td class="text-center">
<span class="badge badge-pill badge-secondary">15%</span>
<span class="badge box dark_grey">15%</span>
</td>
<td class="text-center">
<span class="badge badge-pill badge-info">20%</span>
<span class="badge box sky_blue">20%</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" data-toggle="tooltip" data-placement="bottom" title="" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/edit/" class="btn btn-dark import"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
<a href="/contacts/1/edit/" data-toggle="tooltip" data-placement="bottom" title="" class="green box"><i class="far fa-edit"></i></a>
<a href="/contacts/1/edit/" class="sky_blue box"><i class="far fa-copy"></i></a>
<a href="/contacts/1/edit/" class="purple box"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="red box remove_account"><i class="far fa-trash-alt"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">2</td>
<td>Micropyramid</td>
<td>Peeljobs</td>
<td class="text-center">23</td>
<td class="text-center">
<span class="badge badge-pill badge-secondary">15%</span>
<span class="badge box dark_grey">70%</span>
</td>
<td class="text-center">
<span class="badge badge-pill badge-info">20%</span>
<span class="badge box sky_blue">20%</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/edit/" class="btn btn-dark import"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
<a href="/contacts/1/edit/" data-toggle="tooltip" data-placement="bottom" title="" class="green box"><i class="far fa-edit"></i></a>
<a href="/contacts/1/edit/" class="sky_blue box"><i class="far fa-copy"></i></a>
<a href="/contacts/1/edit/" class="purple box"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="red box remove_account"><i class="far fa-trash-alt"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">2</td>
<td>Micropyramid</td>
<td scope="row">3</td>
<td>Study Signal</td>
<td class="text-center">23</td>
<td class="text-center">
<span class="badge badge-pill badge-secondary">15%</span>
<span class="badge box dark_grey">70%</span>
</td>
<td class="text-center">
<span class="badge badge-pill badge-info">20%</span>
<span class="badge box sky_blue">20%</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/edit/" class="btn btn-dark import"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
<a href="/contacts/1/edit/" data-toggle="tooltip" data-placement="bottom" title="" class="green box"><i class="far fa-edit"></i></a>
<a href="/contacts/1/edit/" class="sky_blue box"><i class="far fa-copy"></i></a>
<a href="/contacts/1/edit/" class="purple box"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="red box remove_account"><i class="far fa-trash-alt"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">2</td>
<td>Micropyramid</td>
<td scope="row">4</td>
<td>Django Crm</td>
<td class="text-center">23</td>
<td class="text-center">
<span class="badge badge-pill badge-secondary">15%</span>
<span class="badge box dark_grey">70%</span>
</td>
<td class="text-center">
<span class="badge badge-pill badge-info">20%</span>
<span class="badge box sky_blue">20%</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/edit/" class="btn btn-dark import"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
<a href="/contacts/1/edit/" data-toggle="tooltip" data-placement="bottom" title="" class="green box"><i class="far fa-edit"></i></a>
<a href="/contacts/1/edit/" class="sky_blue box"><i class="far fa-copy"></i></a>
<a href="/contacts/1/edit/" class="purple box"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="red box remove_account"><i class="far fa-trash-alt"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">2</td>
<td>Micropyramid</td>
<td scope="row">5</td>
<td>Refactored
</td>
<td class="text-center">23</td>
<td class="text-center">
<span class="badge badge-pill badge-secondary">15%</span>
<span class="badge box dark_grey">70%</span>
</td>
<td class="text-center">
<span class="badge badge-pill badge-info">20%</span>
<span class="badge box sky_blue">20%</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/edit/" class="btn btn-dark import"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
<a href="/contacts/1/edit/" data-toggle="tooltip" data-placement="bottom" title="" class="green box"><i class="far fa-edit"></i></a>
<a href="/contacts/1/edit/" class="sky_blue box"><i class="far fa-copy"></i></a>
<a href="/contacts/1/edit/" class="purple box"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="red box remove_account"><i class="far fa-trash-alt"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">2</td>
<td>Micropyramid</td>
<td scope="row">6</td>
<td>Refactored</td>
<td class="text-center">23</td>
<td class="text-center">
<span class="badge badge-pill badge-secondary">15%</span>
<span class="badge box dark_grey">70%</span>
</td>
<td class="text-center">
<span class="badge badge-pill badge-info">20%</span>
<span class="badge box sky_blue">20%</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/edit/" class="btn btn-dark import"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
<a href="/contacts/1/edit/" data-toggle="tooltip" data-placement="bottom" title="" class="green box"><i class="far fa-edit"></i></a>
<a href="/contacts/1/edit/" class="sky_blue box"><i class="far fa-copy"></i></a>
<a href="/contacts/1/edit/" class="purple box"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="red box remove_account"><i class="far fa-trash-alt"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">7</td>
<td>Artbnkr</td>
<td class="text-center">23</td>
<td class="text-center">
<span class="badge box dark_grey">70%</span>
</td>
<td class="text-center">
<span class="badge box sky_blue">20%</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" data-toggle="tooltip" data-placement="bottom" title="" class="green box"><i class="far fa-edit"></i></a>
<a href="/contacts/1/edit/" class="sky_blue box"><i class="far fa-copy"></i></a>
<a href="/contacts/1/edit/" class="purple box"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="red box remove_account"><i class="far fa-trash-alt"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">8</td>
<td>Now Finance</td>
<td class="text-center">125</td>
<td class="text-center">
<span class="badge box dark_grey">45%</span>
</td>
<td class="text-center">
<span class="badge box sky_blue">65%</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" data-toggle="tooltip" data-placement="bottom" title="" class="green box"><i class="far fa-edit"></i></a>
<a href="/contacts/1/edit/" class="sky_blue box"><i class="far fa-copy"></i></a>
<a href="/contacts/1/edit/" class="purple box"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="red box remove_account"><i class="far fa-trash-alt"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">9</td>
<td>Goshoppi</td>
<td class="text-center">154</td>
<td class="text-center">
<span class="badge box dark_grey">5%</span>
</td>
<td class="text-center">
<span class="badge box sky_blue">54%</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" data-toggle="tooltip" data-placement="bottom" title="" class="green box"><i class="far fa-edit"></i></a>
<a href="/contacts/1/edit/" class="sky_blue box"><i class="far fa-copy"></i></a>
<a href="/contacts/1/edit/" class="purple box"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="red box remove_account"><i class="far fa-trash-alt"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">10</td>
<td>Techond</td>
<td class="text-center">23</td>
<td class="text-center">
<span class="badge box dark_grey">15%</span>
</td>
<td class="text-center">
<span class="badge box sky_blue">20%</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" data-toggle="tooltip" data-placement="bottom" title="" class="green box"><i class="far fa-edit"></i></a>
<a href="/contacts/1/edit/" class="sky_blue box"><i class="far fa-copy"></i></a>
<a href="/contacts/1/edit/" class="purple box"><i class="fas fa-upload"></i></a>
<a href="/contacts/1/delete/" class="red box remove_account"><i class="far fa-trash-alt"></i></a>
</td>
</tr>

View file

@ -10,7 +10,7 @@
<div class="card">
<div class="card-body">
<div class="card-title text-right">
<span class="float-left">Contact List - 61</span>
<span class="float-left">Contact List - {{ contact_lists|length }}</span>
<a href="/m/cl/list/new/" class="primary_btn">Add New Contact</a>
</div>
<div class="table-responsive table-bordered ">
@ -21,108 +21,133 @@
<th width="20%">Name</th>
<th width="20%">Email</th>
<th width="15%">Last Actvity</th>
<th width="15%" class="text-center">Status</th>
<th width="15%" class="text-center">Tags</th>
<th width="10%">Actions</th>
</tr>
</thead>
<tbody>
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Micropyramid</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-success">Subscribed</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Peeljobs</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-secondary">Unsubscribed</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Study Signal</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-info">Bounced</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Radiostring</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-warning">Marked</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Dummycompany</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-danger">Spam</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Dummycompany</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-dark">Unconfirmed</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
{% for contact_list in contact_lists %}
<tr style="text-align:center;">
<td scope="row">{{ forloop.counter }}</td>
<td>{{ contact_list.name }}</td>
<td>{{ contact_list.created_by }}</td>
<td>
{{ contact_list.updated_on }}
</td>
<td class="text-center">
{% for tag in contact_list.tags.all %}
<span class="badge badge-success">{{tag.name}}</span>
{% empty %}
<span class="text-center">No Tags!</span>
{% endfor %}
</td>
<td class="actions">
<a href="{% url 'marketing:edit_contact_list' contact_list.id %}" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<!-- <a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a> -->
<a href="{% url 'marketing:delete_contact_list' contact_list.id %}" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
{% empty %}
<tr style="text-align:center;"><td colspan="6">No records found!</td></tr>
{% endfor %}
<!--
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Micropyramid</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-success">Subscribed</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Peeljobs</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-secondary">Unsubscribed</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Study Signal</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-info">Bounced</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Radiostring</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-warning">Marked</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Dummycompany</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-danger">Spam</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
<tr style="text-align:center;">
<td scope="row">12</td>
<td>Dummycompany</td>
<td>adminmp@gmail.com</td>
<td>
Tue, Nov 22, 2016, 12:00AM
</td>
<td class="text-center">
<span class="badge badge-dark">Unconfirmed</span>
</td>
<td class="actions">
<a href="/contacts/1/edit/" class="btn btn-success edit"><i class="fas fa-edit"></i></a>
<a href="/contacts/1/edit/" class="btn btn-info duplicate"><i class="fas fa-copy"></i></a>
<a href="/contacts/1/delete/" class="btn btn-danger delete remove_account"><i class="fas fa-trash"></i></a>
</td>
</tr>
-->
</tbody>
</table>
</div>

View file

@ -1,61 +1,115 @@
{% extends 'marketing/base.html' %}
{% load staticfiles %}
{% load paginate %}
{% block extralinks %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.css" rel="stylesheet">
{% endblock extralinks %}
{% block content %}
{{ block.super }}
{{ block.super }}
<div class="marketing_pages">
<div class="row marl">
<div class="col-md-12">
<form id="add_form" method="POST" action='' novalidate>
<div class="overview_form_block row marl justify-content-center">
<div class="col-md-5">
<div class="card">
<div class="card-body">
<div class="card-title text-center">
{% if contact_obj %}Edit{% else %}Create{% endif %} Contact List
</div>
<div class="row marl justify-content-center">
<form id="add_form" method="post" action="." enctype="multipart/form-data" novalidate>
<div class="overview_form_block row marl justify-content-center">
<div class="col-md-5">
<div class="card">
<div class="card-body">
<div class="card-title text-center">
{% if contact_list %}Edit{% else %}Create{% endif %} Contact List
</div>
<div class="row marl justify-content-center">
<div class="filter_col col-md-12">
<div class="form-group">
<label class="required">Name <span class="error">*</span></label>
<input type="text" class="form-control" placeholder="Enter Contacts Name">
<input type="text" class="form-control" name="name" value="{% if contact_list %}{{contact_list.name}}{% endif %}" placeholder="Enter Contacts Name">
<span id="id_name"></span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label>Contacts File </label>
<input type="file" class="form-control" >
<input type="file" class="form-control" name="contacts_file">
<span id="id_contacts_file"></span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="demo_clist">
<a href="#">Demo file <i class="fas fa-download"></i> </a>
</div>
</div>
<input type="hidden" name="from_account" value="{{request.GET.view_account}}">
{% if request.GET.view_account %}
<input type="hidden" name="account" value="{{request.GET.view_account}}">
{% endif %}
<input type="hidden" id="save_new_form" name="savenewform">
<div class="col-md-12">
<div class="row marl buttons_row text-center form_btn_row">
<a href="/m/" class="btn primary_btn" >Save</a>
<a href="/m/" class="btn seconadry_btn">Cancel</a>
<div class="form-group">
<label>Tags</label>
<div class="txt-box-div" id="tag-div">
<input type="text" name="tags" id="tags_1" class="tags" value="{% if contact_list %}{% for tag in contact_list.tags.all %}{{tag.name}}, {% endfor %}{% endif %}" />
</div>
<span id="id_tags"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="filter_col col-md-12">
<div class="demo_clist">
<a href="#">Demo file <i class="fas fa-download"></i> </a>
</div>
</div>
<div class="col-md-12">
<div class="row marl buttons_row text-center form_btn_row">
<button type="submit" class="btn primary_btn save_btn">Save</button>
<a href="{% url 'marketing:contact_lists' %}" class="btn seconadry_btn">Cancel</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% endblock %}
{% block js_block %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.js"></script>
<script src="https://malsup.github.com/jquery.form.js"></script>
<script>
// $(document).ajaxStart($.blockUI).ajaxStop($.unblockUI);
$('.save_btn').click(function(e){
e.preventDefault();
$("form#add_form").submit()
})
/*$.blockUI({
message: '<h1>Please wait ...</h1>',
css: {
border: 'none',
padding: '15px',
backgroundColor: '#000',
'-webkit-border-radius': '10px',
'-moz-border-radius': '10px',
opacity: .5,
color: '#fff'
}
});*/
// setTimeout($.unblockUI, 2000);
$('form#add_form').ajaxForm({
type: 'POST',
dataType: 'json',
url: $(this).attr('action'),
data: $(this).serialize(),
success: function(data) {
console.log("data", data)
if (data.error == false) {
window.location = "{% url 'marketing:contact_lists' %}";
} 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)
}
});
$(function() {
$('#tags_1').tagsInput({width:'auto'});
});
</script>
{% endblock js_block %}

View file

@ -25,6 +25,7 @@
</head>
<body class="main_body_bg_color">
<div class="wrapper">
{% block header %}
<header>
<nav class="navbar navbar-expand-lg navbar-dark fixed-top">
<a class="navbar-brand" href="{% url 'common:home' %}">Django CRM</a>
@ -63,9 +64,10 @@
</div>
{% endif %}
</nav>
</header>
</header>
{% endblock %}
<div id="mainbody" class="main_container">
<div id="mainbody" class="main_container" style="margin-top:65px;">
{% block breadcrumb %}
{% endblock %}
@ -81,6 +83,7 @@
<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>
<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.min.js"></script>
{% block js_block %}{% endblock js_block %}
<script type="text/javascript">
@ -93,6 +96,22 @@
$('#' + word).addClass('active')
}
});
/*
Usage is very simple; to block user activity for the page:
$.blockUI();
Blocking with a custom message:
$.blockUI({ message: '<h1><img src="busy.gif" /> Just a moment...</h1>' });
Blocking with custom style:
$.blockUI({ css: { backgroundColor: '#f00', color: '#fff'} });
To unblock the page:
$.unblockUI();
If you want to use the default settings and have the UI blocked for all ajax requests, it's as easy as this:
$(document).ajaxStart($.blockUI).ajaxStop($.unblockUI);
*/
</script>
</body>
</html>

View file

@ -1,13 +1,13 @@
{% extends 'root.html' %}
{% block main_menu %}
<ul class="navbar-nav mr-auto">
<li class="nav-item"><a class="nav-link" id="dashboard" href="{% url 'common:home' %}">Dashboard</a></li>
<li class="nav-item"><a class="nav-link" id="accounts" href="{% url 'accounts:list' %}">Accounts</a></li>
<li class="nav-item"><a class="nav-link" id="contacts" href="{% url 'contacts:list' %}">Contacts</a></li>
<li class="nav-item"><a class="nav-link" id="leads" href="{% url 'leads:list' %}">Leads</a></li>
<li class="nav-item"><a class="nav-link" id="opportunities"href="{% url 'opportunities:list' %}">Opportunities</a></li>
<li class="nav-item"><a class="nav-link" id="cases" href="{% url 'cases:list' %}">Cases</a></li>
<li class="nav-item"><a class="nav-link" id="documents" href="{% url 'common:doc_list' %}">Documents</a></li>
</ul>
<ul class="navbar-nav mr-auto">
<li class="nav-item"><a class="nav-link" id="dashboard" href="{% url 'common:home' %}">Dashboard</a></li>
<li class="nav-item"><a class="nav-link" id="accounts" href="{% url 'accounts:list' %}">Accounts</a></li>
<li class="nav-item"><a class="nav-link" id="contacts" href="{% url 'contacts:list' %}">Contacts</a></li>
<li class="nav-item"><a class="nav-link" id="leads" href="{% url 'leads:list' %}">Leads</a></li>
<li class="nav-item"><a class="nav-link" id="opportunities"href="{% url 'opportunities:list' %}">Opportunities</a></li>
<li class="nav-item"><a class="nav-link" id="cases" href="{% url 'cases:list' %}">Cases</a></li>
<li class="nav-item"><a class="nav-link" id="documents" href="{% url 'common:doc_list' %}">Documents</a></li>
</ul>
{% endblock %}