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:
parent
196a225578
commit
88ae667245
78 changed files with 3977 additions and 1779 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -15,3 +15,4 @@ ref.css
|
|||
ref.css.map
|
||||
.coverage
|
||||
crm/dev_settings\.py
|
||||
env
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
from django.contrib import admin
|
||||
from accounts.models import Account
|
||||
# Register your models here.
|
||||
|
||||
|
||||
admin.site.register(Account)
|
||||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;">
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
from django.contrib import admin
|
||||
from cases.models import Case
|
||||
|
||||
|
||||
admin.site.register(Case)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
|||
197
cases/views.py
197
cases/views.py
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -183,4 +183,5 @@
|
|||
$('#docs_filter').submit();
|
||||
});
|
||||
</script>
|
||||
{% endblock js_block %}
|
||||
{% endblock js_block %}
|
||||
|
||||
|
|
|
|||
|
|
@ -148,3 +148,4 @@
|
|||
|
||||
</script>
|
||||
{% endblock js_block %}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,4 +85,5 @@
|
|||
window.location = $(this).attr('href')
|
||||
});
|
||||
</script>
|
||||
{% endblock js_block %}
|
||||
{% endblock js_block %}
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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/',
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
304
common/views.py
304
common/views.py
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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")),
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
18
leads/migrations/0007_auto_20190306_1226.py
Normal file
18
leads/migrations/0007_auto_20190306_1226.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
130
leads/tests.py
130
leads/tests.py
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
|||
224
leads/views.py
224
leads/views.py
|
|
@ -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
290
marketing/forms.py
Normal 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
|
||||
178
marketing/migrations/0001_initial.py
Normal file
178
marketing/migrations/0001_initial.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
40
marketing/migrations/0002_auto_20190307_1227.py
Normal file
40
marketing/migrations/0002_auto_20190307_1227.py
Normal 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,
|
||||
),
|
||||
]
|
||||
34
marketing/migrations/0003_failedcontact.py
Normal file
34
marketing/migrations/0003_failedcontact.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
67
static/css/mention.css
Normal 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 */
|
||||
7
static/css/mention.css.map
Normal file
7
static/css/mention.css.map
Normal 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"
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
4
static/files/contacts_sample.csv
Normal file
4
static/files/contacts_sample.csv
Normal 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
|
||||
|
BIN
static/images/busy.gif
Normal file
BIN
static/images/busy.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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 %}
|
||||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
Loading…
Reference in a new issue