added tags field for campaign, changed styles for marketing module, added loading spinner and validation for file fields in campaign list create page, changes in marketing module (#238)

* 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

* completed crud operations to use email templates model, displaying contact list contacts and faile/invalid contacts, completed changes for new campaign, removed firstname and lastname as required and added title as required in leads create page, completed changes for Campaign email send with attachment

* added test cases in marketing, common modules, updated documentation and changed jquery plugin in campaign

* added mentions in comments section for all modules, added email templates when assigining a case or account, changed validation error messages in documents create and edit pages, added thumbnails for attachments in all detail pages

* added tags field for campaign, changed styles for marketing module, added loading spinner and validation for file fields in campaign list create page, changes in marketing module
This commit is contained in:
nikhila05 2019-06-18 17:48:03 +05:30 committed by GitHub
parent 12de7e6c62
commit 4f08cf7573
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
160 changed files with 14026 additions and 2665 deletions

View file

@ -20,6 +20,7 @@ deploy_mp:
- pip install -r /home/dj_crm/dj_crm/requirements.txt
- python /home/dj_crm/dj_crm/manage.py migrate --noinput
- sudo /etc/init.d/uwsgi restart
- sudo /usr/bin/supervisorctl restart all
only:
- master
tags:
@ -35,6 +36,7 @@ deploy_live:
- pip install -r /home/dj_crm/dj_crm/requirements.txt
- python /home/dj_crm/dj_crm/manage.py migrate --noinput
- sudo /etc/init.d/uwsgi restart
- sudo /usr/bin/supervisorctl restart all
only:
- master
tags:

View file

@ -7,7 +7,7 @@ COPY . /app/
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && \
apt update && \
apt install -y git ruby-dev nodejs postgresql-client && \
apt install -y git ruby-dev nodejs postgresql-client redis-server wkhtmltopdf && \
apt clean && \
gem install compass sass && \
npm -g install less && \

View file

@ -80,6 +80,28 @@ Demo credentials for Django CRM:
* **Password:** admin
Installation
============
* Install the dependencies
.. code-block:: python
pip install -r requirements.txt
python manage.py makemigrations
python manage.py createsuperuser
python manage.py runserver
This will install all the required dependencies for django-crm.
After this download and install the System Requirements.
System Requirements
===================
- wkhtmltopdf (https://wkhtmltopdf.org/downloads.html)
- sass (https://www.npmjs.com/package/sass) or (https://rubygems.org/gems/sass)
Community
=========

View file

@ -1,6 +1,6 @@
from django import forms
from .models import Account
from common.models import Comment, Attachments
from common.models import Comment, Attachments, User
from leads.models import Lead
from contacts.models import Contact
from django.db.models import Q
@ -36,14 +36,18 @@ class AccountForm(forms.ModelForm):
'placeholder': 'Postcode'})
self.fields["billing_country"].choices = [
("", "--Country--"), ] + list(self.fields["billing_country"].choices)[1:]
self.fields["lead"].queryset = Lead.objects.all(
).exclude(status='closed')
if request_user:
# self.fields["lead"].queryset = Lead.objects.all(
# ).exclude(status='closed')
if request_user.role == 'ADMIN':
self.fields["lead"].queryset = Lead.objects.filter().exclude(status='closed')
self.fields["contacts"].queryset = Contact.objects.filter()
else:
self.fields["lead"].queryset = Lead.objects.filter(
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))
self.fields['assigned_to'].required = False
if account_view:
self.fields['billing_address_line'].required = True
self.fields['billing_street'].required = True
@ -55,7 +59,7 @@ class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = ('name', 'phone', 'email', 'website', 'industry',
'description', 'status',
'description', 'status', 'assigned_to',
'billing_address_line', 'billing_street',
'billing_city', 'billing_state',
'billing_postcode', 'billing_country', 'lead', 'contacts')
@ -75,3 +79,15 @@ class AccountAttachmentForm(forms.ModelForm):
class Meta:
model = Attachments
fields = ('attachment', 'account')
class EmailForm(forms.Form):
recipients = forms.CharField(max_length=500)
message_subject = forms.CharField(max_length=500)
message_body = forms.CharField(widget=forms.Textarea)
def __init__(self, *args, **kwargs):
super(EmailForm, self).__init__(*args, **kwargs)
self.fields['message_subject'].widget.attrs['class'] = 'form-control'
self.fields['message_subject'].widget.attrs['required'] = True
self.fields['message_subject'].widget.attrs['placeholder'] = 'Email Subject'

View file

@ -0,0 +1,26 @@
# Generated by Django 2.1.7 on 2019-04-30 08:13
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contacts', '0003_merge_20190214_1427'),
('accounts', '0006_auto_20190212_1708'),
]
operations = [
migrations.CreateModel(
name='Email',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('sent_at', models.DateTimeField(auto_now_add=True)),
('message_subject', models.TextField(null=True)),
('message_body', models.TextField(null=True)),
('recipient', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='recieved_email', to='contacts.Contact')),
('sender', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sent_email', to='accounts.Account')),
],
),
]

View file

@ -0,0 +1,20 @@
# Generated by Django 2.1.7 on 2019-05-17 09:14
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('accounts', '0007_email'),
]
operations = [
migrations.AddField(
model_name='account',
name='assigned_to',
field=models.ManyToManyField(related_name='account_assigned_users', to=settings.AUTH_USER_MODEL),
),
]

View file

@ -6,6 +6,7 @@ from common.models import User
from common.utils import INDCHOICES, COUNTRIES
from phonenumber_field.modelfields import PhoneNumberField
from django.utils.text import slugify
from contacts.models import Contact
class Tags(models.Model):
@ -65,6 +66,8 @@ class Account(models.Model):
"Name of Contact", "Contact Name"), max_length=120)
contacts = models.ManyToManyField(
'contacts.Contact', related_name="account_contacts")
assigned_to = models.ManyToManyField(
User, related_name='account_assigned_users')
def __str__(self):
return self.name
@ -102,3 +105,23 @@ class Account(models.Model):
else:
address += self.get_billing_country_display()
return address
class Email(models.Model):
sender = models.ForeignKey(
Account, related_name='sent_email', on_delete=models.SET_NULL, null=True)
recipient = models.ForeignKey(
Contact, related_name='recieved_email', on_delete=models.SET_NULL, null=True)
sent_at = models.DateTimeField(auto_now_add=True)
message_subject = models.TextField(null=True)
message_body = models.TextField(null=True)
def __str__(self):
return self.message_body
def save(self, *args, **kwargs):
# prevent user from sending messages to themselves
if self.sender.email != self.recipient.email:
super(Email, self).save(*args, **kwargs)

28
accounts/tasks.py Normal file
View file

@ -0,0 +1,28 @@
from celery.task import task
from django.conf import settings
from django.core.mail import EmailMessage
from django.template.loader import render_to_string
from accounts.models import Account, Email
from contacts.models import Contact
@task
def send_email(subject, html_content, from_email=None,
recipients=[]):
account = Account.objects.filter(id=from_email).first()
if account:
contacts = recipients
for contact in contacts:
contact_obj = Contact.objects.filter(email=contact).first()
if contact_obj:
Email.objects.create(sender=account,
recipient=contact_obj,message_subject=subject,
message_body=html_content)
msg = EmailMessage(
subject,
html_content,
from_email=account.email,
to=[contact_obj.email, ]
)
msg.content_subtype = "html"
msg.send()

View file

@ -11,7 +11,8 @@
<!-- heading_create starts here -->
<div class="row marl">
<div class="col-lg-12 text-right">
<span class="d-inline"><a class="primary_btn" href="{% url 'accounts:new_account' %}"><i class="fa fa-plus"></i> Add New Account</a></span>
<span class="d-inline"><a class="primary_btn" href="{% url 'accounts:new_account' %}"><i class="fa fa-plus"></i>
Add New Account</a></span>
</div>
</div>
<div class="filter_row list_filter_row row marl">
@ -25,23 +26,27 @@
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Name</label>
<input type="text" class="form-control" placeholder="Account Name" name="name" {% if request.POST %} value="{{request.POST.name}}" {%endif%}/>
<input type="text" class="form-control" placeholder="Account Name" name="name" {% if request.POST %}
value="{{request.POST.name}}" {%endif%} />
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">City</label>
<input type="text" class="form-control" placeholder="City" name="city" {% if request.POST %} value="{{request.POST.city}}" {%endif%}/>
<input type="text" class="form-control" placeholder="City" name="city" {% if request.POST %}
value="{{request.POST.city}}" {%endif%} />
</div>
</div>
<input type="hidden" name="tab_status" id="tab_status">
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Tags</label>
<select class="form-control" id="id_tag" name="tag" >
<option value="">--Select Tags--</option>
<select class="form-control" id="id_tag" name="tag" multiple>
<!-- <option value="">--Select Tags--</option> -->
{% for tag in tags %}
<option value="{{tag.id}}" {% if request.POST.tag %} {% ifequal tag.id request.POST.tag|add:"0" %}selected{% endifequal %}{% endif %} >{{ tag.name }} </option>
<option value="{{tag.id}}" {% if request_tags %}
{% if tag.id|slugify in request_tags %}selected{% endif %}{% endif %}>{{ tag.name }}
</option>
{% endfor%}
</select>
</div>
@ -67,10 +72,12 @@
<div class="col-md-12">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="open-tab" data-toggle="tab" href="#open" role="tab" aria-controls="open" aria-selected="true">Active ({{open_accounts|length}})</a>
<a class="nav-link active" id="open-tab" data-toggle="tab" href="#open" role="tab" aria-controls="open"
aria-selected="true">Active ({{open_accounts|length}})</a>
</li>
<li class="nav-item">
<a class="nav-link" id="close-tab" data-toggle="tab" href="#close" role="tab" aria-controls="close" aria-selected="false">Closed ({{close_accounts|length}})</a>
<a class="nav-link" id="close-tab" data-toggle="tab" href="#close" role="tab" aria-controls="close"
aria-selected="false">Closed ({{close_accounts|length}})</a>
</li>
</ul>
<div class="tab-content" id="myTabContent">
@ -78,9 +85,10 @@
<div class="card">
<div class="card-body">
<div class="panel-heading-list card-title text-right">
<span class="total_count float-left">Open Accounts - {% if show_pageitems %} {% show_pageitems %}{% else %}{{open_accounts|length}}{% endif %}</span>
<span class="total_count float-left">Open Accounts - {% if show_pageitems %}
{% show_pageitems %}{% else %}{{open_accounts|length}}{% endif %}</span>
<span class="filter_toggle ">
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
</span>
</div>
<div class="table-responsive">
@ -106,19 +114,25 @@
{% endif %}
{% for each_account in open_accounts %}
<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 scope="row" class="s_no" data-id="{{forloop.counter}}">{{ 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 %}
{% 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;" />
<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>{% 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;">
{% with tags=each_account.tags.all %}
{% if tags %}
@ -132,8 +146,10 @@
</td>
<td class="actions">
{# <a href="{% url 'accounts:view_account' each_account.id %}" class="btn btn-info view"><i class="fas fa-eye"></i> View</a> #}
<a href="{% url 'accounts:edit_account' each_account.id %}" class="btn btn-success edit"><i class="fas fa-pencil-alt"></i></a>
<a href="{% url 'accounts:remove_account' each_account.id %}" class="btn btn-danger delete remove_account"><i class="fas fa-trash-alt"></i></a>
<a href="{% url 'accounts:edit_account' each_account.id %}" class="btn btn-success edit"><i
class="fas fa-pencil-alt"></i></a>
<a href="{% url 'accounts:remove_account' each_account.id %}"
class="btn btn-danger delete remove_account"><i class="fas fa-trash-alt"></i></a>
</td>
</tr>
{% endfor %}
@ -153,10 +169,11 @@
<div class="card">
<div class="card-body">
<div class="panel-heading-list card-title text-right">
<span class="total_count float-left">Closed Accounts - {% if show_pageitems %} {% show_pageitems %}{% else %}{{close_accounts|length}}{% endif %}</span>
<span class="total_count float-left">Closed Accounts - {% if show_pageitems %}
{% show_pageitems %}{% else %}{{close_accounts|length}}{% endif %}</span>
<span class="filter_toggle ">
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
</span>
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
</span>
</div>
<div class="table-responsive">
<table class="table">
@ -179,11 +196,15 @@
{% paginate 10 close_accounts %}
{% endif %}
{% for each_account in close_accounts %}
<tr id="account_details{{each_account.id}}" >
<tr id="account_details{{each_account.id}}">
<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.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>{% if each_account.name %}<a
href="{% url 'accounts:view_account' each_account.id %}">{{ each_account.name }}</a>{% 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;">
{% with tags=each_account.tags.all %}
{% if tags %}
@ -196,8 +217,10 @@
{% endwith %}
</td>
<td class="actions">
<a href="{% url 'accounts:edit_account' each_account.id %}" class="btn btn-success edit"><i class="fas fa-pencil-alt"></i></a>
<a href="{% url 'accounts:remove_account' each_account.id %}" class="btn btn-danger delete remove_account"><i class="fas fa-trash-alt"></i></a>
<a href="{% url 'accounts:edit_account' each_account.id %}" class="btn btn-success edit"><i
class="fas fa-pencil-alt"></i></a>
<a href="{% url 'accounts:remove_account' each_account.id %}"
class="btn btn-danger delete remove_account"><i class="fas fa-trash-alt"></i></a>
</td>
</tr>
{% endfor %}
@ -228,48 +251,72 @@
{% block js_block %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<script type="text/javascript">
$("#close-tab").click(function(e){
$("#close-tab").click(function (e) {
$("#tab_status").val('Closed');
})
$("#open-tab").click(function(e){
$("#open-tab").click(function (e) {
$("#tab_status").val('Open')
})
tab_status = "{{tab_status}}"
if(tab_status == 'Closed'){
if (tab_status == 'Closed') {
$("#close-tab").click()
}else{
} else {
$("#open-tab").click()
}
$('.remove_account').click(function(e){
$('.remove_account').click(function (e) {
e.preventDefault()
url = $(this).attr('href')
if (!confirm('Are you sure you want to delete?'))
return;
window.location = $(this).attr('href')
});
$(".reset").click(function(e){
$(".reset").click(function (e) {
window.location = "{% url 'accounts:list'%}"
});
search = "{{search}}"
if (search == 'True'){
if (search == 'True') {
$(".list_filter_row").show();
}
$(document).ready(function(){
$(".filter_toggle").click(function(){
$(".list_filter_row").toggle();
});
});
$("a[rel='page']").click(function(e){
$(document).ready(function () {
$(".filter_toggle").click(function () {
$(".list_filter_row").toggle();
});
$('#id_tag').select2();
////////////////////////
$.urlParam = function (name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)')
.exec(window.location.search);
return (results !== null) ? results[1] || 0 : false;
}
if ($.urlParam('page')) {
$('.s_no').each(function (i, e) {
pageNum = $.urlParam('page')
if (!$(this).attr('data-id').endsWith('0')) {
serialNumber = parseInt((pageNum - 1) + $(this).attr('data-id'))
console.log(serialNumber)
}
else {
serialNumber = parseInt($(this).attr('data-id')) * pageNum
console.log(serialNumber)
}
})
}
////////////////////////
});
$("a[rel='page']").click(function (e) {
e.preventDefault();
$('#accounts_filter').attr("action", $(this).attr("href"));
$('#accounts_filter').submit();

View file

@ -3,8 +3,9 @@
{% block breadcrumb %}
{% block extralinks %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/themes/start/jquery-ui.css" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css"
href="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/themes/start/jquery-ui.css" />
{% endblock %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
@ -27,39 +28,46 @@
<div class="col-md-4">
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Name{% if account_form.name.field.required %}<span class="error_marker" style="color:red">*</span>{% endif %}</label>
<label for="exampleInputEmail1" class="required">Name{% if account_form.name.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ account_form.name }}
<span class="error" id="id__name">{{ account_form.name.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1">Website{% if account_form.website.field.required %}<span class="error">*</span>{% endif %}</label>
<label for="exampleInputEmail1">Website{% if account_form.website.field.required %}<span
class="error">*</span>{% endif %}</label>
{{ account_form.website }}
<span class="error" id="id__website">{{ account_form.website.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Phone{% if account_form.phone.field.required %}<span class="error_marker" style="color:red">*</span>{% endif %}</label>
<label for="exampleInputEmail1" class="required">Phone{% if account_form.phone.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ account_form.phone }}
<span class="error" id="id__phone">{{ account_form.phone.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="exampleInputEmail1" class="required">Email Address{% if account_form.email.field.required %}<span class="error_marker" style="color:red">*</span>{% endif %}</label>
<label for="exampleInputEmail1" class="required">Email
Address{% if account_form.email.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
{{ account_form.email }}
<span class="error" id="id__email">{{ account_form.email.errors }}</span>
</div>
</div>
<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>
<label for="exampleInputEmail1" class="required">Leads{% if account_form.lead.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
<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>
<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>
@ -69,49 +77,60 @@
<div class="col-md-4">
<div class="filter_col billing_block col-md-12" style="padding: 0px;">
<div class="form-group">
<label for="exampleInputEmail1">Billing Address {% if account_form.billing_address_line.field.required %}<span class="error_marker" style="color:red">*</span>{% endif %}</label>
<label for="exampleInputEmail1">Billing Address
{% if account_form.billing_address_line.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
{{ account_form.billing_address_line }}
<span class="error" id="id__billing_address_line">{{ account_form.billing_address_line.errors }}</span>
<span class="error"
id="id__billing_address_line">{{ account_form.billing_address_line.errors }}</span>
</div>
<div class="row" style="margin-top: 10px;">
<div class="col-md-6">
<div class="form-group">
<label for="exampleInputEmail1">Street {% if account_form.billing_street.field.required %}<span class="error_marker" style="color:red">*</span>{% endif %}</label>
<label for="exampleInputEmail1">Street {% if account_form.billing_street.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ account_form.billing_street }}
<span class="error" id="id__billing_street">{{ account_form.billing_street.errors }}</span>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="exampleInputEmail1">PostCode {% if account_form.billing_postcode.field.required %}<span class="error_marker" style="color:red">*</span>{% endif %}</label>
<label for="exampleInputEmail1">PostCode
{% if account_form.billing_postcode.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
{{ account_form.billing_postcode }}
<span class="error" id="id__billing_postcode">{{ account_form.billing_postcode.errors }}</span>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="exampleInputEmail1">City {% if account_form.billing_city.field.required %}<span class="error_marker" style="color:red">*</span>{% endif %}</label>
<label for="exampleInputEmail1">City {% if account_form.billing_city.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ account_form.billing_city }}
<span class="error" id="id__billing_city">{{ account_form.billing_city.errors }}</span>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="exampleInputEmail1">State {% if account_form.billing_state.field.required %}<span class="error_marker" style="color:red">*</span>{% endif %}</label>
<label for="exampleInputEmail1">State {% if account_form.billing_state.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ account_form.billing_state }}
<span class="error" id="id__billing_state">{{ account_form.billing_state.errors }}</span>
</div>
</div>
<div class="col-md-12" style="margin-top: 10px;">
<div class="form-group">
<label for="exampleInputEmail1">Country {% if account_form.billing_country.field.required %}<span class="error_marker" style="color:red">*</span>{% endif %}</label>
<label for="exampleInputEmail1">Country {% if account_form.billing_country.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ account_form.billing_country }}
<span class="error" id="id__billing_country">{{ account_form.billing_country.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="id_contacts" class="required">Contacts{% if account_form.contacts.field.required %}<span class="error_marker" style="color:red">*</span>{% endif %}</label>
<label for="id_contacts"
class="required">Contacts{% if account_form.contacts.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
{{ account_form.contacts }}
<span class="error" id="id__contacts">{{ account_form.contacts.errors }}</span>
</div>
@ -124,19 +143,28 @@
</div>
<div class="col-md-4">
<div class="filter_col col-12">
<!--<div class="form-group">
<label for="exampleInputEmail1">Assigned Users</label>
<select class="assigned_users form-control" name="assigned_to" multiple="multiple">
{% for user in users %}
<option value="{{user.id}}" {% if user in account_obj.assigned_to.all or user.id in assignedto_list %} selected="" {% endif %}>{{user.email}}</option>
{% endfor %}
<div class="form-group">
<label for="exampleInputEmail1">Assign To </label>
<select name="assigned_to" id="id_assigned_to" class="assigned_users form-control"
multiple>
{% for user in users %}
<option value="{{user.id}}" {% if user in account_obj.assigned_to.all %} selected="" {% endif %}>
{{user.email}}</option>
{% endfor %}
</select>
{{ account_form.assigned_to.errors }}
</div>-->
<input type="hidden" id="id_assigned_to_hidden" name="assigned_to_">
<span class="error" id="id__assigned_to">{{ account_form.assigned_to.errors }}</span>
<!-- <label for="id_sattus">Assign To{% if account_form.assigned_to.field.required %}<span
class="error">*</span>{% endif %}</label>
{{ account_form.assigned_to }}
<span class="error">{{ account_form.assigned_to.errors }}</span> -->
</div>
</div>
<div class="filter_col col-12">
<div class="form-group">
<label for="id_sattus">Status{% if account_form.status.field.required %}<span class="error">*</span>{% endif %}</label>
<label for="id_sattus">Status{% if account_form.status.field.required %}<span
class="error">*</span>{% endif %}</label>
{{ account_form.status }}
<span class="error">{{ account_form.status.errors }}</span>
</div>
@ -144,7 +172,8 @@
<div class="filter_col col-12">
<div class="form-group">
<label>Tags</label>
<div class="txt-box-div" id="tag-div"><input type="text" name="tags" id="tags_1" value="{% for t in account_obj.tags.all %}{{t.name}}, {% endfor %}" class="tags"/>
<div class="txt-box-div" id="tag-div"><input type="text" name="tags" id="tags_1"
value="{% for t in account_obj.tags.all %}{{t.name}}, {% endfor %}" class="tags" />
</div>
</div>
</div>
@ -153,14 +182,14 @@
<label for="exampleInputEmail1">Attachment</label>
<input type="file" name="account_attachment">
{% if account_obj.account_attachment.count %}
{% for attachment in account_obj.account_attachment.all %}
<div id="attachment{{attachment.id}}" class="mt-2">
<a target="_blank" href="{{ attachment.attachment.url }}">{{ attachment.file_name }}</a>
{% if not attachment.created_by.role == 'ADMIN' or attachment.created_by.role == request.user.role %}
<a class="action btn primary_btn" onclick="remove_attachment({{attachment.id}})">X</a>
{% endif %}
</div>
{% endfor %}
{% for attachment in account_obj.account_attachment.all %}
<div id="attachment{{attachment.id}}" class="mt-2">
<a target="_blank" href="{{ attachment.attachment.url }}">{{ attachment.file_name }}</a>
{% if not attachment.created_by.role == 'ADMIN' or attachment.created_by.role == request.user.role %}
<a class="action btn primary_btn" onclick="remove_attachment({{attachment.id}})">X</a>
{% endif %}
</div>
{% endfor %}
{% endif %}
<span class="error"></span>
</div>
@ -168,7 +197,7 @@
</div>
<div class="col-md-12">
<div class="row marl buttons_row form_btn_row text-center">
<button class="btn btn-default save" type="submit">Save</button>
<button class="btn btn-default save" type="submit" id="call_save">Save</button>
<a href="{% url 'accounts:list' %}" class="btn btn-default clear" id="create_user_cancel">Cancel</a>
</div>
</div>
@ -182,8 +211,9 @@
</form>
{% else %}
<div class="justify-content-center text-center">
<h3 class="mt-5">create a contacts and leads for creating an account</h3>
</div>
<h3 class="mt-5">Create a <a href="{% url 'contacts:add_contact' %}">contact</a> and <a
href="{% url 'leads:add_lead' %}">lead</a> to create an account</h3>
</div>
{% endif %}
{% endblock %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
@ -196,11 +226,26 @@
// $(document).ready(function() {
// $('.assigned_users').select2();
// });
$(document).ready(function() {
$(document).ready(function () {
$("#id_contacts").select2();
$("#id_assigned_to").select2()
$("#id_assigned_to").change(function(){
$("#id_assigned_to").attr('name', 'assigned_to')
})
// $("#id_assigned_to")
// var data_assigned_to = []
// data_assigned_to = $('#id_assigned_to').val()
// $('#id_assigned_to_').val($('#id_assigned_to').val())
// console.log($('#id_assigned_to').val())
// console.log($('#id_assigned_to_').val())
// $('#id_assigned_to').change(function(){
// data_assigned_to = []
// data_assigned_to.push($(this).val())
// $('#id_assigned_to_hidden').val(data_assigned_to)
// })
});
$("#copy_billing_btn").click(function(){
$("#copy_billing_btn").click(function () {
var address_line = $("#id_address_line").val();
var street = $("#id_street").val();
var city = $("#id_city").val();
@ -208,52 +253,50 @@
var postcode = $("#id_postcode").val();
var country = $("#id_country").val();
});
$(".savenew").on('click', function(e){
$(".savenew").on('click', function (e) {
e.preventDefault()
$("#save_new_form").val('true')
$('#formid').submit()
});
function onAddTag(tag) {
alert("Added a tag: " + tag);
}
function onRemoveTag(tag) {
alert("Removed a tag: " + tag);
}
function onChangeTag(input,tag) {
function onChangeTag(input, tag) {
alert("Changed a tag: " + tag);
}
$(function() {
$('#tags_1').tagsInput({width:'auto'});
$(function () {
$('#tags_1').tagsInput({ width: 'auto' });
$('#tags_2').tagsInput({
width: 'auto',
onChange: function(elem, elem_tags)
{
var languages = ['php','ruby','javascript'];
$('.tag', elem_tags).each(function()
{
if($(this).text().search(new RegExp('\\b(' + languages.join('|') + ')\\b')) >= 0)
onChange: function (elem, elem_tags) {
var languages = ['php', 'ruby', 'javascript'];
$('.tag', elem_tags).each(function () {
if ($(this).text().search(new RegExp('\\b(' + languages.join('|') + ')\\b')) >= 0)
$(this).css('background-color', 'yellow');
});
}
});
$('#tags_3').tagsInput({
width: 'auto',
//autocomplete_url:'test/fake_plaintext_endpoint.html' //jquery.autocomplete (not jquery ui)
autocomplete_url:'test/fake_json_endpoint.html' // jquery ui autocomplete requires a json endpoint
autocomplete_url: 'test/fake_json_endpoint.html' // jquery ui autocomplete requires a json endpoint
});
// Uncomment this line to see the callback functions in action
// $('input.tags').tagsInput({onAddTag:onAddTag,onRemoveTag:onRemoveTag,onChange: onChangeTag});
// Uncomment this line to see an input with no interface for adding new tags.
// $('input.tags').tagsInput({interactive:false});
// Uncomment this line to see the callback functions in action
// $('input.tags').tagsInput({onAddTag:onAddTag,onRemoveTag:onRemoveTag,onChange: onChangeTag});
// Uncomment this line to see an input with no interface for adding new tags.
// $('input.tags').tagsInput({interactive:false});
});
function remove_attachment(x) {
const con = confirm("Do you want to Delete it for Sure!?");
@ -273,22 +316,29 @@
$('form#formid').ajaxForm({
type:'POST',
dataType:'json',
type: 'POST',
dataType: 'json',
url: ".",
data:$('#formid').serialize(),
success: function(data) {
if(data.error){
data: $('#formid').serialize(),
success: function (data) {
if (data.error) {
$('.error').html('')
for (var key in data.errors) {
$('#id__'+key).html("<p>" + data.errors[key][0] + "</p>");
$('#id__' + key).html("<p>" + data.errors[key][0] + "</p>");
};
$('#submit_btn').removeAttr('disabled')
}
else{
window.location = data.success_url;
}
else {
window.location = data.success_url;
}
}
});
// $('#call_save').click(function (e) {
// e.preventDefault()
// console.log($('#formid').serialize())
// console.log($('#formid').serializeArray())
// })
</script>
{% endblock js_block %}
{% endblock js_block %}

View file

@ -0,0 +1,178 @@
{% extends 'sales/base.html' %}
{% load staticfiles %}
{% load paginate %}
{% block extralinks %}
{% load thumbnail %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.6/css/selectize.bootstrap3.min.css">
{% endblock %}
{% block content %}
<!-- main_container starts here -->
<div class="main_container">
<!-- heading_create starts here -->
<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">
Send Mail
</div>
<form>
<div class="row marl">
<div class="col-md-12">
<div class="form-group">
<label for="">Contacts</label>
<!-- <input type="text" class="form-control" name="contacts_list" id="id_contacts"> -->
<!-- <select name="recipients" id="_id_recipients">
<option value="">email</option>
</select> -->
<input type="text" placeholder="Contacts" name="recipients" id="_id_recipients">
</div>
<span id="id_recipients"></span>
</div>
<div class="col-md-12">
<div class="form-group">
<label for="">Subject</label>
<input type="text" class="form-control" name="message_subject" placeholder="Enter Subject">
</div>
<span id="id_message_subject"></span>
</div>
<div class="col-md-12">
<div class="form-group">
<label>Message</label>
<div id="editor-container"></div>
</div>
<input type="hidden" name="message_body" id="html_content">
<span id="id_message_body"></span>
</div>
<!-- <div class="col-md-12 text-center">
<pre id="myPrecious"></pre>
<div id="justText"></div>
<div id="justHtml"></div>
</div> -->
<div class="col-md-12 text-center">
<button type="submit" class="primary_btn mar_btm save_btn">Send</button>
<a href="{% url 'accounts:create_mail' account_id %}" class="primary_btn">Cancel</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="https://malsup.github.com/jquery.form.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.6/js/standalone/selectize.js"></script>
<script>
$(document).ready(function () {
// $("#id_contacts").select2();
var REGEX_EMAIL = '([a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+/=?^_`{|}~-]+)*@' +
'(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)';
$('#_id_recipients').selectize({
persist: false,
maxItems: null,
valueField: 'email',
labelField: 'name',
searchField: ['name', 'email'],
// options: [
// {email: 'brian@thirdroute.com', name: 'Brian Reavis'},
// {email: 'nikola@tesla.com', name: 'Nikola Tesla'},
// {email: 'someone@gmail.com'}
// ],
options: {{contacts_list|safe}},
render: {
item: function(item, escape) {
return '<div>' +
(item.name ? '<span class="name">' + escape(item.name) + '</span>' : '') +
(item.email ? '<span class="email">' + escape(item.email) + '</span>' : '') +
'</div>';
},
option: function(item, escape) {
var label = item.name || item.email;
var caption = item.name ? item.email : null;
return '<div>' +
'<span class="label">' + escape(label) + '</span>' +
(caption ? '<span class="caption">' + escape(caption) + '</span>' : '') +
'</div>';
}
},
createFilter: function(input) {
var match, regex;
// email@address.com
regex = new RegExp('^' + REGEX_EMAIL + '$', 'i');
match = input.match(regex);
if (match) return !this.options.hasOwnProperty(match[0]);
// name <email@address.com>
regex = new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$' , 'i' ); match=input.match(regex); if (match) return
!this.options.hasOwnProperty(match[2]); return false; }, create: function(input) { if ((new RegExp('^' +
REGEX_EMAIL + '$' , 'i' )).test(input)) { return {email: input}; } var match=input.match(new RegExp('^([^<]*)\<'
+ REGEX_EMAIL + '\>$' , 'i' )); if (match) { return { email : match[2], name : $.trim(match[1]) }; }
alert('Invalid email address.'); return false; } });
});
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'
});
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 'accounts:list' %}";
} else {
/*$(document).ajaxStop($.unblockUI);*/
// $.unblockUI();
$('p.failure').remove();
for (var key in data.errors) {
$('#id_' + key).html('<p class="error failure"> *' + data.errors[key] + '</p>');
}
}
},
error: function (xhr, errmsg, err) {
console.log('error data', errmsg, err)
}
});
</script>
{% endblock js_block %}

View file

@ -1,10 +1,21 @@
{% extends 'sales/base.html' %}
{% load staticfiles %}
{% load thumbnail %}
{% block breadcrumb %}
{% block extralinks %}
<link rel="stylesheet" href="{% static 'css/bootstrap-suggest.css' %}">
<style>
.opacity_block{
opacity: 0.2;
}
</style>
{% endblock extralinks %}
<!-- breadcrumb starts here -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'accounts:list' %}">Accounts</a></li>
<li class="breadcrumb-item"><a href="{% url 'accounts:list' %}">Accounts</a></li>
<li class="active breadcrumb-item">{{account_record.name}}</li>
</ol>
</nav>
@ -12,19 +23,19 @@
{% block content %}
<!-- ACCOUNTS VIEW STARTS -->
<div class="main_container" id="maincontainer">
<div class="overview_form_block row marl justify-content-center">
<div class="col-md-8">
<div class="overview_form_block row marl justify-content-center" id="opacity_block">
<div class="col-md-8" id="opacity_block">
<div class="card">
<div class="card-body" id="datashow">
<div class="card-title text-right">
<h5>
<span class="float-left title">Overview</span>
<span style="margin-top: 0px">
<span style="margin-top: 0px">
<div class="dropdown buttons_row">
<button class="btn primary_btn dropdown-toggle" type="button" data-toggle="dropdown">Actions
<span class="caret"></span></button>
<span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="{% url 'accounts:edit_account' account_record.id %}" >Edit</a></li>
<li><a href="{% url 'accounts:edit_account' account_record.id %}">Edit</a></li>
</ul>
</div>
</span>
@ -34,7 +45,7 @@
<div class="col-md-4">
{% if account_record.name %}
<div class="filter_col col-md-12" id="iname">
<div class="form-group" >
<div class="form-group">
<label class="acc_field_label" for="id_name" data-name="name">Name</label>
<div class="account_field" id="account_name" data-name="name">{{ account_record.name }}</div>
</div>
@ -74,12 +85,12 @@
</div>
</div>
{% endif %}
{% if account_record.contacts %}
{% if account_record.contacts.all %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_website" data-name="name">Contacts</label>
{% for contact in account_record.contacts.all %}
<div class="account_field" id="account_website" data-name="name">{{ contact }}</div>
<div class="account_field" id="account_website" data-name="name">{{ contact }}</div>
{% endfor %}
</div>
</div>
@ -104,11 +115,24 @@
</div>
</div>
{% endif %}
<div class="filter_col col-md-12">
{% if account_record.assigned_to.all %}
<div class="form-group">
<label class="case_field_label" for="id_contact" data-name="name">Assigned To</label>
<div class="case_field" id="case_contact" data-name="name">
{% for user in account_record.assigned_to.all %}
<div>{{ user }}</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
{% if account_record.description %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_description" data-name="name">Description</label>
<div class="account_field" id="account_description" data-name="name">{{ account_record.description }}</div>
<div class="account_field" id="account_description" data-name="name">{{ account_record.description }}
</div>
</div>
</div>
{% endif %}
@ -127,7 +151,8 @@
</div>
<div class="col-md-12">
<div class="created_information">
Created by <b>{{ account_record.created_by }}</b> created on <i><b>{{ account_record.created_on }}</b></i>
Created by <b>{{ account_record.created_by }}</b> created on
<b>{{ account_record.created_on }}</b>
</div>
</div>
<div class="col-md-12 " style="padding:0">
@ -141,10 +166,13 @@
<form id="attachment_form" method="POST" enctype="multipart/form-data">
<div class="form-group ">
<input type="file" class="form-control-file" cols="40" id="id_attachments" name="attachment">
<div><i id="AttachmentError" style="display:none; color:red"></i></div>
<div>
<p id="AttachmentError" style="display:none; color:red"></p>
</div>
<br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;" id="attachment_submit">Save</button>
<button class="btn btn-default save" style="text-align: center;"
id="attachment_submit">Save</button>
</div>
</div>
<input type="hidden" value="{{account_record.id}}" name="accountid">
@ -155,11 +183,19 @@
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == attachment.created_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="action btn primary_btn" onclick="remove_attachment({{attachment.id}})">Remove</button>
<button class="action btn primary_btn"
onclick="remove_attachment({{attachment.id}})">Remove</button>
</div>
</div>
{% endif %}
<div class="stream-post-container" id="attachment_name{{attachment.id}}">
{% if attachment.attachment %}
{% if 'image' in attachment.file_type %}
{% thumbnail attachment.attachment "80x80" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" class="mb-2">
{% endthumbnail %}
{% endif %}
{% endif %}
<pre> <span class="icon"><i style="font-size:1.5rem;" class="{{attachment.get_file_type_display}}"></i> </span>{{attachment.file_name}} <a href="{% url 'common:download_attachment' attachment.id %}">Download</a></pre>
</div>
<div class="stream-container">
@ -183,11 +219,15 @@
{% if comment_permission %}
<form id="comment_form" method="POST" enctype="multipart/form-data">
<div class="form-group">
<textarea class="form-control" textarea cols="40" rows="3" id="id_comments" name="comment" placeholder="Submit Your Comments Here"></textarea>
<div><i id="CommentError" style="display:none; color:red"></i></div>
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_comments"
name="comment" placeholder="Submit Your Comments Here"></textarea>
<div>
<p id="CommentError" style="display:none; color:red"></p>
</div>
</br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;" id="comment_submit" type="submit">Submit</button>
<button class="btn btn-default save" style="text-align: center;" id="comment_submit"
type="submit">Submit</button>
</div>
</div>
<input type="hidden" value="{{ account_record.id }}" name="accountid">
@ -198,7 +238,8 @@
<li class="list-group-item list-row" id="comment{{comment.id}}">
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == comment.commented_by %}
<div class="list-row-buttons btn-group float-right">
<button class="btn primary_btn btn-sm dropdown-toggle" data-toggle="dropdown" type="button">Actions<span class="caret"></span></button>
<button class="btn primary_btn btn-sm dropdown-toggle" data-toggle="dropdown"
type="button">Actions<span class="caret"></span></button>
<ul class="dropdown-menu text-center">
<li>
<a class="action" onclick="edit_comment({{comment.id}})">Edit</a>
@ -221,17 +262,21 @@
</ul>
</div>
</div>
<div class="modal fade" id="Comments_Accounts_Modal" role="dialog" data-keyboard="false" data-backdrop="static">
<div class="modal fade" id="Comments_Accounts_Modal" role="dialog" data-keyboard="false"
data-backdrop="static">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Update Your Comment</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</button>
</div>
<form id="comment_edit_form" method="POST" >
<form id="comment_edit_form" method="POST">
<div class="modal-body">
<div class="form-group">
<textarea class="form-control" textarea cols="40" rows="3" id="id_editcomment" name="comment" placeholder="Submit Your Comments" style="color:black;"></textarea><span><i id="CommentEditError" style="display:none; color:red"></i></span>
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_editcomment"
name="comment" placeholder="Submit Your Comments" style="color:black;"></textarea><span>
<p id="CommentEditError" style="display:none; color:red"></p>
</span>
<input type="hidden" value="{{ account.id }}" name="accountid">
<input type="hidden" value="" name="commentid" id="commentid">
</div>
@ -257,7 +302,13 @@
<div class="card-body">
<div class="panel-heading-list card-title view-pad">
Contacts
<a href="{% url 'contacts:add_contact' %}?view_account={{account_record.id}}"><span class="fa fa-plus pull-right" style="color:black;"></span></a>
<a href="{% url 'contacts:add_contact' %}?view_account={{account_record.id}}"><span
class="fa fa-plus pull-right" style="color:black;"></span></a>
{% if contacts|length > 0 %}
<a href="{% url 'accounts:create_mail' account_record.id %}" style="color:black">
<span class="pull-right mr-2">Send Mail</span>
</a>
{% endif %}
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped table-condensed">
@ -280,9 +331,11 @@
<td>{{ contact.phone }}</td>
<td>{{ contact.email }}</td>
<td class="actions">
<a href="{% url 'contacts:view_contact' pk=contact.id %}" class="btn btn-primary">View</a>
<a href="{% url 'contacts:edit_contact' pk=contact.id %}?view_account={{account_record.id}}" class="btn btn-success">Edit</a>
<a href="{% url 'contacts:remove_contact' pk=contact.id %}?view_account={{account_record.id}}" class="btn btn-danger">Remove</a>
<a href="{% url 'contacts:view_contact' pk=contact.id %}" class="btn btn-primary" style="color: white;">View</a>
<a href="{% url 'contacts:edit_contact' pk=contact.id %}?view_account={{account_record.id}}"
class="btn btn-success">Edit</a>
<a href="{% url 'contacts:remove_contact' pk=contact.id %}?view_account={{account_record.id}}"
class="btn btn-danger">Remove</a>
</td>
</tr>
{% endfor %}
@ -305,7 +358,8 @@
<div class="card-body">
<div class="panel-heading-list card-title view-pad">
Opportunities
<a href="{% url 'opportunity:save' %}?view_account={{account_record.id}}"><span class="fa fa-plus pull-right" style="color:black;"></span></a>
<a href="{% url 'opportunity:save' %}?view_account={{account_record.id}}"><span
class="fa fa-plus pull-right" style="color:black;"></span></a>
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped table-condensed">
@ -327,10 +381,13 @@
<td>{{opportunity.name}}</td>
<td>{{opportunity.stage}}</td>
<td>{{opportunity.probability}} {% if opportunity.probability %} % {% endif %}</td>
<td class="actions">
<a href="{% url 'opportunities:opp_view' pk=opportunity.id %}" class="btn btn-primary">View</a>
<a href="{% url 'opportunities:opp_edit' pk=opportunity.id %}?view_account={{account_record.id}}" class="btn btn-success">Edit</a>
<a href="{% url 'opportunities:opp_remove' pk=opportunity.id %}?view_account={{account_record.id}}" class="btn btn-danger">Remove</a>
<td class="actions">
<a href="{% url 'opportunities:opp_view' pk=opportunity.id %}"
class="btn btn-primary" style="color: white;">View</a>
<a href="{% url 'opportunities:opp_edit' pk=opportunity.id %}?view_account={{account_record.id}}"
class="btn btn-success">Edit</a>
<a href="{% url 'opportunities:opp_remove' pk=opportunity.id %}?view_account={{account_record.id}}"
class="btn btn-danger">Remove</a>
</td>
</tr>
{% endfor %}
@ -353,7 +410,8 @@
<div class="card-body">
<div class="panel-heading-list card-title view-pad ">
Cases
<a href="{% url 'cases:add_case' %}?view_account={{account_record.id}}"><span class="fa fa-plus pull-right" style="color:black;"></span></a>
<a href="{% url 'cases:add_case' %}?view_account={{account_record.id}}"><span
class="fa fa-plus pull-right" style="color:black;"></span></a>
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped table-condensed">
@ -375,10 +433,12 @@
<td>{{ case.name }}</td>
<td>{{ case.status }}</td>
<td>{{ case.priority }}</td>
<td class="actions">
<a href="{% url 'cases:view_case' pk=case.id %}" class="btn btn-primary">View</a>
<a href="{% url 'cases:edit_case' pk=case.id %}?view_account={{account_record.id}}" class="btn btn-success">Edit</a>
<a href="{% url 'cases:remove_case' case_id=case.id %}?view_account={{account_record.id}}" class="btn btn-danger">Remove</a>
<td class="actions">
<a href="{% url 'cases:view_case' pk=case.id %}" class="btn btn-primary" style="color: white;">View</a>
<a href="{% url 'cases:edit_case' pk=case.id %}?view_account={{account_record.id}}"
class="btn btn-success">Edit</a>
<a href="{% url 'cases:remove_case' case_id=case.id %}?view_account={{account_record.id}}"
class="btn btn-danger">Remove</a>
</td>
</tr>
{% endfor %}
@ -394,38 +454,100 @@
</div>
</div>
</form>
<form method='GET' class="accountdetails_row">
<div class="table_container_row row marl no-gutters">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<div class="panel-heading-list card-title view-pad">
Tasks
<!-- <a href="{% url 'opportunity:save' %}?view_account={{account_record.id}}"><span
class="fa fa-plus pull-right" style="color:black;"></span></a> -->
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped table-condensed">
{% if tasks|length > 0 %}
<thead>
<tr>
<th width="5%" style="text-align:center;">S.No</th>
<th width="30%" style="text-align:center;">Title</th>
<th width="20%" style="text-align:center;">Status</th>
<th width="20%" style="text-align:center;">Priority</th>
<th width="25%" style="text-align:center;">Actions </th>
</tr>
</thead>
{% endif %}
<tbody>
{% for task in tasks %}
<tr id="task_{{task.id}}" class="text-center">
<td scope="row">{{ forloop.counter }}</td>
<td>{{task.title}}</td>
<td>{{task.status}}</td>
<td>{{task.priority}} {% if task.probability %} % {% endif %}</td>
<td class="actions">
<a href="{% url 'tasks:task_detail' task.id %}" class="btn btn-primary" style="color: white;">View</a>
<a href="{% url 'tasks:task_edit' task.id %}?view_account={{account_record.id}}"
class="btn btn-success">Edit</a>
<a href="{% url 'tasks:task_delete' task.id %}?view_account={{account_record.id}}"
class="btn btn-danger">Remove</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% ifequal tasks|length 0 %}
<h6 class="text-center">No tasks Records Found</h6>
{% endifequal %}
</div>
</div>
<br clear="all">
</div>
</div>
</form>
</div>
<div class="overview_form_block row marl justify-content-center" id="loading_spinner"
style="z-index: 10; left: 0; right: 0; vertical-align: middle; position: absolute; margin-top: 15%;">
<div class="spinner-border text-primary" style="width: 5em; height: 5em;" role="status">
<span class="sr-only">Processing File...</span>
</div>
</div>
</div>
</div>
<!-- ACCOUNTS VIEW ENDS -->
{% endblock content %}
{% block js_block %}
<script type="text/javascript">
$("#comment_form").submit(function(e){
$('#loading_spinner').hide()
$("#comment_form").submit(function (e) {
e.preventDefault()
var formData = new FormData($("#comment_form")[0]);
$.ajax({
url : "{% url 'accounts:add_comment' %}",
type : "POST",
data : formData,
url: "{% url 'accounts:add_comment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function(data){
if(data.error){
success: function (data) {
if (data.error) {
$("#CommentError").html(data.error).show()
} else {
d = new Date(data.commented_on);
$("#comments_div").prepend("<li class='list-group-item list-row' id='comment"+data.comment_id+"'>"+
"<div class='float-right right-container'>"+
"<div class='list-row-buttons btn-group float-right'>"+
"<button class='btn primary_btn btn-sm dropdown-toggle' data-toggle='dropdown' type='button'><span class='caret'></span>Actions</button>"+
"<ul class='dropdown-menu text-center'>"+
"<li><a class='action' onclick='edit_comment("+data.comment_id+")'>Edit</a></li>"+
"<li><a class='action' onclick='remove_comment("+data.comment_id+")''>Remove</a></li></ul></div></div>"+
"<div class='stream-post-container' id='comment_name"+data.comment_id+"'><pre>"+data.comment+"</pre></div>"+
"<div class='stream-container'><pre class='float-left'>"+data.commented_by+"</pre><pre class='float-right'>"+d.toLocaleString('en-US', { hour12: true })+"</pre></div>"
)
$("#comments_div").prepend("<li class='list-group-item list-row' id='comment" + data.comment_id + "'>" +
"<div class='float-right right-container'>" +
"<div class='list-row-buttons btn-group float-right'>" +
"<button class='btn primary_btn btn-sm dropdown-toggle' data-toggle='dropdown' type='button'><span class='caret'></span>Actions</button>" +
"<ul class='dropdown-menu text-center'>" +
"<li><a class='action' onclick='edit_comment(" + data.comment_id + ")'>Edit</a></li>" +
"<li><a class='action' onclick='remove_comment(" + data.comment_id + ")''>Remove</a></li></ul></div></div>" +
"<div class='stream-post-container' id='comment_name" + data.comment_id + "'><pre>" + data.comment + "</pre></div>" +
"<div class='stream-container'><pre class='float-left'>" + data.commented_by + "</pre><pre class='float-right'>" + d.toLocaleString('en-US', { hour12: true }) + "</pre></div>"
)
$("#id_comments").val("")
alert("Comment Submitted")
$("#CommentError").html("")
@ -441,7 +563,7 @@
$("#id_editcomment").val(comment)
$("#edit_file_field").html($("#comment_file_div" + x).clone())
}
$("#comment_edit").click(function (e) {
e.preventDefault()
var formData = new FormData($("#comment_edit_form")[0]);
@ -465,16 +587,16 @@
}
})
})
/*Comment Edit Js Ends*/
function HideError(e) {
$("#CommentError").hide()
$("#CommentEditError").hide()
}
/*Comment Remove Js Start*/
function remove_comment(x) {
var con = confirm("Do you want to Delete it for Sure!?")
if (con == true) {
@ -504,7 +626,7 @@
})
}
}
$("#attachment_form").submit(function (e) {
e.preventDefault()
const formData = new FormData($("#attachment_form")[0]);
@ -515,27 +637,49 @@
cache: false,
contentType: false,
processData: false,
beforeSend: function(){
$('#loading_spinner').show()
$('#opacity_block').addClass('opacity_block')
},
success: function (data) {
if (data.error) {
$("#AttachmentError").html(data.error).show()
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
} else {
d = new Date(data.created_on)
$("#attachment_div").prepend(
"<li class='list-group-item list-row' id='attachment" + data.attachment_id + "'>" +
"<div class='float-right right-container'><div class='list-row-buttons btn-group float-right'><button class='action btn primary_btn' onclick='remove_attachment("+data.attachment_id+")''>Remove</button></div></div>" +
"<div class='stream-post-container' id='attachment_name"+data.attachment_id+"'>"+
"<pre><span class='icon'><i style='font-size:1.5rem;' class='"+data.attachment_display+"'></i>&nbsp;&nbsp;</span><a href='"+data.download_url+"'>Download</a></pre></div>"+
"<div class='stream-container'><pre class='float-left'>"+data.created_by+"</pre><pre class='float-right date'>"+d.toLocaleString('en-US', { hour12: true })+"</pre></div>" +
"</li>"
"<li class='list-group-item list-row' id='attachment" + data.attachment_id + "'>" +
"<div class='float-right right-container'><div class='list-row-buttons btn-group float-right'><button class='action btn primary_btn' onclick='remove_attachment(" + data.attachment_id + ")''>Remove</button></div></div>" +
"<div class='stream-post-container' id='attachment_name" + data.attachment_id + "'>" +
`${(data.file_type[0]).startsWith('image') ? `<img src=${data.attachment_url} width="80px" height="80px" class="mb-2">` : ''}` +
"<pre><span class='icon'><i style='font-size:1.5rem;' class='" + data.attachment_display + "'></i>&nbsp;&nbsp;</span>" + data.attachment + "&nbsp; &nbsp;<a href='" + data.download_url + "'>Download</a></pre></div>" +
"<div class='stream-container'><pre class='float-left'>" + data.created_by + "</pre><pre class='float-right date'>" + d.toLocaleString('en-US', { hour12: true }) + "</pre></div>" +
"</li>"
)
$("#AttachmentError").html("")
$("#id_attachments").val("")
alert("Attachment Saved")
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
setTimeout(() => {
alert("Attachment Saved")
}, 100);
}
}
})
});
</script>
<script src="{% static 'js/bootstrap-suggest.js' %}"></script>
<script>
$('.mentions').suggest('@', {
data: {{ users_mention| safe}},
map: function (user) {
return {
value: user.username,
text: '<strong>' + user.username + '</strong>'
}
}
})
</script>
<script src="{% static 'js/mention.js' %}"></script>
{% endblock js_block %}

View file

@ -112,7 +112,7 @@ class AccountsListTestCase(AccountCreateTest, TestCase):
def test_accounts_list(self):
self.accounts = Account.objects.all()
response = self.client.get('/accounts/list/')
response = self.client.get(reverse('accounts:list'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'accounts.html')
@ -125,7 +125,7 @@ class AccountsListTestCase(AccountCreateTest, TestCase):
'billing_state': "billing_state",
'billing_postcode': "billing_postcode",
'billing_country': "billing_country"}
response = self.client.post('/accounts/list/', data)
response = self.client.post(reverse('accounts:list'), data)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'accounts.html')
@ -152,7 +152,7 @@ class AccountsRemoveTestCase(AccountCreateTest, TestCase):
def test_accounts_remove(self):
response = self.client.get(
'/accounts/' + str(self.account.id) + '/delete/')
self.assertEqual(response['location'], '/accounts/list/')
self.assertEqual(response['location'], '/accounts/')
# def test_accounts_remove_status(self):
# Account.objects.filter(id=self.account.id).delete()

View file

@ -2,13 +2,13 @@ from django.urls import path
from accounts.views import (
AccountsListView, CreateAccountView, AccountDetailView, AccountUpdateView,
AccountDeleteView, AddCommentView, UpdateCommentView, DeleteCommentView,
AddAttachmentView, DeleteAttachmentsView
AddAttachmentView, DeleteAttachmentsView, create_mail
)
app_name = 'accounts'
urlpatterns = [
path('list/', AccountsListView.as_view(), name='list'),
path('', AccountsListView.as_view(), name='list'),
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"),
@ -23,4 +23,6 @@ urlpatterns = [
name="add_attachment"),
path('attachment/remove/', DeleteAttachmentsView.as_view(),
name="remove_attachment"),
path('create-mail/<int:account_id>/', create_mail,
name="create_mail"),
]

View file

@ -1,11 +1,11 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import get_object_or_404, redirect, render
from django.views.generic import (
CreateView, UpdateView, DetailView, TemplateView, View, DeleteView)
CreateView, UpdateView, DetailView, TemplateView, View, DeleteView, FormView)
from accounts.forms import AccountForm, AccountCommentForm, \
AccountAttachmentForm
from accounts.models import Account, Tags
AccountAttachmentForm, EmailForm
from accounts.models import Account, Tags, Email
from common.models import User, Comment, Attachments
from common.utils import INDCHOICES, COUNTRIES, \
CURRENCY_CODES, CASE_TYPE, PRIORITY_CHOICE, STATUS_CHOICE
@ -16,6 +16,10 @@ from django.urls import reverse_lazy, reverse
from leads.models import Lead
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.contrib.auth.decorators import login_required
from accounts.tasks import send_email
from common.tasks import send_email_user_mentions
from django.contrib.sites.shortcuts import get_current_site
class AccountsListView(LoginRequiredMixin, TemplateView):
@ -28,6 +32,9 @@ class AccountsListView(LoginRequiredMixin, TemplateView):
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
queryset = queryset.filter(created_by=self.request.user.id)
if self.request.GET.get('tag', None):
queryset = queryset.filter(tags__in = self.request.GET.getlist('tag'))
request_post = self.request.POST
if request_post:
if request_post.get('name'):
@ -40,9 +47,9 @@ class AccountsListView(LoginRequiredMixin, TemplateView):
queryset = queryset.filter(
industry__icontains=request_post.get('industry'))
if request_post.get('tag'):
queryset = queryset.filter(tags__in=request_post.get('tag'))
queryset = queryset.filter(tags__in=request_post.getlist('tag'))
return queryset
return queryset.distinct()
def get_context_data(self, **kwargs):
context = super(AccountsListView, self).get_context_data(**kwargs)
@ -57,6 +64,12 @@ class AccountsListView(LoginRequiredMixin, TemplateView):
context["per_page"] = self.request.POST.get('per_page')
tag_ids = list(set(Account.objects.values_list('tags', flat=True)))
context["tags"] = Tags.objects.filter(id__in=tag_ids)
if self.request.POST.get('tag', None):
context["request_tags"] = self.request.POST.getlist('tag')
elif self.request.GET.get('tag', None):
context["request_tags"] = self.request.GET.getlist('tag')
else:
context["request_tags"] = None
search = False
if (
@ -88,15 +101,21 @@ class CreateAccountView(LoginRequiredMixin, CreateView):
template_name = "create_account.html"
def dispatch(self, request, *args, **kwargs):
self.users = User.objects.filter(is_active=True).order_by('email')
if self.request.user.role == 'ADMIN' or self.request.user.is_superuser:
self.users = User.objects.filter(is_active=True).order_by('email')
elif request.user.google.all():
self.users = []
else:
self.users = User.objects.filter(role='ADMIN').order_by('email')
return super(
CreateAccountView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(CreateAccountView, self).get_form_kwargs()
kwargs.update({"account": True})
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
kwargs.update({"request_user": self.request.user})
kwargs.update({"request_user": self.request.user})
# if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
# kwargs.update({"request_user": self.request.user})
return kwargs
def post(self, request, *args, **kwargs):
@ -125,6 +144,8 @@ class CreateAccountView(LoginRequiredMixin, CreateView):
account_object.tags.add(tag)
if self.request.POST.getlist('contacts', []):
account_object.contacts.add(*self.request.POST.getlist('contacts'))
if self.request.POST.getlist('assigned_to', []):
account_object.assigned_to.add(*self.request.POST.getlist('assigned_to'))
if self.request.FILES.get('account_attachment'):
attachment = Attachments()
attachment.created_by = self.request.user
@ -159,8 +180,13 @@ class CreateAccountView(LoginRequiredMixin, CreateView):
context["industries"] = INDCHOICES
context["countries"] = COUNTRIES
context["contact_count"] = Contact.objects.count()
context["leads"] = Lead.objects.exclude(
status__in=['converted', 'closed'])
if self.request.user.role == 'ADMIN':
context["leads"] = Lead.objects.exclude(
status__in=['converted', 'closed'])
else:
context["leads"] = Lead.objects.filter(
Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user)).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(
@ -185,6 +211,13 @@ class AccountDetailView(LoginRequiredMixin, DetailView):
self.request.user.is_superuser or self.request.user.role == 'ADMIN'
) else False
if self.request.user.is_superuser or self.request.user.role == 'ADMIN':
users_mention = list(User.objects.all().values('username'))
elif self.request.user != account_record.created_by:
users_mention = [{'username': account_record.created_by.username}]
else:
users_mention = []
context.update({
"comments": account_record.accounts_comments.all(),
"attachments": account_record.account_attachment.all(),
@ -201,6 +234,8 @@ class AccountDetailView(LoginRequiredMixin, DetailView):
"case_priority": PRIORITY_CHOICE,
"case_status": STATUS_CHOICE,
'comment_permission': comment_permission,
'tasks':account_record.accounts_tasks.all(),
'users_mention': users_mention,
})
return context
@ -211,15 +246,21 @@ class AccountUpdateView(LoginRequiredMixin, UpdateView):
template_name = "create_account.html"
def dispatch(self, request, *args, **kwargs):
self.users = User.objects.filter(is_active=True).order_by('email')
if self.request.user.role == 'ADMIN' or self.request.user.is_superuser:
self.users = User.objects.filter(is_active=True).order_by('email')
elif request.user.google.all():
self.users = []
else:
self.users = User.objects.filter(role='ADMIN').order_by('email')
return super(AccountUpdateView, self).dispatch(
request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(AccountUpdateView, self).get_form_kwargs()
kwargs.update({"account": True})
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
kwargs.update({"request_user": self.request.user})
kwargs.update({"request_user": self.request.user})
# if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
# kwargs.update({"request_user": self.request.user})
return kwargs
def post(self, request, *args, **kwargs):
@ -248,6 +289,11 @@ class AccountUpdateView(LoginRequiredMixin, UpdateView):
if self.request.POST.getlist('contacts', []):
account_object.contacts.clear()
account_object.contacts.add(*self.request.POST.getlist('contacts'))
if self.request.POST.getlist('assigned_to', []):
account_object.assigned_to.clear()
account_object.assigned_to.add(*self.request.POST.getlist('assigned_to'))
else:
account_object.assigned_to.clear()
if self.request.FILES.get('account_attachment'):
attachment = Attachments()
attachment.created_by = self.request.user
@ -282,8 +328,13 @@ class AccountUpdateView(LoginRequiredMixin, UpdateView):
context["industries"] = INDCHOICES
context["countries"] = COUNTRIES
context["contact_count"] = Contact.objects.count()
context["leads"] = Lead.objects.exclude(
status__in=['converted', 'closed'])
if self.request.user.role == 'ADMIN':
context["leads"] = Lead.objects.exclude(
status__in=['converted', 'closed'])
else:
context["leads"] = Lead.objects.filter(
Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user)).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(
@ -331,6 +382,11 @@ class AddCommentView(LoginRequiredMixin, CreateView):
comment.commented_by = self.request.user
comment.account = self.account
comment.save()
comment_id = comment.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'accounts', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"comment_id": comment.id, "comment": comment.comment,
"commented_on": comment.commented_on,
@ -360,6 +416,10 @@ class UpdateCommentView(LoginRequiredMixin, View):
def form_valid(self, form):
self.comment_obj.comment = form.cleaned_data.get("comment")
self.comment_obj.save(update_fields=["comment"])
comment_id = self.comment_obj.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'accounts', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"comment_id": self.comment_obj.id,
"comment": self.comment_obj.comment,
@ -422,7 +482,8 @@ class AddAttachmentView(LoginRequiredMixin, CreateView):
kwargs={'pk': attachment.id}),
"attachment_display": attachment.get_file_type_display(),
"created_on": attachment.created_on,
"created_by": attachment.created_by.email
"created_by": attachment.created_by.email,
"file_type": attachment.file_type()
})
def form_invalid(self, form):
@ -446,3 +507,23 @@ class DeleteAttachmentsView(LoginRequiredMixin, View):
data = {
'error': "You don't have permission to delete this attachment."}
return JsonResponse(data)
@login_required
def create_mail(request, account_id):
if request.method == 'GET':
account = get_object_or_404(Account, pk=account_id)
contacts_list = list(account.contacts.all().values('email'))
email_form = EmailForm()
return render(request, 'create_mail_accounts.html', {'account_id': account_id,
'contacts_list': contacts_list, 'email_form': email_form})
if request.method == 'POST':
form = EmailForm(request.POST)
if form.is_valid():
send_email.delay(form.data.get('message_subject'), form.data.get('message_body'),
from_email=account_id, recipients=form.data.get('recipients').split(','))
return JsonResponse({'error': False})
else:
return JsonResponse({'error': True, 'errors': form.errors})

View file

@ -14,10 +14,11 @@ class CaseForm(forms.ModelForm):
field.widget.attrs = {"class": "form-control"}
self.fields['description'].widget.attrs.update({
'rows': '6'})
self.fields['assigned_to'].queryset = assigned_users
if assigned_users:
self.fields['assigned_to'].queryset = assigned_users
self.fields['assigned_to'].required = False
self.fields['account'].queryset = case_accounts
self.fields['contacts'].queryset = case_contacts
self.fields['assigned_to'].required = False
self.fields['contacts'].required = False
for key, value in self.fields.items():
value.widget.attrs['placeholder'] = value.label

36
cases/tasks.py Normal file
View file

@ -0,0 +1,36 @@
from celery.task import task
from django.conf import settings
from django.core.mail import EmailMultiAlternatives, EmailMessage
from django.db.models import Q
from django.shortcuts import reverse
from django.template.loader import render_to_string
from accounts.models import User
from cases.models import Case
@task
def send_email_to_assigned_user(recipients, case_id, domain='demo.django-crm.io', protocol='http'):
""" Send Mail To Users When they are assigned to a case """
case = Case.objects.get(id=case_id)
for user in recipients:
recipients_list = []
user = User.objects.filter(id=user).first()
if user:
recipients_list.append(user.email)
context = {}
context["url"] = protocol + '://' + domain + \
reverse('cases:view_case', args=(case.id,))
context["user"] = user
context["case"] = case
subject = 'Assigned to case.'
html_content = render_to_string(
'assigned_to/cases_assigned.html', context=context)
msg = EmailMessage(
subject,
html_content,
to=recipients_list
)
msg.content_subtype = "html"
msg.send()

View file

@ -1,6 +1,15 @@
{% extends 'sales/base.html' %}
{% load staticfiles %}
{% load thumbnail %}
{% block content %}
{% block extralinks %}
<link rel="stylesheet" href="{% static 'css/bootstrap-suggest.css' %}">
<style>
.opacity_block {
opacity: 0.2;
}
</style>
{% endblock extralinks %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'cases:list' %}">Cases</a></li>
@ -10,16 +19,16 @@
<!-- CASE VIEW STARTS -->
<div class="main_container" id="maincontainer">
<div class="overview_form_block row marl justify-content-center">
<div class="col-md-8">
<div class="col-md-8" id="opacity_block">
<div class="card">
<div class="card-body" id="datashow">
<div class="card-title text-right">
<h5 >
<h5>
<span class="float-left title">Overview</span>
<span class="" style="margin-top: 0px">
<div class="dropdown buttons_row">
<button class="btn primary_btn dropdown-toggle" type="button" data-toggle="dropdown">Actions
<span class="caret"></span></button>
<span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="{% url 'cases:edit_case' case_record.id %}">Edit</a></li>
</ul>
@ -30,7 +39,7 @@
<div class="row marl">
<div class="col-md-4">
<div class="filter_col col-md-12" id="iname">
<div class="form-group" >
<div class="form-group">
<label class="case_field_label" for="id_name" data-name="name">Name</label>
<div class="case_field" id="case_name" data-name="name">{{ case_record.name }}</div>
</div>
@ -39,7 +48,7 @@
{% if case_record.account %}
<div class="form-group">
<label class="case_field_label" for="id_account" data-name="name">Account</label>
<div class="case_field" id ="case_account" data-name="name">{{ case_record.account }}</div>
<div class="case_field" id="case_account" data-name="name">{{ case_record.account }}</div>
</div>
{% endif %}
</div>
@ -49,7 +58,7 @@
<label class="case_field_label" for="id_contact" data-name="name">Contact</label>
<div class="case_field" id="case_contact" data-name="name">
{% for contact in case_record.contacts.all %}
<li>{{ contact }}</li>
<div>{{ contact }}</div>
{% endfor %}
</div>
</div>
@ -90,7 +99,7 @@
<div class="filter_col col-md-12">
{% if case_record.description %}
<div class="form-group">
<label class="case_field_label" for="id_description" data-name="name" >Description</label>
<label class="case_field_label" for="id_description" data-name="name">Description</label>
<div class="case_field" id="case_description" data-name="name">{{ case_record.description }}</div>
</div>
{% endif %}
@ -98,7 +107,7 @@
</div>
<div class="col-md-12">
<div class="created_information">
Created by <b>{{ case_record.created_by }}</b> created on <i><b>{{ case_record.created_on }}</b></i>
Created by <b>{{ case_record.created_by }}</b> created on <b>{{ case_record.created_on }}</b>
</div>
</div>
</div>
@ -113,10 +122,13 @@
<form id="attachment_form" method="POST" enctype="multipart/form-data">
<div class="form-group ">
<input type="file" class="form-control-file" cols="40" id="id_attachments" name="attachment">
<div><i id="AttachmentError" style="display:none; color:red"></i></div>
<div>
<p id="AttachmentError" style="display:none; color:red"></p>
</div>
<br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;" id="attachment_submit">Save</button>
<button class="btn btn-default save" style="text-align: center;"
id="attachment_submit">Save</button>
</div>
</div>
<input type="hidden" value="{{case_record.id}}" name="caseid">
@ -127,11 +139,19 @@
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == attachment.created_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="action btn primary_btn" onclick="remove_attachment({{attachment.id}})">Remove</button>
<button class="action btn primary_btn"
onclick="remove_attachment({{attachment.id}})">Remove</button>
</div>
</div>
{% endif %}
<div class="stream-post-container" id="attachment_name{{attachment.id}}">
{% if attachment.attachment %}
{% if 'image' in attachment.file_type %}
{% thumbnail attachment.attachment "80x80" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" class="mb-2">
{% endthumbnail %}
{% endif %}
{% endif %}
<pre> <span class="icon"><i style="font-size:1.5rem;" class="{{attachment.get_file_type_display}}"></i> </span>{{attachment.file_name}} <a href="{% url 'common:download_attachment' attachment.id %}">Download</a></pre>
</div>
<div class="stream-container">
@ -153,8 +173,11 @@
<div class="col-md-12">
<form id="comment_form" method="POST" enctype="multipart/form-data">
<div class="form-group">
<textarea class="form-control" textarea cols="40" rows="3" id="id_comments" name="comment" placeholder="Submit Your Comments Here"></textarea>
<div><i id="CommentError" style="display:none; color:red"></i></div>
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_comments" name="comment"
placeholder="Submit Your Comments Here"></textarea>
<div>
<p id="CommentError" style="display:none; color:red"></p>
</div>
</br>
<div class="buttons_row">
<button class="btn btn-default save" id="comment_submit" type="submit">Submit</button>
@ -168,7 +191,8 @@
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == comment.commented_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="btn primary_btn btn-sm dropdown-toggle" data-toggle="dropdown" type="button">Actions <span class="caret"></span></button>
<button class="btn primary_btn btn-sm dropdown-toggle" data-toggle="dropdown"
type="button">Actions <span class="caret"></span></button>
<ul class="dropdown-menu text-center">
<li>
<a class="action" onclick="edit_comment({{comment.id}})">Edit</a>
@ -199,10 +223,13 @@
<h4 class="modal-title">Update Your Comment</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</button>
</div>
<form id="comment_edit_form" method="POST" >
<form id="comment_edit_form" method="POST">
<div class="modal-body">
<div class="form-group">
<textarea class="form-control" textarea cols="40" rows="3" id="id_editcomment" name="comment" placeholder="Submit Your Comments" style="color:black;"></textarea>
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_editcomment"
name="comment" placeholder="Submit Your Comments" style="color:black;"></textarea><span>
<p id="CommentEditError" style="display:none; color:red"></p>
</span>
<input type="hidden" value="{{ case.id }}" name="caseid">
<input type="hidden" value="" name="commentid" id="commentid">
</div>
@ -218,6 +245,13 @@
<!-- Comments Section Ends-->
</div>
</div>
<div class="overview_form_block row marl justify-content-center" id="loading_spinner"
style="z-index: 10; left: 0; right: 0; vertical-align: middle; position: absolute; margin-top: 15%;">
<div class="spinner-border text-primary" style="width: 5em; height: 5em;" role="status">
<span class="sr-only">Processing File...</span>
</div>
</div>
</div>
</div>
<!-- CASE VIEW ENDS -->
@ -356,99 +390,123 @@
<script src="{% static 'js/cases/view_case.js' %}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<script type="text/javascript">
$('#loading_spinner').hide()
var assigned_to_list = "{{ assigned_data }}";
assigned_to_list = JSON.parse(assigned_to_list.replace(/&(l|g|quo)t;/g, function(a,b){
return {
l : '<',
g : '>',
quo : '"'
}[b];
}));
$('#remove_case').click(function(e){
assigned_to_list = JSON.parse(assigned_to_list.replace(/&(l|g|quo)t;/g, function (a, b) {
return {
l: '<',
g: '>',
quo: '"'
}[b];
}));
$('#remove_case').click(function (e) {
var result = confirm("Are You Sure You Want to delete?");
if (result==false) {
if (result == false) {
e.preventDefault()
}
});
</script>
<script type="text/javascript">
$(function () {
var date = new Date();
var today = new Date(date.getFullYear(), date.getMonth(), date.getDate());
var date = new Date();
var today = new Date(date.getFullYear(), date.getMonth(), date.getDate());
$('#id_closed_on').datetimepicker({
'format': 'YYYY-MM-DD',
minDate: today
'format': 'YYYY-MM-DD',
minDate: today
})
});
$(".savenew").on('click', function(e){
$(".savenew").on('click', function (e) {
e.preventDefault()
$("#save_new_form").val('true')
$('#submitForm').submit()
});
$(document).ready(function() {
$(document).ready(function () {
$('.assigned_users').select2();
});
$('#id_account').change(function(){
var account = $("#id_account").val()
$.get("{% url 'cases:select_contacts' %}", {"account":account}, function(data){
$("#id_contacts").html("")
$.each(data, function (index, value) {
$("#id_contacts").append("<option value="+index+">"+value+"</option>")
});
})
$('#id_account').change(function () {
var account = $("#id_account").val()
$.get("{% url 'cases:select_contacts' %}", { "account": account }, function (data) {
$("#id_contacts").html("")
$.each(data, function (index, value) {
$("#id_contacts").append("<option value=" + index + ">" + value + "</option>")
});
})
});
function remove_attachment(x) {
const con = confirm("Do you want to Delete it for Sure!?");
if (con == true) {
$.post('{% url "cases:remove_attachment" %}', {
"attachment_id": x
}, function (data) {
if (data.error) {
alert(data.errors)
} else {
$("#attachment" + data.attachment_object).remove()
const con = confirm("Do you want to Delete it for Sure!?");
if (con == true) {
$.post('{% url "cases:remove_attachment" %}', {
"attachment_id": x
}, function (data) {
if (data.error) {
alert(data.errors)
} else {
$("#attachment" + data.attachment_object).remove()
}
})
}
})
}
}
$("#attachment_form").submit(function (e) {
e.preventDefault()
const formData = new FormData($("#attachment_form")[0]);
$.ajax({
url: "{% url 'cases:add_attachment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#AttachmentError").html(data.errors).show()
} else {
d = new Date(data.created_on)
$("#attachment_div").prepend(
e.preventDefault()
const formData = new FormData($("#attachment_form")[0]);
$.ajax({
url: "{% url 'cases:add_attachment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
beforeSend: function () {
$('#loading_spinner').show()
$('#opacity_block').addClass('opacity_block')
},
success: function (data) {
if (data.error) {
$("#AttachmentError").html(data.errors).show()
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
} else {
d = new Date(data.created_on)
$("#attachment_div").prepend(
"<li class='list-group-item list-row' id='attachment" + data.attachment_id + "'>" +
"<div class='float-right right-container'><div class='list-row-buttons btn-group float-right'><button class='action btn primary_btn' onclick='remove_attachment("+data.attachment_id+")''>Remove</button></div></div>" +
"<div class='stream-post-container' id='attachment_name"+data.attachment_id+"'>"+
"<pre><span class='icon'><i style='font-size:1.5rem;' class='"+data.attachment_display+"'></i>&nbsp;&nbsp;</span><a href='"+data.download_url+"'>Download</a></pre></div>"+
"<div class='stream-container'><pre class='float-left'>"+data.created_by+"</pre><pre class='float-right date'>"+d.toLocaleString('en-US', { hour12: true })+"</pre></div>" +
"<div class='float-right right-container'><div class='list-row-buttons btn-group float-right'><button class='action btn primary_btn' onclick='remove_attachment(" + data.attachment_id + ")''>Remove</button></div></div>" +
"<div class='stream-post-container' id='attachment_name" + data.attachment_id + "'>" +
`${(data.file_type[0]).startsWith('image') ? `<img src=${data.attachment_url} width="80px" height="80px" class="mb-2">` : ''}` +
"<pre><span class='icon'><i style='font-size:1.5rem;' class='" + data.attachment_display + "'></i>&nbsp;&nbsp;</span>" + data.attachment + "&nbsp; &nbsp;<a href='" + data.download_url + "'>Download</a></pre></div>" +
"<div class='stream-container'><pre class='float-left'>" + data.created_by + "</pre><pre class='float-right date'>" + d.toLocaleString('en-US', { hour12: true }) + "</pre></div>" +
"</li>"
)
$("#AttachmentError").html("")
alert("Attachment Saved")
$("#id_attachments").val("")
}
}
)
$("#AttachmentError").html("")
// alert("Attachment Saved")
$("#id_attachments").val("")
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
setTimeout(() => {
alert("Attachment Saved")
}, 100)
}
}
});
});
});
</script>
<script src="{% static 'js/bootstrap-suggest.js' %}"></script>
<script>
$('.mentions').suggest('@', {
data: {{ users_mention| safe}},
map: function (user) {
return {
value: user.username,
text: '<strong>' + user.username + '</strong>'
}
}
})
</script>
<script src="{% static 'js/mention.js' %}"></script>
{% endblock js_block %}

View file

@ -77,7 +77,7 @@ class CaseViewTestCase(CaseCreation, TestCase):
def test_list_cases(self):
self.cases = Case.objects.all()
response = self.client.get('/cases/list/')
response = self.client.get(reverse('cases:list'))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['cases'][0].id, self.case.id)
self.assertTrue(response.context['cases'])
@ -89,7 +89,7 @@ class CaseViewTestCase(CaseCreation, TestCase):
'status': 'status',
'priority': 'prioty',
'account': int(self.account.id)}
response = self.client.post('/cases/list/', data)
response = self.client.post(reverse('cases:list'), data)
self.assertEqual(response.status_code, 200)
@ -144,7 +144,7 @@ class CaseRemoveTestCase(CaseCreation, TestCase):
def test_case_deletion_show_case(self):
response = self.client.get('/cases/' + str(self.case.id) + '/remove/')
self.assertEqual(response['location'], '/cases/list/')
self.assertEqual(response['location'], '/cases/')
def test_case_delete(self):
response = self.client.post(

View file

@ -8,7 +8,7 @@ from cases.views import (
app_name = 'cases'
urlpatterns = [
path('list/', CasesListView.as_view(), name='list'),
path('', CasesListView.as_view(), name='list'),
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"),

View file

@ -17,6 +17,8 @@ from contacts.models import Contact
from common.utils import PRIORITY_CHOICE, STATUS_CHOICE, CASE_TYPE
from django.urls import reverse
from django.core.exceptions import PermissionDenied
from common.tasks import send_email_user_mentions
from cases.tasks import send_email_to_assigned_user
class CasesListView(LoginRequiredMixin, TemplateView):
@ -76,7 +78,13 @@ class CasesListView(LoginRequiredMixin, TemplateView):
def create_case(request):
users = User.objects.filter(is_active=True).order_by('email')
users = []
if request.user.role == 'ADMIN' or request.user.is_superuser:
users = User.objects.filter(is_active=True).order_by('email')
elif request.user.google.all():
users = []
else:
users = User.objects.filter(role='ADMIN').order_by('email')
accounts = Account.objects.filter(status="open")
contacts = Contact.objects.all()
if request.user.role != "ADMIN" and not request.user.is_superuser:
@ -99,20 +107,23 @@ def create_case(request):
case.assigned_to.add(*request.POST.getlist('assigned_to'))
assigned_to_list = request.POST.getlist('assigned_to')
current_site = get_current_site(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])
email.content_subtype = "html"
email.send()
recipients = assigned_to_list
send_email_to_assigned_user.delay(recipients, case.id, domain=current_site.domain,
protocol=request.scheme)
# 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])
# email.content_subtype = "html"
# email.send()
if request.POST.getlist('contacts', []):
case.contacts.add(*request.POST.getlist('contacts'))
@ -176,8 +187,16 @@ class CaseDetailView(LoginRequiredMixin, DetailView):
assigned_dict['name'] = each.email
assigned_data.append(assigned_dict)
if self.request.user.is_superuser or self.request.user.role == 'ADMIN':
users_mention = list(User.objects.all().values('username'))
elif self.request.user != context['object'].created_by:
users_mention = [{'username': context['object'].created_by.username}]
else:
users_mention = list(context['object'].assigned_to.all().values('username'))
context.update({"comments": context["case_record"].cases.all(),
"attachments": context['case_record'].case_attachment.all(),
"users_mention": users_mention,
"assigned_data": json.dumps(assigned_data)})
return context
@ -191,8 +210,15 @@ def update_case(request, pk):
created_by=request.user)
contacts = Contact.objects.filter(
Q(assigned_to__in=[request.user]) | Q(created_by=request.user))
users = []
if request.user.role == 'ADMIN' or request.user.is_superuser:
users = User.objects.filter(is_active=True).order_by('email')
elif request.user.google.all():
users = []
else:
users = User.objects.filter(role='ADMIN').order_by('email')
kwargs_data = {
"assigned_to": User.objects.filter(is_active=True).order_by('email'),
"assigned_to": users,
"account": accounts, "contacts": contacts}
form = CaseForm(instance=case_object, **kwargs_data)
@ -215,22 +241,24 @@ def update_case(request, pk):
all_members_list = 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 case.'
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"
email.send()
recipients = all_members_list
send_email_to_assigned_user.delay(recipients, case_obj.id, domain=current_site.domain,
protocol=request.scheme)
# 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
# })
# email = EmailMessage(
# mail_subject, message, to=[user.email])
# email.content_subtype = "html"
# email.send()
case_obj.assigned_to.clear()
case_obj.assigned_to.add(*request.POST.getlist('assigned_to'))
@ -379,6 +407,10 @@ class AddCommentView(LoginRequiredMixin, CreateView):
comment.commented_by = self.request.user
comment.case = self.case
comment.save()
comment_id = comment.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'cases', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"comment_id": comment.id, "comment": comment.comment,
"commented_on": comment.commented_on,
@ -412,6 +444,10 @@ class UpdateCommentView(LoginRequiredMixin, View):
def form_valid(self, form):
self.comment_obj.comment = form.cleaned_data.get("comment")
self.comment_obj.save(update_fields=["comment"])
comment_id = self.comment_obj.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'cases', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"comment_id": self.comment_obj.id,
"comment": self.comment_obj.comment,
@ -483,6 +519,7 @@ class AddAttachmentView(LoginRequiredMixin, CreateView):
"created_by": attachment.created_by.email,
"message": "attachment Created",
"attachment_display": attachment.get_file_type_display(),
"file_type": attachment.file_type(),
"error": False
})

Binary file not shown.

View file

@ -3,6 +3,7 @@ from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.forms import PasswordResetForm
from common.models import Address, User, Document, Comment, APISettings
from django.contrib.auth import password_validation
class BillingAddressForm(forms.ModelForm):
@ -154,20 +155,22 @@ class LoginForm(forms.ModelForm):
class ChangePasswordForm(forms.Form):
CurrentPassword = forms.CharField(max_length=100)
# CurrentPassword = forms.CharField(max_length=100)
Newpassword = forms.CharField(max_length=100)
confirm = forms.CharField(max_length=100)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(ChangePasswordForm, self).__init__(*args, **kwargs)
def clean_confirm(self):
if len(self.data.get('confirm')) < 4:
raise forms.ValidationError(
'Password must be at least 4 characters long!')
# if len(self.data.get('confirm')) < 4:
# raise forms.ValidationError(
# 'Password must be at least 4 characters long!')
if self.data.get('confirm') != self.cleaned_data.get('Newpassword'):
raise forms.ValidationError(
'Confirm password do not match with new password')
password_validation.validate_password(self.cleaned_data.get('Newpassword'), user=self.user)
return self.data.get('confirm')
@ -184,6 +187,7 @@ class PasswordResetEmailForm(PasswordResetForm):
class DocumentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.instance = kwargs.get('instance', None)
users = kwargs.pop('users', [])
super(DocumentForm, self).__init__(*args, **kwargs)
@ -194,13 +198,27 @@ class DocumentForm(forms.ModelForm):
(each[0], each[1]) for each in Document.DOCUMENT_STATUS_CHOICE]
self.fields['status'].required = False
self.fields['title'].required = True
self.fields['shared_to'].queryset = users
if users:
self.fields['shared_to'].queryset = users
self.fields['shared_to'].required = False
class Meta:
model = Document
fields = ['title', 'document_file', 'status', 'shared_to']
def clean_title(self):
title = self.cleaned_data.get('title')
if not self.instance:
if Document.objects.filter(title=title).exists():
raise forms.ValidationError(
'Document with this Title already exists')
return title
if Document.objects.filter(title=title).exclude(id=self.instance.id).exists():
raise forms.ValidationError(
'Document with this Title already exists')
return title
return title
class UserCommentForm(forms.ModelForm):
comment = forms.CharField(max_length=64, required=True)

View file

@ -0,0 +1,25 @@
# Generated by Django 2.1.7 on 2019-05-08 10:10
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tasks', '0002_task_created_by'),
('common', '0012_apisettings_website'),
]
operations = [
migrations.AddField(
model_name='attachments',
name='task',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tasks_attachment', to='tasks.Task'),
),
migrations.AddField(
model_name='comment',
name='task',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tasks_comments', to='tasks.Task'),
),
]

View file

@ -0,0 +1,25 @@
# Generated by Django 2.1.7 on 2019-05-24 05:43
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('invoices', '0002_auto_20190524_1113'),
('common', '0013_auto_20190508_1540'),
]
operations = [
migrations.AddField(
model_name='attachments',
name='invoice',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='invoice_attachment', to='invoices.Invoice'),
),
migrations.AddField(
model_name='comment',
name='invoice',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='invoice_comments', to='invoices.Invoice'),
),
]

View file

@ -0,0 +1,25 @@
# Generated by Django 2.1.7 on 2019-06-04 13:00
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('events', '0002_event_date_of_meeting'),
('common', '0014_auto_20190524_1113'),
]
operations = [
migrations.AddField(
model_name='attachments',
name='event',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='events_attachment', to='events.Event'),
),
migrations.AddField(
model_name='comment',
name='event',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='events_comments', to='events.Event'),
),
]

View file

@ -12,6 +12,7 @@ from common.templatetags.common_tags import (
is_document_file_sheet, is_document_file_zip
)
from common.utils import COUNTRIES, ROLES
from phonenumber_field.modelfields import PhoneNumberField
def img_url(self, filename):
@ -121,7 +122,7 @@ class Comment(models.Model):
on_delete=models.CASCADE)
lead = models.ForeignKey('leads.Lead',
blank=True, null=True,
related_name="leads",
related_name="leads_comments",
on_delete=models.CASCADE)
opportunity = models.ForeignKey(
'opportunity.Opportunity', blank=True,
@ -136,6 +137,15 @@ class Comment(models.Model):
related_name="user_comments",
on_delete=models.CASCADE)
task = models.ForeignKey('tasks.Task', blank=True, null=True,
related_name='tasks_comments', on_delete=models.CASCADE)
invoice = models.ForeignKey('invoices.Invoice', blank=True, null=True,
related_name='invoice_comments', on_delete=models.CASCADE)
event = models.ForeignKey('events.Event', blank=True, null=True,
related_name='events_comments', on_delete=models.CASCADE)
def get_files(self):
return Comment_Files.objects.filter(comment_id=self)
@ -180,6 +190,14 @@ class Attachments(models.Model):
'cases.Case', blank=True, null=True,
on_delete=models.CASCADE, related_name='case_attachment')
task = models.ForeignKey('tasks.Task', blank=True, null=True,
related_name='tasks_attachment', on_delete=models.CASCADE)
invoice = models.ForeignKey('invoices.Invoice', blank=True, null=True,
related_name='invoice_attachment', on_delete=models.CASCADE)
event = models.ForeignKey('events.Event', blank=True, null=True,
related_name='events_attachment', on_delete=models.CASCADE)
def file_type(self):
name_ext_list = self.attachment.url.split(".")
if (len(name_ext_list) > 1):
@ -298,3 +316,32 @@ class Google(models.Model):
def __str__(self):
return self.email
# # Need to be reviewed
# class Task(models.Model):
# # assigned_to can be assigned to contact, oppurtunity, case
# assigned_to = models.ManyToManyField(User, related_name='task_assigned_to')
# status = models.CharField(max_length=50, choices=)
# subject = models.CharField(max_length=200, default='')
# name = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='user_task')
# due_date = models.DateField(auto_now=False, auto_now_add=False)
# # this could be choice field
# related_to = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='to_account')
# # related_to = models.CharField(max_length=50, choices=)
# priority = models.CharField(max_length=50, , choices=)
# comments = models.TextField()
# recurrence = models.BooleanField()
# # optional fields
# status = models.CharField(max_length=50, choices=)
# # type_task = models.CharField(max_length=50)
# # phone = models.phonenumber_field(null=True)
# # email = models.EmailField(max_length=254)
# send_notification_mail = models.BooleanField(default=False)
# reminder_field = models.DateTimeField(auto_now=False, auto_now_add=False)

129
common/tasks.py Normal file
View file

@ -0,0 +1,129 @@
from celery.task import task
from django.core.mail import EmailMessage
from django.shortcuts import reverse
from django.template.loader import render_to_string
from common.models import Comment, User
@task
def send_email_to_new_user(user_email, created_by, domain='demo.django-crm.io', protocol='http'):
""" Send Mail To Users When their account is created """
if user_email:
context = {}
context["user_email"] = user_email
context["created_by"] = created_by
context["url"] = protocol + '://' + domain + '/'
recipients = []
recipients.append(user_email)
subject = 'Created account in CRM'
html_content = render_to_string('user_status_in.html', context=context)
if recipients:
msg = EmailMessage(
subject,
html_content,
to=recipients
)
msg.content_subtype = "html"
msg.send()
@task
def send_email_user_mentions(comment_id, called_from, domain='demo.django-crm.io', protocol='http'):
""" Send Mail To Mentioned Users In The Comment """
comment = Comment.objects.filter(id=comment_id).first()
if comment:
comment_text = comment.comment
comment_text_list = comment_text.split()
recipients = []
for comment_text in comment_text_list:
if comment_text.startswith('@'):
if comment_text.strip('@').strip(',') not in recipients:
if User.objects.filter(username=comment_text.strip('@').strip(',')).exists():
email = User.objects.filter(
username=comment_text.strip('@').strip(',')).first().email
recipients.append(email)
context = {}
context["name"] = comment.commented_by
context["comment_description"] = comment.comment
if called_from == 'accounts':
context["url"] = protocol + '://' + domain + \
reverse('accounts:view_account', args=(comment.account.id,))
elif called_from == 'contacts':
context["url"] = protocol + '://' + domain + \
reverse('contacts:view_contact', args=(comment.contact.id,))
elif called_from == 'leads':
context["url"] = protocol + '://' + domain + \
reverse('leads:view_lead', args=(comment.lead.id,))
elif called_from == 'opportunity':
context["url"] = protocol + '://' + domain + \
reverse('opportunity:opp_view', args=(comment.opportunity.id,))
elif called_from == 'cases':
context["url"] = protocol + '://' + domain + \
reverse('cases:view_case', args=(comment.case.id,))
elif called_from == 'tasks':
context["url"] = protocol + '://' + domain + \
reverse('tasks:task_detail', args=(comment.task.id,))
elif called_from == 'invoices':
context["url"] = protocol + '://' + domain + \
reverse('invoices:invoice_details', args=(comment.invoice.id,))
elif called_from == 'events':
context["url"] = protocol + '://' + domain + \
reverse('events:detail_view', args=(comment.event.id,))
else:
context["url"] = ''
html_content = render_to_string('comment_email.html', context=context)
subject = 'Django CRM : comment '
if recipients:
msg = EmailMessage(
subject,
html_content,
from_email=comment.commented_by.email,
to=recipients
)
msg.content_subtype = "html"
msg.send()
@task
def send_email_user_status(user_id, domain='demo.django-crm.io', protocol='http'):
""" Send Mail To Users Regarding their status i.e active or inactive """
user = User.objects.filter(id=user_id).first()
if user:
context = {}
context["message"] = 'deactivated'
if user.is_active:
context["url"] = protocol + '://' + domain + '/'
context["message"] = 'activated'
html_content = render_to_string('user_status.html', context=context)
subject = 'CRM : User Status '
recipients = []
recipients.append(user.email)
if recipients:
msg = EmailMessage(
subject,
html_content,
to=recipients
)
msg.content_subtype = "html"
msg.send()
@task
def send_email_user_delete(user_email, domain='demo.django-crm.io', protocol='http'):
""" Send Mail To Users When their account is deleted """
if user_email:
context = {}
context["message"] = 'deleted'
recipients = []
recipients.append(user_email)
subject = 'CRM : User Deleted. '
html_content = render_to_string('user_status.html', context=context)
if recipients:
msg = EmailMessage(
subject,
html_content,
to=recipients
)
msg.content_subtype = "html"
msg.send()

View file

@ -0,0 +1,18 @@
{% extends 'root_email_template.html' %}
{% block heading %}
{{name}} mentioned you in a comment
{% endblock heading %}
{% block content_body %}
{{comment_description}}
{% endblock content_body %}
{% block button_link %}
{% if url %}
<div style="margin-bottom:20px">
<a href="{{url}}"
style="display:inline-block;width:170px;background:#38abdd;padding:10px;text-align:center;color:#fff;font-size:1rem;font-weight:600;margin:0px auto;margin-bottom:20px;border-radius:5px;text-decoration:none;display:block">Click here</a>
</div>
{% endif %}
{% endblock button_link %}

View file

@ -122,12 +122,14 @@
success: function(data) {
if(data.error){
$('.error').html('')
$('.progress').hide()
for (var key in data.errors) {
$('#id_'+key).html("<p>" + data.errors[key][0] + "</p>");
};
$('#submit_btn').removeAttr('disabled')
}
else{
$('.progress').show()
window.location = data.success_url;
}
}

View file

@ -7,11 +7,11 @@
<!-- filter_row starts here -->
<div class="row marl">
<div class="col-lg-12 text-right">
<span class="d-inline"><a href="{% url 'common:create_user'%}" class="btn primary_btn"><i class="fa fa-plus"></i> Add New User</a></span>
<span class="d-inline"><a href="{% url 'common:create_user'%}" class="btn primary_btn"><i class="fa fa-plus"></i>
Add New User</a></span>
</div>
</div>
<!-- filter_row ends here -->
<!-- popup for change password start's here -->
<!-- modal begins -->
<div class="modal fade" id="pass_change_div" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
@ -20,87 +20,253 @@
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body" style="padding:20px 50px;">
<form role="form" id="chang_password_form" method="POST" action="{% url 'common:change_passsword_by_admin' %}">
<form role="form" id="chang_password_form" method="POST"
action="{% url 'common:change_passsword_by_admin' %}">
<div class="form-group">
<label for="usrname"><span class="glyphicon glyphicon-user"></span> New Password</label>
<input type="password" class="form-control" name="new_passwoord" id="new_passwoord" placeholder="New Password">
<p id="password_eror"style="color:red"></p>
<input type="password" class="form-control" name="new_passwoord" id="new_passwoord"
placeholder="New Password">
<p id="password_eror" style="color:red"></p>
<input type="hidden" id="useer_id" name="useer_id">
</div>
<button id="submit_password" type="submit" class="btn btn-success btn-block"><span class="glyphicon glyphicon-off"></span> Change</button>
<button id="submit_password" type="submit" class="btn btn-success btn-block"><span
class="glyphicon glyphicon-off"></span> Change</button>
</form>
</div>
</div>
</div>
</div>
<!-- popup for change password end's here -->
<div class="table_container_row row marl">
<!-- modal ends -->
<div class="filter_row list_filter_row row marl">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<div class="card-title list text-right">
<span class="float-left">Users - {% if show_pageitems %} {% show_pageitems %}{% else %} {{ users|length }}{% endif %}</span>
<span class="filter_toggle">
<a style="padding:0px 10px;" href="#" class="primary_btn"></a>
</span>
</div>
<div class="table-responsive ">
<table class="table table-bordered table-striped table-condensed">
<thead>
{% if users|length > 0 %}
<tr>
<th width="20%">Username</th>
<th width="20%">Email Address</th>
<th width="8%">User Role</th>
<th width="15%">Status</th>
<th width="20%">Actions</th>
</tr>
{% endif %}
</thead>
<tbody>
{% if per_page %}
{% paginate per_page users %}
{% else %}
{% paginate 10 users %}
{% endif %}
{% for user in users %}
<tr class="text-center">
<td><a href="{% url 'common:view_user' user.id %}">{{ user.username }}</a></td>
<td>{{ user.email }}</td>
<td class="user_role">{% if user.is_superuser %} Admin {% else %} {{user.role}} {% endif %}</td>
<td>
{% ifnotequal request.user user %}
<a {% if user.is_active %} onclick="return confirm('Are you sure you want to deactivate this user?')" {% else %} onclick="return confirm('Are you sure you want to activate this user?')" {% endif %} href="{% url 'common:change_user_status' pk=user.id %}" class="on_off" style="color: #454545; text-decoration: none;">
{% if user.is_active == True %}
<i class="fa fa-toggle-on"></i> Active
{% else %}
<i class="fa fa-toggle-off"></i> InActive
{% endif %}
</a>
{% else %}
Active
{% endifnotequal %}
</td>
<td class="actions">
<!-- <a href="{% url 'common:view_user' user.id %}" class="btn btn-info view"><i class="fas fa-eye"></i>View</a> -->
{% if user.email != admin_email or request.user.email == admin_email %}
<a href="{% url 'common:edit_user' user.id %}" class="btn btn-success edit action"><i class="fas fa-pencil-alt"></i>Edit</a>
{% endif %}
<a href="{% url 'common:remove_user' user.id %}" class="btn btn-danger trash action" {% if request.user.email == user.email %} style="visibility: hidden;" {% endif %}><i class="fas fa-trash-alt"></i>Delete</a>
{% if user.email != admin_email and request.user != user %}
<a style="color:black;margin-bottom: -11px;" data-id="{{user.id}}" href="" class="fa-passwd-reset fa-stack" ><i class="fa fa-undo fa-stack-2x"></i><i class="fa fa-lock fa-stack-1x"></i></a>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% ifequal users|length 0 %}
<h6 class="text-center">No User Records Found</h6>
{% endifequal %}
<div class="marl row text-center">
{% show_pages %}
<form id="users_filter" method="POST" action="">
<div class="card-body">
<div class="card-title">Filters</div>
<div class="row marl">
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Name</label>
<input type="text" class="form-control" placeholder="Username" name="username" {% if request.POST %}
value="{{request.POST.username}}" {%endif%} />
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Email</label>
<input type="text" class="form-control" placeholder="email" name="email" {% if request.POST %}
value="{{request.POST.email}}" {%endif%} />
</div>
</div>
<input type="hidden" name="tab_status" id="tab_status">
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">User Roles</label>
<select class="form-control" id="id_roles" name="role">
<option value="">--Select Roles--</option>
{% for role in roles %}
<option value="{{role.0}}" {% if request.POST.role %}
{% ifequal role.0 request.POST.role %}selected{% endifequal %}{% endif %}>{{ role.1 }}
</option>
{% endfor%}
</select>
</div>
</div>
<div class="filter_col col-lg-2">
<div class="form-group buttons_row">
<button class="btn btn-primary save" type="submit">Search</button>
<a href="{% url 'accounts:list' %}" class="btn btn-default clear">Clear</a>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- filter_row ends here -->
<!-- popup for change password start's here -->
<div class="filter_row row marl">
<div class="col-md-12 col-lg-12 col-xl-12">
<div class="table_container_row row marl no-gutters">
<div class="col-md-12">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="open-tab" data-toggle="tab" href="#open" role="tab" aria-controls="open"
aria-selected="true">Active Users ({{active_users|length}})</a>
</li>
<li class="nav-item">
<a class="nav-link" id="close-tab" data-toggle="tab" href="#close" role="tab" aria-controls="close"
aria-selected="false">Inactive Users ({{inactive_users|length}})</a>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="open" role="tabpanel" aria-labelledby="open">
<div class="card">
<div class="card-body">
<div class="panel-heading-list card-title text-right">
<span class="total_count float-left">Active Users - {% if show_pageitems %}
{% show_pageitems %}{% else %}{{active_users|length}}{% endif %}</span>
<span class="filter_toggle ">
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
</span>
</div>
<div class="table-responsive">
<table class="table ">
<thead>
{% if active_users|length > 0 %}
<tr>
<th width="20%">Username</th>
<th width="20%">Email Address</th>
<th width="8%">User Role</th>
<th width="15%">Status</th>
<th width="20%">Actions</th>
</tr>
{% endif %}
</thead>
<tbody>
{% if per_page %}
{% paginate per_page active_users %}
{% else %}
{% paginate 10 active_users %}
{% endif %}
{% for user in active_users %}
<tr class="text-center">
<td><a href="{% url 'common:view_user' user.id %}">{{ user.username }}</a></td>
<td>{{ user.email }}</td>
<td class="user_role">{% if user.is_superuser %} Admin {% else %} {{user.role}} {% endif %}
</td>
<td>
{% ifnotequal request.user user %}
<a {% if user.is_active %}
onclick="return confirm('Are you sure you want to deactivate this user?')" {% else %}
onclick="return confirm('Are you sure you want to activate this user?')" {% endif %}
href="{% url 'common:change_user_status' pk=user.id %}" class="on_off"
style="color: #454545; text-decoration: none;">
{% if user.is_active == True %}
<i class="fa fa-toggle-on"></i> Active
{% else %}
<i class="fa fa-toggle-off"></i> InActive
{% endif %}
</a>
{% else %}
Active
{% endifnotequal %}
</td>
<td class="actions">
<!-- <a href="{% url 'common:view_user' user.id %}" class="btn btn-info view"><i class="fas fa-eye"></i>View</a> -->
{% if user.email != admin_email or request.user.email == admin_email %}
<a href="{% url 'common:edit_user' user.id %}" class="btn btn-success edit action"><i
class="fas fa-pencil-alt"></i>Edit</a>
{% endif %}
<a href="{% url 'common:remove_user' user.id %}" class="btn btn-danger trash action"
{% if request.user.email == user.email %} style="visibility: hidden;" {% endif %}><i
class="fas fa-trash-alt"></i>Delete</a>
{% if user.email != admin_email and request.user != user %}
<a style="color:black;margin-bottom: -11px;" data-id="{{user.id}}" href=""
class="fa-passwd-reset fa-stack"><i class="fa fa-undo fa-stack-2x"></i><i
class="fa fa-lock fa-stack-1x"></i></a>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% ifequal active_users|length 0 %}
<h6 class="text-center">No Active Users Found</h6>
{% endifequal %}
<div class="text-center row marl">
{% show_pages %}
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="close" role="tabpanel" aria-labelledby="close">
<div class="card">
<div class="card-body">
<div class="panel-heading-list card-title text-right">
<span class="total_count float-left">Inactive Users - {% if show_pageitems %}
{% show_pageitems %}{% else %}{{inactive_users|length}}{% endif %}</span>
<span class="filter_toggle ">
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
</span>
</div>
<div class="table-responsive">
<table class="table">
<thead>
{% if inactive_users|length > 0 %}
<tr>
<th width="20%">Username</th>
<th width="20%">Email Address</th>
<th width="8%">User Role</th>
<th width="15%">Status</th>
<th width="20%">Actions</th>
</tr>
{% endif %}
</thead>
<tbody>
{% if per_page %}
{% paginate per_page inactive_users %}
{% else %}
{% paginate 10 inactive_users %}
{% endif %}
{% for user in inactive_users %}
<tr class="text-center">
<td><a href="{% url 'common:view_user' user.id %}">{{ user.username }}</a></td>
<td>{{ user.email }}</td>
<td class="user_role">{% if user.is_superuser %} Admin {% else %} {{user.role}} {% endif %}
</td>
<td>
{% ifnotequal request.user user %}
<a {% if user.is_active %}
onclick="return confirm('Are you sure you want to deactivate this user?')" {% else %}
onclick="return confirm('Are you sure you want to activate this user?')" {% endif %}
href="{% url 'common:change_user_status' pk=user.id %}" class="on_off"
style="color: #454545; text-decoration: none;">
{% if user.is_active == True %}
<i class="fa fa-toggle-on"></i> Active
{% else %}
<i class="fa fa-toggle-off"></i> InActive
{% endif %}
</a>
{% else %}
Active
{% endifnotequal %}
</td>
<td class="actions">
<!-- <a href="{% url 'common:view_user' user.id %}" class="btn btn-info view"><i class="fas fa-eye"></i>View</a> -->
{% if user.email != admin_email or request.user.email == admin_email %}
<a href="{% url 'common:edit_user' user.id %}" class="btn btn-success edit action"><i
class="fas fa-pencil-alt"></i>Edit</a>
{% endif %}
<a href="{% url 'common:remove_user' user.id %}" class="btn btn-danger trash action"
{% if request.user.email == user.email %} style="visibility: hidden;" {% endif %}><i
class="fas fa-trash-alt"></i>Delete</a>
{% if user.email != admin_email and request.user != user %}
<a style="color:black;margin-bottom: -11px;" data-id="{{user.id}}" href=""
class="fa-passwd-reset fa-stack"><i class="fa fa-undo fa-stack-2x"></i><i
class="fa fa-lock fa-stack-1x"></i></a>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% ifequal inactive_users|length 0 %}
<h6 class="text-center">No Closed Acccount Records Found</h6>
{% endifequal %}
<div class="text-center row marl">
{% show_pages %}
</div>
</div>
</div>
</div>
</div>
<br clear="all">
</div>
</div>
</div>
@ -110,7 +276,13 @@
{% endblock %}
{% block js_block %}
<script type="text/javascript">
$('.trash').click(function(e){
$(document).ready(function () {
$(".filter_toggle").click(function () {
$(".list_filter_row").toggle();
});
});
$('.trash').click(function (e) {
e.preventDefault()
url = $(this).attr('href')
if (!confirm('Are you sure you want to delete?'))
@ -119,25 +291,30 @@
});
</script>
<script>
$(document).ready(function(){
var user_id;
$(".fa-passwd-reset").click(function(e){
e.preventDefault();
user_id = $(this).attr('data-id')
$("#password_eror").text("");
$("#pass_change_div").modal();
});
$("#submit_password").click(function(e){
e.preventDefault();
if ($("#new_passwoord").val().length <4){
$("#password_eror").text("* Password must be atleast 4 characters long! ")
}
else{
$(document).ready(function () {
var user_id;
$(".fa-passwd-reset").click(function (e) {
e.preventDefault();
user_id = $(this).attr('data-id')
$("#password_eror").text("");
$('#useer_id').val(user_id);
$("#chang_password_form").submit()
}
$("#pass_change_div").modal();
});
$("#submit_password").click(function (e) {
e.preventDefault();
if ($("#new_passwoord").val().length < 4) {
$("#password_eror").text("* Password must be atleast 4 characters long! ")
}
else {
$("#password_eror").text("");
$('#useer_id').val(user_id);
$("#chang_password_form").submit()
}
});
$("a[rel='page']").click(function (e) {
e.preventDefault();
$('#users_filter').attr("action", $(this).attr("href"));
$('#users_filter').submit();
});
});
});
</script>
{% endblock js_block %}

View file

@ -54,8 +54,8 @@
<td>{% if setting.created_by %}{{ setting.created_by.email }}{% else %}No user!{% endif %}</td>
<td class="actions">
<!-- <a href="{% url 'common:view_api_settings' setting.id %}" class="btn btn-info view"><i class="fas fa-eye"></i>View</a> -->
<a href="{% url 'common:update_api_settings' setting.id %}" class="btn btn-success edit action"><i class="fas fa-pencil-alt"></i>Edit</a>
<a href="{% url 'common:delete_api_settings' setting.id %}" class="btn btn-danger trash action"><i class="fas fa-trash-alt"></i>Delete</a>
<a href="{% url 'common:update_api_settings' setting.id %}" class="btn btn-success edit action"><i class="fas fa-pencil-alt"></i></a>
<a href="{% url 'common:delete_api_settings' setting.id %}" class="btn btn-danger trash action"><i class="fas fa-trash-alt"></i></a>
</td>
</tr>
{% endfor %}

View file

@ -0,0 +1,14 @@
{% extends 'root_email_template.html' %}
{% block heading %}
Your account has been {{message}}
{% endblock heading %}
{% block button_link %}
{% if url %}
<div style="margin-bottom:20px">
<a href="{{url}}"
style="display:inline-block;width:170px;background:#38abdd;padding:10px;text-align:center;color:#fff;font-size:1rem;font-weight:600;margin:0px auto;margin-bottom:20px;border-radius:5px;text-decoration:none;display:block">Login</a>
</div>
{% endif %}
{% endblock button_link %}

View file

@ -0,0 +1,17 @@
{% extends 'root_email_template.html' %}
{% block heading %}
Hello {{user_email}}
{% endblock heading %}
{% block content_body %}
Your account has been successfully created in Django CRM by {{created_by}}
{% endblock content_body %}
{% block button_link %}
<div style="margin-bottom:20px"><a href="{{url}}"
style="display:inline-block;width:170px;background:#38abdd;padding:10px;text-align:center;color:#fff;font-size:1rem;font-weight:600;margin:0px auto;margin-bottom:20px;border-radius:5px;text-decoration:none;display:block">Login</a>
</div>
{% endblock button_link %}

View file

@ -120,7 +120,7 @@ class PasswordChangeTestCase(ObjectsCreation, TestCase):
password="admin123")
url = "/change-password/"
data = {'CurrentPassword': "admin123",
'Newpassword': "test123", 'confirm': 'test123'}
'Newpassword': "strongpassword", 'confirm': 'strongpassword'}
response = self.client.post(url, data)
self.assertEqual(response.status_code, 302)
@ -772,12 +772,30 @@ class TestUserCreationView(ObjectsCreation, TestCase):
'shared_to':str(self.user2.id)
})
self.assertEqual(200, response.status_code)
self.assertEqual(force_text(response.content),
json.dumps({'success_url': reverse('common:doc_list'), 'error': False}))
# self.assertEqual(force_text(response.content),
# json.dumps({'success_url': reverse('common:doc_list'), 'error': False}))
response = self.client.get(reverse('common:download_document', args=(self.document.id,)))
self.assertEqual(200, response.status_code)
# response = self.client.get(reverse('common:download_document', args=(self.document.id,)))
# self.assertEqual(200, response.status_code)
self.attachment = Attachments.objects.create(
attachment=SimpleUploadedFile(upload_file.name, upload_file.read()), created_by=self.user)
response = self.client.get(reverse('common:download_attachment', kwargs=({'pk':self.attachment.id})))
def test_document_update(self):
self.client.login(username='mp@micropyramid.com', password='mp')
response = self.client.get(reverse('common:download_document', args=(self.document.id,)))
self.assertEqual(403, response.status_code)
def test_user_status(self):
self.user_status = User.objects.create(
first_name="mpus",
username='mpus',
email='mpus@micropyramid.com',
role="USER",
is_active=False
)
self.user_status.set_password('mp')
self.user_status.save()
response = self.client.get(reverse('common:change_user_status', kwargs={'pk': self.user_status.id}))
self.assertEqual(302, response.status_code)

View file

@ -12,7 +12,7 @@ from common.views import (
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
change_passsword_by_admin, google_login, create_lead_from_site
)
from django.conf.urls.static import static
from django.conf import settings
@ -51,7 +51,7 @@ urlpatterns = [
name='password_reset_complete'),
# Document
path('documents/list/', DocumentListView.as_view(), name='doc_list'),
path('documents/', 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/',
@ -87,6 +87,8 @@ urlpatterns = [
path('change-password-by-admin/', change_passsword_by_admin,
name="change_passsword_by_admin"),
path('google/login/', google_login, name="google_login"),
path('create-lead-from-site/', create_lead_from_site,
name='create_lead_from_site')
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -24,6 +24,7 @@ from common.forms import (
APISettingsForm
)
from django.contrib.auth.views import PasswordResetView
from django.contrib.sites.shortcuts import get_current_site
from django.urls import reverse_lazy, reverse
from django.conf import settings
from opportunity.models import Opportunity
@ -36,6 +37,9 @@ from django.core.exceptions import PermissionDenied
import boto3
import botocore
from common.utils import ROLES
from common.tasks import send_email_user_status, send_email_user_delete, send_email_to_new_user
def handler404(request, exception):
return render(request, '404.html', status=404)
@ -65,7 +69,8 @@ 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')
leads = Lead.objects.exclude(
status='converted').exclude(status='closed')
opportunities = Opportunity.objects.all()
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
pass
@ -98,17 +103,17 @@ class ChangePasswordView(LoginRequiredMixin, TemplateView):
def post(self, request, *args, **kwargs):
error, errors = "", ""
form = ChangePasswordForm(request.POST)
form = ChangePasswordForm(request.POST, user=request.user)
if form.is_valid():
user = request.user
if not check_password(request.POST.get('CurrentPassword'),
user.password):
error = "Invalid old password"
else:
user.set_password(request.POST.get('Newpassword'))
user.is_active = True
user.save()
return HttpResponseRedirect('/')
# if not check_password(request.POST.get('CurrentPassword'),
# user.password):
# error = "Invalid old password"
# else:
user.set_password(request.POST.get('Newpassword'))
user.is_active = True
user.save()
return HttpResponseRedirect('/')
else:
errors = form.errors
return render(request, "change_password.html",
@ -210,13 +215,30 @@ class UsersListView(AdminRequiredMixin, TemplateView):
def get_queryset(self):
queryset = self.model.objects.all()
return queryset
request_post = self.request.POST
if request_post:
if request_post.get('username'):
queryset = queryset.filter(
username__icontains=request_post.get('username'))
if request_post.get('email'):
queryset = queryset.filter(
email=request_post.get('email'))
if request_post.get('role'):
queryset = queryset.filter(
role=request_post.get('role'))
return queryset.order_by('username')
def get_context_data(self, **kwargs):
context = super(UsersListView, self).get_context_data(**kwargs)
context["users"] = self.get_queryset()
active_users = self.get_queryset().filter(is_active=True)
inactive_users = self.get_queryset().filter(is_active=False)
context["active_users"] = active_users
context["inactive_users"] = inactive_users
context["per_page"] = self.request.POST.get('per_page')
context['admin_email'] = settings.ADMIN_EMAIL
context['roles'] = ROLES
return context
def post(self, request, *args, **kwargs):
@ -235,15 +257,19 @@ class CreateUserView(AdminRequiredMixin, CreateView):
user.set_password(form.cleaned_data.get("password"))
user.save()
mail_subject = 'Created account in CRM'
message = render_to_string('new_user.html', {
'user': user,
'created_by': self.request.user
current_site = self.request.get_host()
protocol = self.request.scheme
send_email_to_new_user.delay(user.email, self.request.user.email,
domain=current_site, protocol=protocol)
# mail_subject = 'Created account in CRM'
# message = render_to_string('new_user.html', {
# 'user': user,
# 'created_by': self.request.user
})
email = EmailMessage(mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
# })
# email = EmailMessage(mail_subject, message, to=[user.email])
# email.content_subtype = "html"
# email.send()
if self.request.is_ajax():
data = {'success_url': reverse_lazy(
@ -343,6 +369,9 @@ class UserDeleteView(AdminRequiredMixin, DeleteView):
def get(self, request, *args, **kwargs):
self.object = self.get_object()
current_site = request.get_host()
send_email_user_delete.delay(
self.object.email, domain=current_site, protocol=request.scheme)
self.object.delete()
return redirect("common:users_list")
@ -355,8 +384,13 @@ class PasswordResetView(PasswordResetView):
def document_create(request):
template_name = "doc_create.html"
users = User.objects.filter(is_active=True).order_by('email')
users = []
if request.user.role == 'ADMIN' or request.user.is_superuser:
users = User.objects.filter(is_active=True).order_by('email')
elif request.user.google.all():
users = []
else:
users = User.objects.filter(role='ADMIN').order_by('email')
form = DocumentForm(users=users)
if request.POST:
form = DocumentForm(request.POST, request.FILES, users=users)
@ -455,9 +489,15 @@ class DocumentDeleteView(LoginRequiredMixin, DeleteView):
def document_update(request, pk):
template_name = "doc_create.html"
users = User.objects.filter(is_active=True).order_by('email')
form = DocumentForm(users=users)
users = []
if request.user.role == 'ADMIN' or request.user.is_superuser:
users = User.objects.filter(is_active=True).order_by('email')
elif request.user.google.all():
users = []
else:
users = User.objects.filter(role='ADMIN').order_by('email')
document = Document.objects.filter(id=pk).first()
form = DocumentForm(users=users, instance=document)
if request.POST:
form = DocumentForm(request.POST, request.FILES,
@ -602,6 +642,9 @@ def change_user_status(request, pk):
else:
user.is_active = True
user.save()
current_site = request.get_host()
send_email_user_status.delay(
pk, domain=current_site, protocol=request.scheme)
return HttpResponseRedirect('/users/list/')
@ -857,3 +900,21 @@ def google_login(request):
+ 'http://' + request.META['HTTP_HOST'] + \
reverse('common:google_login') + "&state=" + next_url
return HttpResponseRedirect(rty)
def create_lead_from_site(request):
allowed_domains = ['micropyramid.com', 'test.microsite.com:8000', ]
# add origin_domain = request.get_host() in the post body
if (request.get_host() in ['sales.micropyramid.com', ] and request.POST.get('origin_domain') in allowed_domains):
if request.method == 'POST':
if request.POST.get('full_name', None):
lead = Lead.objects.create(title=request.POST.get('full_name'), email=request.POST.get(
'email'), phone=request.POST.get('phone'), description=request.POST.get('message'),
created_from_site=True)
recipients = User.objects.filter(role='ADMIN').values_list('id', flat=True)
lead.assigned_to.add(*recipients)
from leads.tasks import send_email_to_assigned_user
send_email_to_assigned_user(recipients, lead.id, domain='sales.micropyramid.com')
return HttpResponse('Lead Created')
from django.http import HttpResponseBadRequest
return HttpResponseBadRequest('Bad Request')

View file

@ -12,7 +12,8 @@ class ContactForm(forms.ModelForm):
field.widget.attrs = {"class": "form-control"}
self.fields['description'].widget.attrs.update({
'rows': '6'})
self.fields['assigned_to'].queryset = assigned_users
if assigned_users:
self.fields['assigned_to'].queryset = assigned_users
self.fields['assigned_to'].required = False
for key, value in self.fields.items():

34
contacts/tasks.py Normal file
View file

@ -0,0 +1,34 @@
from celery.task import task
from django.core.mail import EmailMessage
from django.shortcuts import reverse
from django.template.loader import render_to_string
from common.models import User
from contacts.models import Contact
@task
def send_email_to_assigned_user(recipients, contact_id, domain='demo.django-crm.io', protocol='http'):
""" Send Mail To Users When they are assigned to a contact """
contact = Contact.objects.get(id=contact_id)
for user in recipients:
recipients_list = []
user = User.objects.filter(id=user).first()
if user:
recipients_list.append(user.email)
context = {}
context["url"] = protocol + '://' + domain + \
reverse('contacts:view_contact', args=(contact.id,))
context["user"] = user
context["contact"] = contact
subject = 'Assigned to contact.'
html_content = render_to_string(
'assigned_to/contact_assigned.html', context=context)
msg = EmailMessage(
subject,
html_content,
to=recipients_list
)
msg.content_subtype = "html"
msg.send()

View file

@ -36,7 +36,7 @@
</div>
<div class="filter_col col-md-3">
<div class="form-group">
<label for="exampleInputEmail1">Assigned Users</label>
<label for="exampleInputEmail1">Assigned To</label>
<select class="assigned_users form-control" name="assigned_to" multiple="multiple">
{% for user in users %}
<option value="{{user.id}}" {% if user.id in assignedto_list %} selected="" {% endif %}>{{user.email}}</option>
@ -79,7 +79,7 @@
<tr>
<th width="5%" >S.no</th>
<th width="20%" >Name</th>
<th width="20%">Assigned User</th>
<th width="20%">Assigned To</th>
<th width="15%" >City</th>
<th width="15%" >State</th>
<th width="15%" >Actions</th>
@ -95,7 +95,7 @@
{% for contact in contact_obj_list %}
<tr style="text-align:center;">
<td scope="row">{{ forloop.counter }}</td>
<td><a href="{% url 'contacts:view_contact' contact.id %}">{{contact.first_name}}</a></td>
<td><a href="{% url 'contacts:view_contact' contact.id %}">{{contact.first_name}} {{contact.last_name}}</a></td>
<td>
{% with contact_users=contact.assigned_to.all %}
{% for user in contact_users %}
@ -107,7 +107,7 @@
<img src="{% static 'images/user.png' %}" title="{{ user.email }}" width="40" height="40">
{% endif %}
{% empty %}
Not Assigned to Any
None
{% endfor %}
{% endwith %}
</td>

View file

@ -1,30 +1,38 @@
{% extends 'sales/base.html' %}
{% load staticfiles %}
{% load thumbnail %}
{% block breadcrumb %}
{% block extralinks %}
<link rel="stylesheet" href="{% static 'css/bootstrap-suggest.css' %}">
<style>
.opacity_block {
opacity: 0.2;
}
</style>
{% endblock extralinks %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"> <a href="{% url 'contacts:list' %}">Contacts</a></li>
<li class="breadcrumb-item active">view</li>
</ol>
<ol class="breadcrumb">
<li class="breadcrumb-item"> <a href="{% url 'contacts:list' %}">Contacts</a></li>
<li class="breadcrumb-item active">view</li>
</ol>
</nav>
{% endblock %}
{% block content %}
<!-- CONTACTS VIEW STARTS -->
<div class="main_container" id="maincontainer">
<div class="overview_form_block row marl justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-body" id="datashow">
<div class="main_container" id="maincontainer">
<div class="overview_form_block row marl justify-content-center">
<div class="col-md-8" id="opacity_block">
<div class="card">
<div class="card-title text-right">
<div class="card-body" id="datashow">
<div class="card-title text-right">
<h5>
<span class="float-left title">Overview</span>
<span class="" style="margin-top: 0px">
<div class="dropdown buttons_row">
<button class="btn primary_btn dropdown-toggle" type="button" data-toggle="dropdown">Actions
<span class="caret"></span></button>
<span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="{% url 'contacts:edit_contact' contact_record.id %}">Edit</a></li>
</ul>
@ -33,118 +41,137 @@
</h5>
</div>
<div class="row marl">
<div class="col-md-4">
<div class="filter_col col-md-12" id="iname">
<div class="form-group" >
<label class="contact_field_label" for="id_name" data-name="name">Name</label>
<div class="contact_field" id="contact_name" data-name="name">{{ contact_record.first_name }} {{ contact_record.last_name }}</div>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label class="contact_field_label" for="id_phone" data-name="name">Phone</label>
<div class="contact_field" id="contact_phone" data-name="name">{{ contact_record.phone }}</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="filter_col col-md-12">
<div class="form-group">
<label class="contact_field_label" for="id_email" data-name="name">Email</label>
<div class="contact_field" id="contact_email" data-name="name">{{ contact_record.email }}</div>
</div>
<div class="row marl">
<div class="col-md-4">
<div class="filter_col col-md-12" id="iname">
<div class="form-group">
<label class="contact_field_label" for="id_name" data-name="name">Name</label>
<div class="contact_field" id="contact_name" data-name="name">{{ contact_record.first_name }}
{{ contact_record.last_name }}</div>
</div>
</div>
<div class="col-md-4">
<div class="filter_col col-md-12">
<div class="form-group">
{% if contact_record.address.address_line or contact_record.address.street or contact_record.address.city or contact_record.address.state or contact_record.address.postcode or contact_record.address.country %}
<label class="contact_field_label" for="id_address" data-name="name" >Billing Address</label>
<div class="contact_field" id="contact_address" data-name="name">
{{contact_record.address.get_complete_address}}
</div>
{% endif %}
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
{% if contact_record.description %}
<label class="contact_field_label" for="id_description" data-name="name">Description</label>
<div class="contact_field" id="contact_description" data-name="name">{{ contact_record.description }}</div>
{% endif %}
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label class="contact_field_label" for="id_phone" data-name="name">Phone</label>
<div class="contact_field" id="contact_phone" data-name="name">{{ contact_record.phone }}</div>
</div>
</div>
<div class="col-md-12">
<div class="created_information">
Created by <b>{{ contact_record.created_by }}</b> created on <i><b>{{ contact_record.created_on }}</b></i>
</div>
<div class="col-md-4">
<div class="filter_col col-md-12">
<div class="form-group">
<label class="contact_field_label" for="id_email" data-name="name">Email</label>
<div class="contact_field" id="contact_email" data-name="name">{{ contact_record.email }}</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="filter_col col-md-12">
<div class="form-group">
{% if contact_record.address.address_line or contact_record.address.street or contact_record.address.city or contact_record.address.state or contact_record.address.postcode or contact_record.address.country %}
<label class="contact_field_label" for="id_address" data-name="name">Billing Address</label>
<div class="contact_field" id="contact_address" data-name="name">
{{contact_record.address.get_complete_address}}
</div>
{% endif %}
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
{% if contact_record.description %}
<label class="contact_field_label" for="id_description" data-name="name">Description</label>
<div class="contact_field" id="contact_description" data-name="name">{{ contact_record.description }}
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-12">
<div class="created_information">
Created by <b>{{ contact_record.created_by }}</b> created on
<b>{{ contact_record.created_on }}</b>
</div>
</div>
</div>
</div>
<!-- Attachment Section start -->
<div>
<div class="card-body mar-top" id="datashow">
<div class="card-title view-pad">
<div>
<div class="card-body mar-top" id="datashow">
<div class="card-title view-pad">
<h5>Attachments</h5>
</div>
<div class="row marl">
</div>
<div class="row marl">
<div class="col-md-12">
<form id="attachment_form" method="POST" enctype="multipart/form-data">
<div class="form-group ">
<input type="file" class="form-control-file" cols="40" id="id_attachments" name="attachment">
<div><i id="AttachmentError" style="display:none; color:red"></i></div>
<br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;" id="attachment_submit">Save</button>
</div>
<form id="attachment_form" method="POST" enctype="multipart/form-data">
<div class="form-group ">
<input type="file" class="form-control-file" cols="40" id="id_attachments" name="attachment">
<div>
<p id="AttachmentError" style="display:none; color:red"></p>
</div>
<br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;"
id="attachment_submit">Save</button>
</div>
</div>
<input type="hidden" value="{{contact_record.id}}" name="contactid">
</form>
<ul class="list-group" id="attachment_div">
{% for attachment in attachments %}
<li class="list-group-item list-row" id="attachment{{attachment.id}}">
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == attachment.created_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="action btn primary_btn"
onclick="remove_attachment({{attachment.id}})">Remove</button>
</div>
<input type="hidden" value="{{contact_record.id}}" name="contactid">
</form>
<ul class="list-group" id="attachment_div">
{% for attachment in attachments %}
<li class="list-group-item list-row" id="attachment{{attachment.id}}">
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == attachment.created_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="action btn primary_btn" onclick="remove_attachment({{attachment.id}})">Remove</button>
</div>
</div>
{% endif %}
</div>
{% endif %}
<div class="stream-post-container" id="attachment_name{{attachment.id}}">
<pre> <span class="icon"><i style="font-size:1.5rem;" class="{{attachment.get_file_type_display}}"></i>{{attachment.file_name}} </span> <a href="{% url 'common:download_attachment' attachment.id %}">Download</a></pre>
</div>
<div class="stream-post-container" id="attachment_name{{attachment.id}}">
{% if attachment.attachment %}
{% if 'image' in attachment.file_type %}
{% thumbnail attachment.attachment "80x80" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" class="mb-2">
{% endthumbnail %}
{% endif %}
{% endif %}
<pre> <span class="icon"><i style="font-size:1.5rem;" class="{{attachment.get_file_type_display}}"></i>{{attachment.file_name}} </span> <a href="{% url 'common:download_attachment' attachment.id %}">Download</a></pre>
</div>
<div class="stream-container">
<pre class="float-left">Uploaded by :{{ attachment.created_by }}</pre>
<pre class="float-right date">{{ attachment.created_on|date:'m/d/Y, h:i A' }}</pre>
</div>
</li>
{% endfor %}
</ul>
<div class="stream-container">
<pre class="float-left">Uploaded by :{{ attachment.created_by }}</pre>
<pre class="float-right date">{{ attachment.created_on|date:'m/d/Y, h:i A' }}</pre>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
<!-- Attachment Section end -->
<!-- Attachment Section end -->
<!-- Comments Section starts-->
<div class="card-body" id="datashow">
<div class="card-title view-pad">
<h5>Comments</h5>
</div>
<div class="card-title view-pad">
<h5>Comments</h5>
</div>
<div class="row marl">
<div class="col-md-12">
<form id="comment_form" method="POST" enctype="multipart/form-data">
<div class="form-group">
<textarea class="form-control" textarea cols="40" rows="3" id="id_comments" name="comment" placeholder="Submit Your Comments Here"></textarea><div><i id="CommentError" style="display:none; color:red"></i></div>
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_comments" name="comment"
placeholder="Submit Your Comments Here"></textarea>
<div>
<p id="CommentError" style="display:none; color:red"></p>
</div>
</br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;" id="comment_submit" type="submit">Submit</button>
<button class="btn btn-default save" style="text-align: center;" id="comment_submit"
type="submit">Submit</button>
</div>
</div>
<input type="hidden" value="{{contact_record.id}}" name="contactid">
@ -155,7 +182,8 @@
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == comment.commented_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="btn primary_btn btn-sm dropdown-toggle" data-toggle="dropdown" type="button">Actions <span class="caret"></span></button>
<button class="btn primary_btn btn-sm dropdown-toggle" data-toggle="dropdown"
type="button">Actions <span class="caret"></span></button>
<ul class="dropdown-menu text-center">
<li>
<a class="action" onclick="edit_comment({{comment.id}})">Edit</a>
@ -167,7 +195,9 @@
</div>
</div>
{% endif %}
<div class="stream-post-container" id="comment_name{{comment.id}}"><pre>{{ comment.comment }}</pre></div>
<div class="stream-post-container" id="comment_name{{comment.id}}">
<pre>{{ comment.comment }}</pre>
</div>
<div class="stream-container">
<pre class="float-left">{{ comment.commented_by }}</pre>
<pre class="float-right">{{ comment.commented_on|date:'m/d/Y, h:i:s A' }}</pre>
@ -177,17 +207,22 @@
</ul>
</div>
</div>
<div class="modal fade" id="Comments_Contacts_Modal" role="dialog" data-keyboard="false" data-backdrop="static">
<div class="modal fade" id="Comments_Contacts_Modal" role="dialog" data-keyboard="false"
data-backdrop="static">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Update Your Comment</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</button>
</div>
<form id="comment_edit_form" method="POST" >
<form id="comment_edit_form" method="POST">
<div class="modal-body">
<div class="form-group">
<textarea class="form-control" textarea cols="40" rows="3" id="id_editcomment" name="comment" placeholder="Submit Your Comments" style="color:black;"></textarea>
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_editcomment"
name="comment" placeholder="Submit Your Comments" style="color:black;"></textarea>
<span>
<p id="CommentEditError" style="display:none; color:red"></p>
</span>
<input type="hidden" value="{{ contact.id }}" name="contactid">
<input type="hidden" value="" name="commentid" id="commentid">
</div>
@ -203,167 +238,249 @@
<!-- Comments Section Ends-->
</div>
</div>
<br>
<form method='GET' class="accountdetails_row">
<div class="table_container_row row marl no-gutters">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<div class="panel-heading-list card-title view-pad">
Tasks
<!-- <a href="{% url 'opportunity:save' %}?view_account={{account_record.id}}"><span
class="fa fa-plus pull-right" style="color:black;"></span></a> -->
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped table-condensed ">
{% if tasks|length > 0 %}
<thead>
<tr>
<th width="5%" style="text-align:center;">S.No</th>
<th width="30%" style="text-align:center;">Title</th>
<th width="20%" style="text-align:center;">Status</th>
<th width="20%" style="text-align:center;">Priority</th>
<th width="25%" style="text-align:center;">Actions </th>
</tr>
</thead>
{% endif %}
<tbody>
{% for task in tasks %}
<tr id="task_{{task.id}}" class="text-center">
<td scope="row">{{ forloop.counter }}</td>
<td>{{task.title}}</td>
<td>{{task.status}}</td>
<td>{{task.priority}} {% if task.probability %} % {% endif %}</td>
<td class="actions">
<a href="{% url 'tasks:task_detail' task.id %}" class="btn btn-primary"
style="color: white;">View</a>
<a href="{% url 'tasks:task_edit' task.id %}?view_account={{account_record.id}}"
class="btn btn-success">Edit</a>
<a href="{% url 'tasks:task_delete' task.id %}?view_account={{account_record.id}}"
class="btn btn-danger">Remove</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% ifequal tasks|length 0 %}
<h6 class="text-center">No tasks Records Found</h6>
{% endifequal %}
</div>
</div>
<br clear="all">
</div>
</div>
</form>
</div>
<div class="overview_form_block row marl justify-content-center" id="loading_spinner"
style="z-index: 10; left: 0; right: 0; vertical-align: middle; position: absolute; margin-top: 15%;">
<div class="spinner-border text-primary" style="width: 5em; height: 5em;" role="status">
<span class="sr-only">Processing File...</span>
</div>
</div>
</div>
<!-- CONTACTS VIEW ENDS -->
{% endblock%}
{% block js_block %}
<script type="text/javascript">
<!-- CONTACTS VIEW ENDS -->
{% endblock%}
{% block js_block %}
<script type="text/javascript">
$('#loading_spinner').hide()
var assigned_to_list = "{{ assigned_data }}";
assigned_to_list = JSON.parse(assigned_to_list.replace(/&(l|g|quo)t;/g, function(a,b){
return {
l : '<',
g : '>',
quo : '"'
}[b];
var assigned_to_list = "{{ assigned_data }}";
assigned_to_list = JSON.parse(assigned_to_list.replace(/&(l|g|quo)t;/g, function (a, b) {
return {
l: '<',
g: '>',
quo: '"'
}[b];
}));
function resetForm() {
$('#CommentEditError').hide()
document.getElementById("CommentEditError").reset();
}
$("#comment_form").submit(function (e) {
e.preventDefault()
var formData = new FormData($("#comment_form")[0]);
$.ajax({
url: "{% url 'contacts:add_comment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentError").html(data.error).show()
} else {
d = new Date(data.commented_on);
$("#comments_div").prepend("<li class='list-group-item list-row' id='comment" + data.comment_id + "'>" +
"<div class='float-right right-container'>" +
"<div class='list-row-buttons btn-group float-right'>"+
"<button class='btn primary_btn btn-sm dropdown-toggle' data-toggle='dropdown' type='button'><span class='caret'></span>Actions</button>"+
"<ul class='dropdown-menu text-center'>" +
"<li><a class='action' onclick='edit_comment(" + data.comment_id + ")'>Edit</a></li>" +
"<li><a class='action' onclick='remove_comment(" + data.comment_id + ")''>Remove</a></li></ul></div></div>" +
"<div class='stream-post-container' id='comment_name"+data.comment_id+"'><pre>"+data.comment+"</pre></div>"+
"<div class='stream-container'><pre class='float-left'>"+data.commented_by+"</pre><pre class='float-right'>"+d.toLocaleString('en-US', { hour12: true })+"</pre></div>"
)
$("#id_comments").val("")
alert("Comment Submitted")
$("#CommentError").html("")
}
function resetForm() {
$('#CommentEditError').hide()
document.getElementById("CommentEditError").reset();
}
})
})
function edit_comment(x) {
$('#Comments_Contacts_Modal').modal('show');
comment = $("#comment_name" + x).text()
$("#commentid").val(x)
$("#id_editcomment").val(comment)
}
$("#comment_edit").click(function (e) {
e.preventDefault()
var formData = new FormData($("#comment_edit_form")[0]);
$.ajax({
url: "/contacts/comment/edit/",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentEditError").html(data.error).show()
} else {
$("#comment_name" + data.commentid).html('<pre>' + data.comment + '</pre>')
$('#Comments_Contacts_Modal').modal('hide');
$("#id_editcomment").val("")
$("#CommentEditError").html("")
}
}
})
})
function HideError(e) {
}
function HideError(e) {
$("#CommentError").hide()
$("#AttachmentError").hide()
}
function remove_comment(x) {
var con = confirm("Do you want to Delete it for Sure!?")
if (con == true) {
$.post('/contacts/comment/remove/', {
"comment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#comment" + data.cid).remove()
}
})
}
}
$("#attachment_form").submit(function (e) {
e.preventDefault()
const formData = new FormData($("#attachment_form")[0]);
$.ajax({
url: "{% url 'contacts:add_attachment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#AttachmentError").html(data.error).show()
} else {
d = new Date(data.created_on)
$("#attachment_div").prepend(
"<li class='list-group-item list-row' id='attachment" + data.attachment_id + "'>" +
"<div class='float-right right-container'><div class='list-row-buttons btn-group float-right'><button class='action btn primary_btn' onclick='remove_attachment("+data.attachment_id+")''>Remove</button></div></div>" +
"<div class='stream-post-container' id='attachment_name"+data.attachment_id+"'>"+
"<pre><span class='icon'><i style='font-size:1.5rem;' class='"+data.attachment_display+"'></i>&nbsp;&nbsp;</span><a href='"+data.download_url+"'>Download</a></pre></div>"+
"<div class='stream-container'><pre class='float-left'>"+data.created_by+"</pre><pre class='float-right date'>"+d.toLocaleString('en-US', { hour12: true })+"</pre></div>" +
"</li>"
)
$("#id_attachments").val("")
alert("Attachment Saved")
$("#AttachmentError").html("")
}
}
})
})
function remove_attachment(x) {
const con = confirm("Do you want to Delete it for Sure!?");
if (con == true) {
$.post('{% url "leads:remove_attachment" %}', {
"attachment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#attachment" + data.aid).remove()
}
})
}
}
$('#remove_contact').click(function(e){
var result = confirm("Are You Sure You Want to delete?");
if (result==false) {
e.preventDefault()
$("#comment_form").submit(function (e) {
e.preventDefault()
var formData = new FormData($("#comment_form")[0]);
$.ajax({
url: "{% url 'contacts:add_comment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentError").html(data.error).show()
} else {
d = new Date(data.commented_on);
$("#comments_div").prepend("<li class='list-group-item list-row' id='comment" + data.comment_id + "'>" +
"<div class='float-right right-container'>" +
"<div class='list-row-buttons btn-group float-right'>" +
"<button class='btn primary_btn btn-sm dropdown-toggle' data-toggle='dropdown' type='button'><span class='caret'></span>Actions</button>" +
"<ul class='dropdown-menu text-center'>" +
"<li><a class='action' onclick='edit_comment(" + data.comment_id + ")'>Edit</a></li>" +
"<li><a class='action' onclick='remove_comment(" + data.comment_id + ")''>Remove</a></li></ul></div></div>" +
"<div class='stream-post-container' id='comment_name" + data.comment_id + "'><pre>" + data.comment + "</pre></div>" +
"<div class='stream-container'><pre class='float-left'>" + data.commented_by + "</pre><pre class='float-right'>" + d.toLocaleString('en-US', { hour12: true }) + "</pre></div>"
)
$("#id_comments").val("")
alert("Comment Submitted")
$("#CommentError").html("")
}
}
})
})
function edit_comment(x) {
$('#Comments_Contacts_Modal').modal('show');
comment = $("#comment_name" + x).text()
$("#commentid").val(x)
$("#id_editcomment").val(comment)
}
$("#comment_edit").click(function (e) {
e.preventDefault()
var formData = new FormData($("#comment_edit_form")[0]);
$.ajax({
url: "/contacts/comment/edit/",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentEditError").html(data.error).show()
} else {
$("#comment_name" + data.commentid).html('<pre>' + data.comment + '</pre>')
$('#Comments_Contacts_Modal').modal('hide');
$("#id_editcomment").val("")
$("#CommentEditError").html("")
}
}
})
})
function HideError(e) {
}
function HideError(e) {
$("#CommentError").hide()
$("#AttachmentError").hide()
}
function remove_comment(x) {
var con = confirm("Do you want to Delete it for Sure!?")
if (con == true) {
$.post('/contacts/comment/remove/', {
"comment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#comment" + data.cid).remove()
}
})
}
}
$("#attachment_form").submit(function (e) {
e.preventDefault()
const formData = new FormData($("#attachment_form")[0]);
$.ajax({
url: "{% url 'contacts:add_attachment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
beforeSend: function () {
$('#loading_spinner').show()
$('#opacity_block').addClass('opacity_block')
},
success: function (data) {
if (data.error) {
$("#AttachmentError").html(data.error).show()
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
} else {
d = new Date(data.created_on)
$("#attachment_div").prepend(
"<li class='list-group-item list-row' id='attachment" + data.attachment_id + "'>" +
"<div class='float-right right-container'><div class='list-row-buttons btn-group float-right'><button class='action btn primary_btn' onclick='remove_attachment(" + data.attachment_id + ")''>Remove</button></div></div>" +
"<div class='stream-post-container' id='attachment_name" + data.attachment_id + "'>" +
`${(data.file_type[0]).startsWith('image') ? `<img src=${data.attachment_url} width="80px" height="80px" class="mb-2">` : ''}` +
"<pre><span class='icon'><i style='font-size:1.5rem;' class='" + data.attachment_display + "'></i>&nbsp;&nbsp;</span>" + data.attachment + "&nbsp; &nbsp;<a href='" + data.download_url + "'>Download</a></pre></div>" +
"<div class='stream-container'><pre class='float-left'>" + data.created_by + "</pre><pre class='float-right date'>" + d.toLocaleString('en-US', { hour12: true }) + "</pre></div>" +
"</li>"
)
$("#id_attachments").val("")
// alert("Attachment Saved")
$("#AttachmentError").html("")
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
setTimeout(() => {
alert("Attachment Saved")
}, 100);
}
}
})
})
function remove_attachment(x) {
const con = confirm("Do you want to Delete it for Sure!?");
if (con == true) {
$.post('{% url "leads:remove_attachment" %}', {
"attachment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#attachment" + data.aid).remove()
}
})
}
}
$('#remove_contact').click(function (e) {
var result = confirm("Are You Sure You Want to delete?");
if (result == false) {
e.preventDefault()
}
});
</script>
<script src="{% static 'js/mention.js' %}"></script>
{% endblock js_block %}
<script src="{% static 'js/bootstrap-suggest.js' %}"></script>
<script>
$('.mentions').suggest('@', {
data: {{ users_mention| safe}},
map: function (user) {
return {
value: user.username,
text: '<strong>' + user.username + '</strong>'
}
}
})
</script>
{% endblock js_block %}

View file

@ -78,7 +78,7 @@ class ContactObjectsCreation_Count(ContactObjectsCreation, TestCase):
class ContactViewsTestCase(ContactObjectsCreation, TestCase):
def test_contacts_list_page(self):
response = self.client.get('/contacts/list/')
response = self.client.get(reverse('contacts:list'))
self.assertEqual(response.status_code, 200)
if response.status_code == 200:
self.assertEqual(
@ -86,7 +86,7 @@ class ContactViewsTestCase(ContactObjectsCreation, TestCase):
self.assertTrue(response.context['contact_obj_list'])
def test_contacts_list_html(self):
response = self.client.get('/contacts/list/')
response = self.client.get(reverse('contacts:list'))
self.assertTemplateUsed(response, 'contacts.html')
def test_contacts_create(self):
@ -159,7 +159,7 @@ class ContactViewsTestCase(ContactObjectsCreation, TestCase):
def test_contacts_delete_location_checking(self):
response = self.client.post(
'/contacts/' + str(self.contact.id) + '/delete/')
self.assertEqual(response['location'], '/contacts/list/')
self.assertEqual(response['location'], '/contacts/')
def test_contacts_edit(self):
response = self.client.post(
@ -203,7 +203,7 @@ class ContactsListTestCase(ContactObjectsCreation, TestCase):
def test_contacts_list(self):
self.contacts = Contact.objects.all()
response = self.client.get('/contacts/list/')
response = self.client.get(reverse('contacts:list'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'contacts.html')
@ -211,7 +211,7 @@ class ContactsListTestCase(ContactObjectsCreation, TestCase):
data = {'fist_name': 'contact',
'city': "Orlando", 'phone': '12345',
'email': "contact@gmail.com", 'assigned_to': str(self.user.id)}
response = self.client.post('/contacts/list/', data)
response = self.client.post(reverse('contacts:list'), data)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'contacts.html')
@ -222,12 +222,12 @@ class ContactsListTestCase(ContactObjectsCreation, TestCase):
first_name="contactmp",
email="contact@gmmpail.com",
created_by=self.user_contacts_mp)
response = self.client.get('/contacts/list/')
response = self.client.get(reverse('contacts:list'))
self.assertEqual(response.status_code, 200)
self.contact.delete()
response = self.client.post(
'/contacts/list/', {'first_name': 'contactmp', 'assigned_to': str(self.user.id)})
reverse('contacts:list'), {'first_name': 'contactmp', 'assigned_to': str(self.user.id)})
self.assertEqual(response.status_code, 200)

View file

@ -9,7 +9,7 @@ app_name = 'contacts'
urlpatterns = [
path('list/', ContactsListView.as_view(), name='list'),
path('', ContactsListView.as_view(), name='list'),
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"),

View file

@ -17,6 +17,8 @@ from contacts.forms import (ContactForm,
from accounts.models import Account
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from common.tasks import send_email_user_mentions
from contacts.tasks import send_email_to_assigned_user
class ContactsListView(LoginRequiredMixin, TemplateView):
@ -49,7 +51,7 @@ class ContactsListView(LoginRequiredMixin, TemplateView):
if request_post.getlist('assigned_to'):
queryset = queryset.filter(
assigned_to__id__in=request_post.getlist('assigned_to'))
return queryset
return queryset.distinct()
def get_context_data(self, **kwargs):
context = super(ContactsListView, self).get_context_data(**kwargs)
@ -82,13 +84,20 @@ class CreateContactView(LoginRequiredMixin, CreateView):
template_name = "create_contact.html"
def dispatch(self, request, *args, **kwargs):
self.users = User.objects.filter(is_active=True).order_by('email')
if self.request.user.role == 'ADMIN' or self.request.user.is_superuser:
self.users = User.objects.filter(is_active=True).order_by('email')
elif request.user.google.all():
self.users = []
else:
self.users = User.objects.filter(role='ADMIN').order_by('email')
return super(CreateContactView, self).dispatch(
request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(CreateContactView, self).get_form_kwargs()
kwargs.update({"assigned_to": self.users})
if self.request.user.role == 'ADMIN' or self.request.user.is_superuser:
self.users = User.objects.filter(is_active=True).order_by('email')
kwargs.update({"assigned_to": self.users})
return kwargs
def post(self, request, *args, **kwargs):
@ -117,19 +126,22 @@ class CreateContactView(LoginRequiredMixin, CreateView):
*self.request.POST.getlist('assigned_to'))
assigned_to_list = self.request.POST.getlist('assigned_to')
current_site = get_current_site(self.request)
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
})
email = EmailMessage(mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
recipients = assigned_to_list
send_email_to_assigned_user.delay(recipients, contact_obj.id, domain=current_site.domain,
protocol=self.request.scheme)
# 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
# })
# email = EmailMessage(mail_subject, message, to=[user.email])
# email.content_subtype = "html"
# email.send()
if self.request.FILES.get('contact_attachment'):
attachment = Attachments()
@ -199,11 +211,20 @@ class ContactDetailView(LoginRequiredMixin, DetailView):
assigned_dict['name'] = each.email
assigned_data.append(assigned_dict)
if self.request.user.is_superuser or self.request.user.role == 'ADMIN':
users_mention = list(User.objects.all().values('username'))
elif self.request.user != context['object'].created_by:
users_mention = [{'username': context['object'].created_by.username}]
else:
users_mention = list(context['object'].assigned_to.all().values('username'))
context.update({"comments":
context["contact_record"].contact_comments.all(),
'attachments':
context["contact_record"].contact_attachment.all(),
"assigned_data": json.dumps(assigned_data)
"assigned_data": json.dumps(assigned_data),
"tasks" : context['object'].contacts_tasks.all(),
'users_mention': users_mention,
})
return context
@ -214,13 +235,20 @@ class UpdateContactView(LoginRequiredMixin, UpdateView):
template_name = "create_contact.html"
def dispatch(self, request, *args, **kwargs):
self.users = User.objects.filter(is_active=True).order_by('email')
if self.request.user.role == 'ADMIN' or self.request.user.is_superuser:
self.users = User.objects.filter(is_active=True).order_by('email')
elif request.user.google.all():
self.users = []
else:
self.users = User.objects.filter(role='ADMIN').order_by('email')
return super(UpdateContactView, self).dispatch(
request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(UpdateContactView, self).get_form_kwargs()
kwargs.update({"assigned_to": self.users})
if self.request.user.role == 'ADMIN' or self.request.user.is_superuser:
self.users = User.objects.filter(is_active=True).order_by('email')
kwargs.update({"assigned_to": self.users})
return kwargs
def post(self, request, *args, **kwargs):
@ -248,21 +276,25 @@ 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 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
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
current_site = get_current_site(self.request)
recipients = all_members_list
send_email_to_assigned_user.delay(recipients, contact_obj.id, domain=current_site.domain,
protocol=self.request.scheme)
# 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
# })
# email = EmailMessage(
# mail_subject, message, to=[user.email])
# email.content_subtype = "html"
# email.send()
contact_obj.assigned_to.clear()
contact_obj.assigned_to.add(
@ -375,6 +407,10 @@ class AddCommentView(LoginRequiredMixin, CreateView):
comment.commented_by = self.request.user
comment.contact = self.contact
comment.save()
comment_id = comment.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'contacts', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"comment_id": comment.id, "comment": comment.comment,
"commented_on": comment.commented_on,
@ -404,6 +440,10 @@ class UpdateCommentView(LoginRequiredMixin, View):
def form_valid(self, form):
self.comment_obj.comment = form.cleaned_data.get("comment")
self.comment_obj.save(update_fields=["comment"])
comment_id = self.comment_obj.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'contacts', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"commentid": self.comment_obj.id,
"comment": self.comment_obj.comment,
@ -476,7 +516,8 @@ class AddAttachmentsView(LoginRequiredMixin, CreateView):
"download_url": reverse('common:download_attachment',
kwargs={'pk': attachment.id}),
"attachment_display": attachment.get_file_type_display(),
"created_by": attachment.created_by.email
"created_by": attachment.created_by.email,
"file_type": attachment.file_type()
})
def form_invalid(self, form):

View file

@ -8,7 +8,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 = True
DEBUG = False
ALLOWED_HOSTS = ['*']
@ -38,6 +38,9 @@ INSTALLED_APPS = [
'phonenumber_field',
'storages',
'marketing',
'tasks',
'invoices',
'events',
]
MIDDLEWARE = [
@ -232,11 +235,7 @@ CELERY_BEAT_SCHEDULE = {
"runs-scheduled-campaigns-for-every-one-hour": {
"task": "marketing.tasks.send_scheduled_campaigns",
"schedule": crontab(hour='*/1')
},
"runs-unsubscribes-bounces-for-every-one-hour": {
"task": "marketing.tasks.list_all_bounces_unsubscribes",
"schedule": crontab(hour='*/1')
},
}
}
MAIL_SENDER = 'AMAZON'

View file

@ -9,13 +9,16 @@ app_name = 'crm'
urlpatterns = [
path('', include('common.urls', namespace="common")),
path('', include('django.contrib.auth.urls')),
path('m/', include('marketing.urls', namespace="marketing")),
path('marketing/', include('marketing.urls', namespace="marketing")),
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('cases/', include('cases.urls', namespace="cases")),
path('tasks/', include('tasks.urls', namespace="tasks")),
path('invoices/', include('invoices.urls', namespace="invoices")),
path('events/', include('events.urls', namespace="events")),
path('emails/', include('emails.urls', namespace="emails")),
# path('planner/', include('planner.urls', namespace="planner")),
path('logout/', views.LogoutView, {'next_page': '/login/'}, name="logout"),

0
events/__init__.py Normal file
View file

3
events/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
events/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class EventsConfig(AppConfig):
name = 'events'

129
events/forms.py Normal file
View file

@ -0,0 +1,129 @@
from django import forms
from contacts.models import Contact
from common.models import User, Attachments, Comment
from django.db.models import Q
from events.models import Event
class EventForm(forms.ModelForm):
WEEKDAYS = (('Monday', 'Monday'),
('Tuesday', 'Tuesday'),
('Wednesday', 'Wednesday'),
('Thursday', 'Thursday'),
('Friday', 'Friday'),
('Saturday', 'Saturday'),
('Sunday', 'Sunday'))
recurring_days = forms.MultipleChoiceField(
required=False, choices=WEEKDAYS)
def __init__(self, *args, **kwargs):
request_user = kwargs.pop('request_user', None)
self.obj_instance = kwargs.get('instance', None)
super(EventForm, self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs = {"class": "form-control"}
if request_user.role == 'ADMIN' or request_user.is_superuser:
self.fields['assigned_to'].queryset = User.objects.all()
self.fields["contacts"].queryset = Contact.objects.filter()
self.fields['assigned_to'].required = True
elif request_user.google.all():
self.fields['assigned_to'].queryset = User.objects.none()
self.fields["contacts"].queryset = Contact.objects.filter(
Q(assigned_to__in=[request_user]) | Q(created_by=request_user))
self.fields['assigned_to'].required = False
elif request_user.role == 'USER':
self.fields['assigned_to'].queryset = User.objects.filter(
role='ADMIN')
self.fields["contacts"].queryset = Contact.objects.filter(
Q(assigned_to__in=[request_user]) | Q(created_by=request_user))
self.fields['assigned_to'].required = True
else:
pass
if self.obj_instance:
# self.fields['name'].widget.attrs['readonly'] = True
self.fields['start_date'].widget.attrs['readonly'] = True
self.fields['end_date'].widget.attrs['readonly'] = True
self.fields['name'].required = True
self.fields['event_type'].required = True
self.fields['contacts'].required = True
self.fields['start_date'].required = True
self.fields['start_time'].required = True
self.fields['end_date'].required = True
self.fields['end_time'].required = True
self.fields['description'].required = False
def clean_recurring_days(self):
recurring_days = self.cleaned_data.get('recurring_days')
if self.cleaned_data.get('event_type') == 'Recurring':
if len(recurring_days) < 1:
raise forms.ValidationError('Choose atleast on recurring day')
def clean_name(self):
name = self.cleaned_data.get('name')
if not self.obj_instance:
if Event.objects.filter(name=name).exclude(id=self.instance.id).exists():
raise forms.ValidationError(
'Event with this name already exists.')
return name
def clean_event_type(self):
""" This Validation Is For Keeping The Field Readonly While Editing or Updating"""
event_type = self.cleaned_data.get('event_type')
if self.obj_instance:
return self.obj_instance.event_type
else:
return event_type
def clean_start_date(self):
start_date = self.cleaned_data.get('start_date')
if self.obj_instance:
return self.obj_instance.start_date
else:
return start_date
def clean_end_date(self):
end_date = self.cleaned_data.get('end_date')
event_type = self.cleaned_data.get('event_type')
if event_type == 'Recurring':
if self.clean_start_date() == end_date:
raise forms.ValidationError(
'Start Date and End Date cannot be equal for recurring events')
if self.clean_start_date() > end_date:
raise forms.ValidationError(
'End Date cannot be less than start date')
return end_date
def clean_end_time(self):
end_time = self.cleaned_data.get('end_time')
if self.cleaned_data.get('start_time') > end_time:
raise forms.ValidationError(
'End Time cannot be less than Start Time')
return end_time
class Meta:
model = Event
fields = (
'name', 'event_type', 'contacts', 'assigned_to', 'start_date', 'start_time',
'end_date', 'end_time', 'description',
)
class EventCommentForm(forms.ModelForm):
comment = forms.CharField(max_length=64, required=True)
class Meta:
model = Comment
fields = ('comment', 'event', 'commented_by')
class EventAttachmentForm(forms.ModelForm):
attachment = forms.FileField(max_length=1001, required=True)
class Meta:
model = Attachments
fields = ('attachment', 'event')

View file

@ -0,0 +1,38 @@
# Generated by Django 2.1.7 on 2019-06-03 05:58
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('contacts', '0003_merge_20190214_1427'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Event',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64, verbose_name='Event')),
('event_type', models.CharField(choices=[('Recurring', 'Recurring'), ('Non-Recurring', 'Non-Recurring')], max_length=20)),
('status', models.CharField(blank=True, choices=[('Planned', 'Planned'), ('Held', 'Held'), ('Not Held', 'Not Held'), ('Not Started', 'Not Started'), ('Started', 'Started'), ('Completed', 'Completed'), ('Canceled', 'Canceled'), ('Deferred', 'Deferred')], max_length=64, null=True)),
('start_date', models.DateField(default=None)),
('start_time', models.TimeField(default=None)),
('end_date', models.DateField(default=None)),
('end_time', models.TimeField(blank=True, default=None, null=True)),
('description', models.TextField(blank=True, null=True)),
('created_on', models.DateTimeField(auto_now_add=True, verbose_name='Created on')),
('is_active', models.BooleanField(default=True)),
('disabled', models.BooleanField(default=False)),
('assigned_to', models.ManyToManyField(blank=True, related_name='event_assigned', to=settings.AUTH_USER_MODEL)),
('contacts', models.ManyToManyField(blank=True, related_name='event_contact', to='contacts.Contact')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='event_created_by_user', to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.1.7 on 2019-06-03 13:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('events', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='event',
name='date_of_meeting',
field=models.DateField(blank=True, null=True),
),
]

View file

45
events/models.py Normal file
View file

@ -0,0 +1,45 @@
from django.db import models
from django.utils.translation import pgettext_lazy
from django.utils.translation import ugettext_lazy as _
from common.models import User
from contacts.models import Contact
# Create your models here.
class Event(models.Model):
EVENT_TYPE = (
('Recurring', 'Recurring'),
('Non-Recurring', 'Non-Recurring'),
# ("Call", "Call"),
# ('Meeting', 'Meeting'),
# ('Task', 'Task')
)
EVENT_STATUS = (
('Planned', 'Planned'),
('Held', 'Held'),
('Not Held', 'Not Held'),
('Not Started', 'Not Started'),
('Started', 'Started'),
('Completed', 'Completed'),
('Canceled', 'Canceled'),
('Deferred', 'Deferred')
)
name = models.CharField(_("Event"), max_length=64)
event_type = models.CharField(max_length=20, choices=EVENT_TYPE)
status = models.CharField(choices=EVENT_STATUS, max_length=64, blank=True, null=True)
contacts = models.ManyToManyField(
Contact, blank=True, related_name='event_contact')
assigned_to = models.ManyToManyField(
User, blank=True, related_name='event_assigned')
start_date = models.DateField(default=None)
start_time = models.TimeField(default=None)
end_date = models.DateField(default=None)
end_time = models.TimeField(default=None, blank=True, null=True)
description = models.TextField(blank=True, null=True)
created_on = models.DateTimeField(_("Created on"), auto_now_add=True)
created_by = models.ForeignKey(
User, related_name='event_created_by_user', null=True, on_delete=models.SET_NULL)
is_active = models.BooleanField(default=True)
disabled = models.BooleanField(default=False)
date_of_meeting = models.DateField(blank=True, null=True)
# tags = models.ManyToManyField(Tag)

44
events/tasks.py Normal file
View file

@ -0,0 +1,44 @@
from celery.task import task
from django.conf import settings
from django.core.mail import EmailMessage
from django.shortcuts import reverse
from django.template.loader import render_to_string
from common.models import User
from contacts.models import Contact
from events.models import Event
@task
def send_email(event_id, domain='demo.django-crm.io', protocol='http'):
event = Event.objects.filter(id=event_id).first()
subject = 'Event : {0}'.format(event.name)
context = {}
context['event'] = event.name
context['event_id'] = event_id
context['event_created_by'] = event.created_by
context['event_date_of_meeting'] = event.date_of_meeting
context["url"] = protocol + '://' + domain + \
reverse('events:detail_view', args=(event.id,))
recipients = event.assigned_to.all()
if recipients.count() > 0:
for recipient in recipients:
context['user'] = recipient.email
html_content = render_to_string(
'assigned_to_email_template_event.html', context=context)
msg = EmailMessage(
subject=subject, body=html_content, to=[recipient.email, ])
msg.content_subtype = "html"
msg.send()
# might need to add contacts to TODO
# recipients = event.contacts.all()
# if recipients.count() > 0:
# for recipient in recipients:
# context['user'] = recipient.email
# html_content = render_to_string(
# 'assigned_to_email_template.html', context=context)
# msg = EmailMessage(
# subject=subject, body=html_content, to=[recipient.email, ])
# msg.content_subtype = "html"
# msg.send

View file

@ -0,0 +1,19 @@
{% extends 'root_email_template.html' %}
{% block heading %}
Hello {{ user }}
{% endblock heading %}
{% block content_body %}
You have been invited to an Event {{event_name}} created by {{event_created_by}}
Date of meeting {{event_date_of_meeting}}
{% endblock content_body %}
{% block button_link %}
<div style="margin-bottom:20px">
<a href="{{url}}"
style="display:inline-block;width:170px;background:#38abdd;padding:10px;text-align:center;color:#fff;font-size:1rem;font-weight:600;margin:0px auto;margin-bottom:20px;border-radius:5px;text-decoration:none;display:block">click
here</a>
</div>
{% endblock button_link %}

View file

@ -0,0 +1,251 @@
{% extends 'sales/base.html' %}
{% load staticfiles %}
{% block breadcrumb %}
{% block extralinks %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css"
href="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/themes/start/jquery-ui.css" />
{% endblock %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'events:event_create' %}">Events</a></li>
<li class="breadcrumb-item active">{% if event_obj %}Edit{% else %}Create{% endif %}</li>
</ol>
</nav>
{% endblock %}
{% block content %}
<form id="formid" action='' method="POST" novalidate enctype="multipart/form-data">
<div class="overview_form_block row marl justify-content-center">
<div class="col-md-9">
<div class="card">
<div class="card-body">
<div class="card-title text-center">
{% if event_obj %}EDIT{% else %}CREATE{% endif %} EVENT
</div>
<div class="row marl">
<div class="col-md-4">
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Event
Name{% if form.name.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
{{ form.name }}
<span class="error" id="id__name">{{ form.name.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1">Event Type{% if form.event_type.field.required %}<span
class="error">*</span>{% endif %}</label>
{{ form.event_type }}
<span class="error" id="id__event_type">{{ form.event_type.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Contacts{% if form.contacts.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.contacts }}
<span class="error" id="id__contacts">{{ form.contacts.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Assigned
To{% if form.assigned_to.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
{{ form.assigned_to }}
<span class="error" id="id__assigned_to">{{ form.assigned_to.errors }}</span>
</div>
</div>
</div>
<div class="col-md-4">
<div class="filter_col billing_block col-md-12" style="padding: 0px;">
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Start
Date{% if form.start_date.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
{{ form.start_date }}
<span class="error" id="id__start_date">{{ form.start_date.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Start
Time{% if form.start_time.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
{{ form.start_time }}
<span class="error" id="id__start_time">{{ form.start_time.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="exampleInputEmail1" class="required">End Date{% if form.end_date.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.end_date }}
<span class="error" id="id__end_date">{{ form.end_date.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group end_time_selector">
<label for="id_contacts" class="required">End Time{% if form.end_time.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.end_time }}
<span class="error" id="id__end_time">{{ form.end_time.errors }}</span>
</div>
</div>
<div class="filter_col billing_block col-md-12" style="padding: 0px;">
</div>
</div>
</div>
<div class="col-md-4">
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="id_contacts" class="required">Description{% if form.description.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.description }}
<span class="error" id="id__description">{{ form.description.errors }}</span>
</div>
</div>
{% if not event_obj %}
<div class="filter_col col-md-12" id="recurring_block">
<p class="mt-3" style="color: #333">Days</p>
<div class="form-check mb-1">
<input type="checkbox" class="form-check-input" name="recurring_days" id="" value="Monday"
{% if 'Monday' in selected_recurring_days %} checked {% endif %}>
<label class="form-check-label" for="exampleCheck1">Monday</label>
</div>
<div class="form-check mb-1">
<input type="checkbox" class="form-check-input" name="recurring_days" id="" value="Tuesday"
{% if 'Tuesday' in selected_recurring_days %} checked {% endif %}>
<label class="form-check-label" for="exampleCheck1">Tuesday</label>
</div>
<div class="form-check mb-1">
<input type="checkbox" class="form-check-input" name="recurring_days" id="" value="Wednesday"
{% if 'Wednesday' in selected_recurring_days %} checked {% endif %}>
<label class="form-check-label" for="exampleCheck1">Wednesday</label>
</div>
<div class="form-check mb-1">
<input type="checkbox" class="form-check-input" name="recurring_days" id="" value="Thursday"
{% if 'Thursday' in selected_recurring_days %} checked {% endif %}>
<label class="form-check-label" for="exampleCheck1">Thursday</label>
</div>
<div class="form-check mb-1">
<input type="checkbox" class="form-check-input" name="recurring_days" id="" value="Friday"
{% if 'Friday' in selected_recurring_days %} checked {% endif %}>
<label class="form-check-label" for="exampleCheck1">Friday</label>
</div>
<div class="form-check mb-1">
<input type="checkbox" class="form-check-input" name="recurring_days" id="" value="Saturday"
{% if 'Saturday' in selected_recurring_days %} checked {% endif %}>
<label class="form-check-label" for="exampleCheck1">Saturday</label>
</div>
<div class="form-check mb-1">
<input type="checkbox" class="form-check-input" name="recurring_days" id="" value="Sunday"
{% if 'Sunday' in selected_recurring_days %} checked {% endif %}>
<label class="form-check-label" for="exampleCheck1">Sunday</label>
</div>
<div>
<span id="id__recurring_days" class="error"></span>
</div>
</div>
{% endif %}
</div>
</div>
<div class="col-md-12">
<div class="col-md-12">
<div class="row marl buttons_row form_btn_row text-center">
<button class="btn btn-default save" type="submit">Save</button>
<a href="{% url 'events:events_list' %}" class="btn btn-default clear"
id="create_user_cancel">Cancel</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- <input type="hidden" id="save_new_form" name="savenewform"> -->
</div>
<!-- overview ends here -->
</form>
{% endblock %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
{% block js_block %}
<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js'></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.js"></script>
<script src="https://malsup.github.io/jquery.form.js"></script>
<script type="text/javascript">
// $(document).ready(function() {
// $('.assigned_users').select2();
// });
$(document).ready(function () {
$('#recurring_block').hide()
$("#id_contacts").select2();
$("#id_assigned_to").select2();
$("#id_start_date").css('color', 'black');
$("#id_end_date").css('color', 'black');
var date = new Date();
var today = new Date(date.getFullYear(), date.getMonth(), date.getDate());
$('#id_start_date, #id_end_date').datetimepicker({
'format': 'YYYY-MM-DD',
minDate: today
})
$('#id_start_time, #id_end_time').datetimepicker({
'format': 'HH:mm:ss',
icons: {
time: "far fa-clock",
up: "fas fa-chevron-up",
down: "fas fa-chevron-down"
}
})
if ($('#id_event_type').val() == 'Recurring') {
$('#recurring_block').show()
}
else {
$('#recurring_block').hide()
}
$('#id_event_type').on('change', function () {
// console.log($(this).val())
if ($(this).val() == 'Recurring') {
$('#recurring_block').show()
}
else {
$('#recurring_block').hide()
}
})
// if ($('#recurring_block').length) {
// if ($('#id_event_type').val() == 'Recurring') {
// $('#recurring_block').show()
// }
// else {
// $('#recurring_block').hide()
// }
// }
});
$('form#formid').ajaxForm({
type: 'POST',
dataType: 'json',
url: ".",
data: $('#formid').serialize(),
success: function (data) {
if (data.error) {
$('.error').html('')
for (var key in data.errors) {
$('#id__' + key).html("<p>" + data.errors[key][0] + "</p>");
};
$('#submit_btn').removeAttr('disabled')
}
else {
window.location = data.success_url;
}
}
});
</script>
{% endblock js_block %}

View file

@ -0,0 +1,474 @@
{% extends 'sales/base.html' %}
{% load staticfiles %}
{% load thumbnail %}
{% block breadcrumb %}
{% block extralinks %}
<link rel="stylesheet" href="{% static 'css/bootstrap-suggest.css' %}">
<style>
.opacity_block {
opacity: 0.2;
}
</style>
{% endblock extralinks %}
<!-- breadcrumb starts here -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'events:events_list' %}">Event</a></li>
<li class="active breadcrumb-item">{{event.name}}</li>
</ol>
</nav>
{% endblock breadcrumb %}
{% block content %}
<!-- Task VIEW STARTS -->
<div class="main_container" id="maincontainer">
<div class="overview_form_block row marl justify-content-center">
<div class="col-md-8" id="opacity_block">
<div class="card">
<div class="card-body" id="datashow">
<div class="card-title text-right">
<h5>
<span class="float-left title">Overview</span>
<span style="margin-top: 0px">
<div class="dropdown buttons_row">
<button class="btn primary_btn dropdown-toggle" type="button" data-toggle="dropdown">Actions
<span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="{% url 'events:event_update' event.id %}">Edit</a></li>
</ul>
</div>
</span>
</h5>
</div>
<div class="row marl">
<div class="col-md-4">
{% if event.name %}
<div class="filter_col col-md-12" id="iname">
<div class="form-group">
<label class="acc_field_label" for="id_name" data-name="name">Name</label>
<div class="account_field" id="account_name" data-name="name">{{ event.name }}</div>
</div>
</div>
{% endif %}
{% if event.event_type %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_phone" data-name="name">Event Type</label>
<div class="account_field" id="account_phone" data-name="name">{{ event.event_type }}</div>
</div>
</div>
{% endif %}
{% if event.start_date %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_status" data-name="name">Start Date</label>
<div class="account_field" id="account_status" data-name="name">{{ event.start_date }}</div>
</div>
</div>
{% endif %}
{% if event.end_date %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_status" data-name="name">End Date</label>
<div class="account_field" id="account_status" data-name="name">{{ event.end_date }}</div>
</div>
</div>
{% endif %}
</div>
<div class="col-md-4">
<div class="filter_col col-md-12">
<div class="form-group">
{% with contacts=event.contacts.all %}
<label class="acc_field_label" for="id_website" data-name="name">Contacts</label>
{% if contacts %}
{% for contact in contacts %}
<div class="account_field" id="account_website" data-name="name">{{ contact }}</div>
{% endfor %}
{% else %}
<div class="account_field" id="account_website" data-name="name">None</div>
{% endif %}
{% endwith %}
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
{% with users=event.assigned_to.all %}
<label class="acc_field_label" for="id_website" data-name="name">Assigned To</label>
{% if users %}
{% for user in users %}
<div class="account_field" id="account_website" data-name="name">{{ user }}</div>
{% endfor %}
{% else %}
<div class="account_field" id="account_website" data-name="name">None</div>
{% endif %}
{% endwith %}
</div>
</div>
{% if event.start_time %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_website" data-name="name">Start Time</label>
<div class="account_field" id="account_website" data-name="name">{{event.start_time}}</div>
</div>
</div>
{% endif %}
{% if event.end_time %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_website" data-name="name">End Time</label>
<div class="account_field" id="account_website" data-name="name">{{event.end_time}}</div>
</div>
</div>
{% endif %}
</div>
<div class="col-md-4">
{% if event.description %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_website" data-name="name">Description</label>
<div class="account_field" id="account_website" data-name="name">{{ event.description }}</div>
</div>
</div>
{% endif %}
{% if event.date_of_meeting %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_website" data-name="name">Date Of Meeting</label>
<div class="account_field" id="account_website" data-name="name">{{ event.date_of_meeting }}</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-12">
<div class="created_information">
Created by <b>{{ event.created_by }}</b> created on
<b>{{ event.created_on }}</b>
</div>
</div>
<div class="col-md-12" style="padding:0;">
<div class="col-md-12 " style="padding:0">
<!-- Attachment Section start -->
<div class="card-body mar-top" id="datashow">
<div class="card-title view-pad">
<h5>Attachments</h5>
</div>
<div class="row marl">
<div class="col-md-12">
<form id="attachment_form" method="POST" enctype="multipart/form-data">
<div class="form-group ">
<input type="file" class="form-control-file" cols="40" id="id_attachments" name="attachment">
<div>
<p id="AttachmentError" style="display:none; color:red"></p>
</div>
<br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;"
id="attachment_submit">Save</button>
</div>
</div>
<input type="hidden" value="{{event.id}}" name="event_id">
</form>
<ul class="list-group" id="attachment_div">
{% for attachment in attachments %}
<li class="list-group-item list-row" id="attachment{{attachment.id}}">
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == attachment.created_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="action btn primary_btn"
onclick="remove_attachment({{attachment.id}})">Remove</button>
</div>
</div>
{% endif %}
<div class="stream-post-container" id="attachment_name{{attachment.id}}">
{% if attachment.attachment %}
{% if 'image' in attachment.file_type %}
{% thumbnail attachment.attachment "80x80" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" class="mb-2">
{% endthumbnail %}
{% endif %}
{% endif %}
<pre> <span class="icon"><i style="font-size:1.5rem;" class="{{attachment.get_file_type_display}}"></i> </span>{{attachment.file_name}} <a href="{% url 'common:download_attachment' attachment.id %}">Download</a></pre>
</div>
<div class="stream-container">
<pre class="float-left">Uploaded by :{{ attachment.created_by }}</pre>
<pre class="float-right date">{{ attachment.created_on|date:'m/d/Y, h:i A' }}</pre>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<!-- Attachment Section end -->
<!-- Comments Section starts-->
<div class="card-body" id="datashow">
<div class="card-title">
<h5>Comments</h5>
</div>
<div class="row marl">
<div class="col-md-12">
<form id="comment_form" method="POST" enctype="multipart/form-data">
<div class="form-group">
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_comments"
name="comment" placeholder="Submit Your Comments Here"></textarea>
<div>
<p id="CommentError" style="display:none; color:red"></p>
</div>
</br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;" id="comment_submit"
type="submit">Submit</button>
</div>
</div>
<input type="hidden" value="{{ event.id }}" name="event_id">
</form>
<ul class="list-group" id="comments_div">
{% for comment in comments %}
<li class="list-group-item list-row" id="comment{{comment.id}}">
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == comment.commented_by %}
<div class="list-row-buttons btn-group float-right">
<button class="btn primary_btn btn-sm dropdown-toggle" data-toggle="dropdown"
type="button">Actions<span class="caret"></span></button>
<ul class="dropdown-menu text-center">
<li>
<a class="action" onclick="edit_comment({{comment.id}})">Edit</a>
</li>
<li>
<a class="action" onclick="remove_comment({{comment.id}})">Remove</a>
</li>
</ul>
</div>
{% endif %}
<div class="stream-post-container" id="comment_name{{comment.id}}">
<pre>{{ comment.comment }}</pre>
</div>
<div class="stream-container">
<pre class="float-left">{{ comment.commented_by }}</pre>
<pre class="float-right date">{{ comment.commented_on|date:'m/d/Y, h:i:s A' }}</pre>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
<div class="modal fade" id="Comments_Accounts_Modal" role="dialog" data-keyboard="false"
data-backdrop="static">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Update Your Comment</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</button>
</div>
<form id="comment_edit_form" method="POST">
<div class="modal-body">
<div class="form-group">
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_editcomment"
name="comment" placeholder="Submit Your Comments" style="color:black;"></textarea><span>
<p id="CommentEditError" style="display:none; color:red"></p>
</span>
<input type="hidden" value="{{ event.id }}" name="event_id">
<input type="hidden" value="" name="commentid" id="commentid">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default save" id="comment_edit" type="submit">Update</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Comments Section Ends-->
</div>
</div>
</div>
</div>
<div class="overview_form_block row marl justify-content-center" id="loading_spinner"
style="z-index: 10; left: 0; right: 0; vertical-align: middle; position: absolute; margin-top: 15%;">
<div class="spinner-border text-primary" style="width: 5em; height: 5em;" role="status">
<span class="sr-only">Processing File...</span>
</div>
</div>
<br>
</div>
</div>
</div>
<!-- ACCOUNTS VIEW ENDS -->
</div>
</div>
</div>
</div>
<br>
</div>
</div>
</div>
<!-- ACCOUNTS VIEW ENDS -->
{% endblock content %}
{% block js_block %}
<script type="text/javascript">
$('#loading_spinner').hide()
$("#comment_form").submit(function (e) {
e.preventDefault()
var formData = new FormData($("#comment_form")[0]);
$.ajax({
url: "{% url 'events:add_comment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentError").html(data.error).show()
} else {
d = new Date(data.commented_on);
$("#comments_div").prepend("<li class='list-group-item list-row' id='comment" + data.comment_id + "'>" +
"<div class='float-right right-container'>" +
"<div class='list-row-buttons btn-group float-right'>" +
"<button class='btn primary_btn btn-sm dropdown-toggle' data-toggle='dropdown' type='button'><span class='caret'></span>Actions</button>" +
"<ul class='dropdown-menu text-center'>" +
"<li><a class='action' onclick='edit_comment(" + data.comment_id + ")'>Edit</a></li>" +
"<li><a class='action' onclick='remove_comment(" + data.comment_id + ")''>Remove</a></li></ul></div></div>" +
"<div class='stream-post-container' id='comment_name" + data.comment_id + "'><pre>" + data.comment + "</pre></div>" +
"<div class='stream-container'><pre class='float-left'>" + data.commented_by + "</pre><pre class='float-right'>" + d.toLocaleString('en-US', { hour12: true }) + "</pre></div>"
)
$("#id_comments").val("")
alert("Comment Submitted")
$("#CommentError").html("")
}
}
});
});
function edit_comment(x) {
$('#Comments_Accounts_Modal').modal('show');
comment = $("#comment_name" + x).text()
$("#commentid").val(x)
$("#id_editcomment").val(comment)
$("#edit_file_field").html($("#comment_file_div" + x).clone())
}
$("#comment_edit").click(function (e) {
e.preventDefault()
var formData = new FormData($("#comment_edit_form")[0]);
$.ajax({
url: "{% url 'events:edit_comment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentEditError").html(data.error).show()
} else {
console.log($("#comment_name" + data.comment_id))
$("#comment_name" + data.comment_id).html('<pre>' + data.comment + '</pre>')
$('#Comments_Accounts_Modal').modal('hide');
$("#id_editcomment").val("")
$("#CommentEditError").html('')
}
}
})
})
/*Comment Edit Js Ends*/
function HideError(e) {
$("#CommentError").hide()
$("#CommentEditError").hide()
}
/*Comment Remove Js Start*/
function remove_comment(x) {
var con = confirm("Do you want to Delete it for Sure!?")
if (con == true) {
$.post('{% url "accounts:remove_comment" %}', {
"comment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#comment" + data.cid).remove()
}
})
}
}
function remove_attachment(x) {
const con = confirm("Do you want to Delete it for Sure!?");
if (con == true) {
$.post('{% url "leads:remove_attachment" %}', {
"attachment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#attachment" + data.aid).remove()
}
})
}
}
$("#attachment_form").submit(function (e) {
e.preventDefault()
const formData = new FormData($("#attachment_form")[0]);
$.ajax({
url: "{% url 'events:add_attachment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
beforeSend: function () {
$('#loading_spinner').show()
$('#opacity_block').addClass('opacity_block')
},
success: function (data) {
if (data.error) {
$("#AttachmentError").html(data.error).show()
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
} else {
d = new Date(data.created_on)
$("#attachment_div").prepend(
"<li class='list-group-item list-row' id='attachment" + data.attachment_id + "'>" +
"<div class='float-right right-container'><div class='list-row-buttons btn-group float-right'><button class='action btn primary_btn' onclick='remove_attachment(" + data.attachment_id + ")''>Remove</button></div></div>" +
"<div class='stream-post-container' id='attachment_name" + data.attachment_id + "'>" +
`${(data.file_type[0]).startsWith('image') ? `<img src=${data.attachment_url} width="80px" height="80px" class="mb-2">` : ''}` +
"<pre><span class='icon'><i style='font-size:1.5rem;' class='" + data.attachment_display + "'></i>&nbsp;&nbsp;</span>" + data.attachment + "&nbsp; &nbsp;<a href='" + data.download_url + "'>Download</a></pre></div>" +
"<div class='stream-container'><pre class='float-left'>" + data.created_by + "</pre><pre class='float-right date'>" + d.toLocaleString('en-US', { hour12: true }) + "</pre></div>" +
"</li>"
)
$("#AttachmentError").html("")
$("#id_attachments").val("")
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
setTimeout(() => {
alert("Attachment Saved")
}, 100)
// alert("Attachment Saved")
}
}
})
});
</script>
<script src="{% static 'js/bootstrap-suggest.js' %}"></script>
<script>
$('.mentions').suggest('@', {
data: {{ users_mention| safe}},
map: function (user) {
return {
value: user.username,
text: '<strong>' + user.username + '</strong>'
}
}
})
</script>
{% endblock js_block %}

View file

@ -0,0 +1,366 @@
{% extends 'sales/base.html' %}
{% load staticfiles %}
{% load paginate %}
{% load thumbnail %}
{% block extralinks %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
{% endblock %}
{% block content %}
<!-- main_container starts here -->
<div class="main_container">
<!-- heading_create starts here -->
<div class="row marl">
<div class="col-lg-12 text-right">
<span class="d-inline"><a class="primary_btn" href="{% url 'events:event_create'%}"><i class="fa fa-plus"></i>
Add New
Event</a></span>
</div>
</div>
<!-- heading_create ends here -->
<div class="filter_row list_filter_row row marl">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<form id="leads_filter" action="" method="POST">
<div class="card-body ">
<div class="card-title">Filters</div>
<div class="row marl">
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Name</label>
<input type="text" class="form-control" placeholder="Event Name" name="event_name"
value="{{request.POST.event_name}}">
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Created By</label>
<select name="created_by" id="" class="assigned_users_ form-control">
<option value="">
------------------</option>
{% for user in users %}
<option value="{{user.id}}" {% if user.id|slugify == request.POST.created_by %} selected=""
{% endif %}>
{{user.email}}</option>
{% endfor %}
</select>
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Assigned Users</label>
<select class="assigned_users form-control" name="assigned_to" multiple="multiple">
{% for user in users %}
<option value="{{user.id}}" {% if user.id|slugify in assigned_to %} selected="" {% endif %}>
{{user.email}}</option>
{% endfor %}
</select>
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Date of Meeting</label>
<input type="text" id="date_of_meeting" name="date_of_meeting" class="form-control"
value="{{request.POST.date_of_meeting}}">
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<!-- <label for="exampleInputEmail1">Total Amount</label>
<input type="number" class="form-control" placeholder="Total Amount" name="total_amount"
value="{{request.POST.total_amount}}"> -->
</div>
</div>
<input type="hidden" name="tab_status" id="tab_status">
<div class="filter_col col-2">
<div class="form-group buttons_row">
<button class="btn btn-primary save" type="submit">Search</button>
<a href="{% url 'events:events_list' %}" class="btn btn-default clear">Clear</a>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="col-md-12 col-lg-12 col-xl-12">
<div class="table_container_row row marl no-gutters">
<div class="col-md-12">
<!-- <ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="open-tab" data-toggle="tab" href="#open" role="tab" aria-controls="open"
aria-selected="true">Open ({{open_leads|length}})</a>
</li>
<li class="nav-item">
<a class="nav-link" id="close-tab" data-toggle="tab" href="#close" role="tab" aria-controls="close"
aria-selected="false">Closed ({{close_leads|length}})</a>
</li>
</ul> -->
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="open" role="tabpanel" aria-labelledby="open">
<div class="card">
<div class="card-body">
<div class="card-title text-right">
<span class="float-left">Events - {% if show_pageitems %}
{% show_pageitems %}{% else %}
{{ events|length }}{% endif %}</span>
<span class="filter_toggle">
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
</span>
</div>
<div class="table-responsive">
<table class="table ">
<thead>
{% if events|length > 0 %}
<tr>
<th width="5%">ID</th>
<th width="5%">Name</th>
<th width="5%">Contacts</th>
<th width="10%">Assigned To</th>
<th width="10%">Start Date</th>
<th width="10%">Date of Meeting</th>
<th width="10%">End Date</th>
<th width="5%">Created On</th>
<th width="5%">Created by</th>
<th width="10%">Actions</th>
</tr>
{% endif %}
</thead>
<tbody>
{% if per_page %}
{% paginate per_page events %}
{% else %}
{% paginate 10 events %}
{% endif %}
{% for event in events %}
<tr class="text-center">
<td scope="row">{{ forloop.counter }}</td>
<td><a href="{% url 'events:detail_view' event.id %}">
{{ event.name }} </a></td>
<td>
{% with contacts=event.contacts.all %}
{% if contacts %}
{% for contact in contacts %}
<div>
<a href="{% url 'contacts:view_contact' contact.id %}">{{contact}}</a>
</div>
{% empty %}
None
{% endfor %}
{% else %}
<div>
None
</div>
{% endif %}
{% endwith %}
</td>
<td>
{% with users=event.assigned_to.all %}
{% for user in users %}
{% if user.profile_pic %}
{% thumbnail user.profile_pic "40x40" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"
title="{{ user.email }}">
{% endthumbnail %}
{% else %}
<img src="{% static 'images/user.png' %}" title="{{ user.email }}" width="40" height="40">
{% endif %}
{% empty %}
None
{% endfor %}
{% endwith %}
</td>
<td>{{ event.start_date }} </td>
<td>{{ event.date_of_meeting }} </td>
<td>{{ event.end_date }} </td>
<td>{{ event.created_on|date:'d-m-Y' }}</td>
<td>
{% if event.created_by.profile_pic %}
{% thumbnail event.created_by.profile_pic "40x40" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"
title="{{ event.created_by }}" style="border-radius: 50%" />
{% endthumbnail %}
{% else %}
<img src="{% static 'images/user.png' %}" alt="Micro profile pic"
style="width: 40px;height: 40px;border-radius: 50%;" />
{% endif %}
</td>
<td class="actions">
{% if request.user == event.created_by or request.user.role == 'ADMIN' %}
<a href="{% url 'events:event_update' event.id %}" class="btn btn-success edit"><i
class="fas fa-pencil-alt"></i></a>
<a href="{% url 'events:event_delete' event.id %}"
class="btn btn-danger delete remove_document"><i class="fas fa-trash-alt"></i></a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% ifequal events|length 0 %}
<h6 class="text-center">No Events Found</h6>
{% endifequal %}
<div class="marl row text-center">
{% show_pages %}
</div>
</div>
</div>
</div>
<!-- second tab for closed and other statuses -->
<!--
<div class="tab-pane fade show active" id="open" role="tabpanel" aria-labelledby="open">
<div class="card">
<div class="card-body">
<div class="card-title text-right">
<span class="float-left">Open Leads - {% if show_pageitems %}
{% show_pageitems %}{% else %}
{{ open_leads|length }}{% endif %}</span>
<span class="filter_toggle">
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
</span>
</div>
<div class="table-responsive">
<table class="table ">
<thead>
{% if events|length > 0 %}
<tr>
<th width="5%">ID</th>
<th width="10%">Invoice Title</th>
<th width="8%">Invoice Number</th>
<th width="10%">Email</th>
<th width="10%">Status</th>
<th width="20%">Assigned To</th>
<th width="15%">Created by</th>
<th width="10%">Created On</th>
<th width="10%">Actions</th>
</tr>
{% endif %}
</thead>
<tbody>
{% if per_page %}
{% paginate per_page events %}
{% else %}
{% paginate 10 events %}
{% endif %}
{% for invoice in events %}
<tr class="text-center">
<td scope="row">{{ forloop.counter }}</td>
<td><a href="">
{{ invoice.invoice_title }} </a></td>
<td>{{ invoice.invoice_number }} </td>
<td>{{ invoice.email }} </td>
<td>{{ invoice.status }} </td>
<td>
{% with users=invoice.assigned_to.all %}
{% for user in users %}
{% if user.profile_pic %}
{% thumbnail user.profile_pic "40x40" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"
title="{{ user.email }}">
{% endthumbnail %}
{% else %}
<img src="{% static 'images/user.png' %}" title="{{ user.email }}" width="40" height="40">
{% endif %}
{% empty %}
Not Assigned to Any
{% endfor %}
{% endwith %}
</td>
<td>
{% if invoice.created_by.profile_pic %}
{% thumbnail invoice.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>{{ invoice.created_on }}</td>
<td class="actions">
<a href="" class="btn btn-success edit"><i
class="fas fa-pencil-alt"></i></a>
{% if request.user == lead.created_by or request.user.role == "ADMIN" or request.user.is_superuser %}
<a href=""
class="btn btn-danger delete remove_account"><i class="fas fa-trash-alt"></i></a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{%ifequal invoices|length 0%}
<h6 class="text-center">No Invoices Found</h6>
{%endifequal%}
<div class="marl row text-center">
{% show_pages %}
</div>
</div>
</div>
</div>
-->
<!-- second tab div for closed or another status -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('.assigned_users').select2();
$('#id_tag').select2();
$(".filter_toggle").click(function () {
$(".list_filter_row").toggle();
});
var date = new Date();
var today = new Date(date.getFullYear(), date.getMonth(), date.getDate());
$('#date_of_meeting').datetimepicker({
'format': 'YYYY-MM-DD',
minDate: today
})
});
search = "{{search}}"
if (search == 'True') {
$(".list_filter_row").show();
}
$("#close-tab").click(function (e) {
$("#tab_status").val('Closed')
})
$("#open-tab").click(function (e) {
$("#tab_status").val('Open')
})
tab_status = "{{tab_status}}"
if (tab_status == 'Closed') {
$("#close-tab").click()
} else {
$("#open-tab").click()
}
$('.delete').click(function (e) {
e.preventDefault()
url = $(this).attr('href')
if (!confirm('Are you sure you want to delete?'))
return;
window.location = $(this).attr('href')
});
$("a[rel='page']").click(function (e) {
e.preventDefault();
$('#leads_filter').attr("action", $(this).attr("href"));
$('#leads_filter').submit();
});
</script>
{% endblock js_block %}

3
events/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

21
events/urls.py Normal file
View file

@ -0,0 +1,21 @@
from django.urls import path
from events.views import *
app_name = 'events'
urlpatterns = [
path('', events_list, name='events_list'),
path('create/', event_create, name='event_create'),
path('detail/<int:event_id>/', event_detail_view, name='detail_view'),
path('edit/<int:event_id>/', event_update, name='event_update'),
path('delete/<int:event_id>/', event_delete, name='event_delete'),
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('attachment/add/', AddAttachmentView.as_view(), name="add_attachment"),
path('attachment/remove/', DeleteAttachmentsView.as_view(),
name="remove_attachment"),
]

356
events/views.py Normal file
View file

@ -0,0 +1,356 @@
from datetime import date, datetime, timedelta
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render, reverse
from django.views.generic import (CreateView, DeleteView, DetailView, FormView,
TemplateView, UpdateView, View)
from common.models import Attachments, Comment, User
from common.tasks import send_email_user_mentions
from events.forms import EventAttachmentForm, EventCommentForm, EventForm
from events.models import Event
from events.tasks import send_email
@login_required
def events_list(request):
if request.user.role == 'ADMIN' or request.user.is_superuser:
users = User.objects.all()
elif request.user.google.all():
users = User.objects.none()
elif request.user.role == 'USER':
users = User.objects.filter(role='ADMIN')
if request.method == 'GET':
context = {}
if request.user.role == 'ADMIN' or request.user.is_superuser:
events = Event.objects.all().distinct()
else:
events = Event.objects.filter(
Q(created_by=request.user) | Q(assigned_to=request.user)).distinct()
context['events'] = events.order_by('id')
# context['status'] = status
context['users'] = users
return render(request, 'events_list.html', context)
if request.method == 'POST':
context = {}
# context['status'] = status
context['users'] = users
events = Event.objects.filter()
if request.user.role == 'ADMIN' or request.user.is_superuser:
events = events
else:
events = events.filter(
Q(created_by=request.user) | Q(assigned_to=request.user)).distinct()
if request.POST.get('event_name', None):
events = events.filter(name__icontains=request.POST.get('event_name'))
if request.POST.get('created_by', None):
events = events.filter(
created_by__id=request.POST.get('created_by'))
if request.POST.getlist('assigned_to', None):
events = events.filter(
assigned_to__in=request.POST.getlist('assigned_to'))
context['assigned_to'] = request.POST.getlist('assigned_to')
if request.POST.get('date_of_meeting', None):
events = events.filter(date_of_meeting=request.POST.get('date_of_meeting'))
context['events'] = events.distinct().order_by('id')
return render(request, 'events_list.html', context)
@login_required
def event_create(request):
if request.method == 'GET':
context = {}
context["form"] = EventForm(request_user=request.user)
return render(request, 'event_create.html', context)
if request.method == 'POST':
form = EventForm(request.POST, request_user=request.user)
if form.is_valid():
start_date = form.cleaned_data.get('start_date')
end_date = form.cleaned_data.get('end_date')
# recurring_days
recurring_days = request.POST.getlist('recurring_days')
if form.cleaned_data.get('event_type') == 'Non-Recurring':
event = form.save(commit=False)
event.date_of_meeting = start_date
event.created_by = request.user
event.save()
form.save_m2m()
send_email.delay(event.id, domain=request.get_host(), protocol=request.scheme)
if form.cleaned_data.get('event_type') == 'Recurring':
delta = end_date - start_date
all_dates = []
required_dates = []
for day in range(delta.days + 1):
each_date = (start_date + timedelta(days=day))
if each_date.strftime("%A") in recurring_days:
required_dates.append(each_date)
for each in required_dates:
each = datetime.strptime(str(each), '%Y-%m-%d').date()
data = form.cleaned_data
event = Event.objects.create(
created_by=request.user, start_date=start_date, end_date=end_date,
name=data['name'], event_type=data['event_type'],
description=data['description'], start_time=data['start_time'],
end_time=data['end_time'], date_of_meeting=each
)
event.contacts.add(*request.POST.getlist('contacts'))
event.assigned_to.add(*request.POST.getlist('assigned_to'))
send_email.delay(event.id, domain=request.get_host(), protocol=request.scheme)
return JsonResponse({'error': False, 'success_url': reverse('events:events_list')})
else:
return JsonResponse({'error': True, 'errors': form.errors, })
@login_required
def event_detail_view(request, event_id):
event = get_object_or_404(Event, pk=event_id)
if not (request.user.role == 'ADMIN' or request.user.is_superuser or event.created_by == request.user or request.user in event.assigned_to.all()):
raise PermissionDenied
if request.method == 'GET':
context = {}
context['event'] = event
context['attachments'] = event.events_attachment.all()
context['comments'] = event.events_comments.all()
if request.user.is_superuser or request.user.role == 'ADMIN':
context['users_mention'] = list(
User.objects.all().values('username'))
elif request.user != event.created_by:
context['users_mention'] = [
{'username': event.created_by.username}]
else:
context['users_mention'] = list(
event.assigned_to.all().values('username'))
return render(request, 'event_detail.html', context)
@login_required
def event_update(request, event_id):
event_obj = get_object_or_404(Event, pk=event_id)
if not (request.user.role == 'ADMIN' or request.user.is_superuser or event_obj.created_by == request.user or request.user in event_obj.assigned_to.all()):
raise PermissionDenied
if request.method == 'GET':
context = {}
context["event_obj"] = event_obj
context["form"] = EventForm(
instance=event_obj, request_user=request.user)
selected_recurring_days = Event.objects.filter(
name=event_obj.name).values_list('date_of_meeting', flat=True)
selected_recurring_days = [day.strftime(
'%A') for day in selected_recurring_days]
context['selected_recurring_days'] = selected_recurring_days
return render(request, 'event_create.html', context)
if request.method == 'POST':
form = EventForm(request.POST, instance=event_obj,
request_user=request.user)
if form.is_valid():
start_date = form.cleaned_data.get('start_date')
end_date = form.cleaned_data.get('end_date')
# recurring_days
# recurring_days = request.POST.getlist('days')
if form.cleaned_data.get('event_type') == 'Non-Recurring':
event = form.save(commit=False)
event.date_of_meeting = start_date
# event.created_by = request.user
event.save()
form.save_m2m()
send_email.delay(event.id, domain=request.get_host(), protocol=request.scheme)
if form.cleaned_data.get('event_type') == 'Recurring':
event = form.save(commit=False)
event.save()
form.save_m2m()
send_email.delay(event.id, domain=request.get_host(), protocol=request.scheme)
# event.contacts.add(*request.POST.getlist('contacts'))
# event.assigned_to.add(*request.POST.getlist('assigned_to'))
return JsonResponse({'error': False, 'success_url': reverse('events:events_list')})
else:
return JsonResponse({'error': True, 'errors': form.errors, })
@login_required
def event_delete(request, event_id):
event = get_object_or_404(Event, pk=event_id)
if not (request.user.role == 'ADMIN' or request.user.is_superuser or event.created_by == request.user):
raise PermissionDenied
event.delete()
return redirect('events:events_list')
class AddCommentView(LoginRequiredMixin, CreateView):
model = Comment
form_class = EventCommentForm
http_method_names = ["post"]
def post(self, request, *args, **kwargs):
self.object = None
self.event = get_object_or_404(
Event, id=request.POST.get('event_id'))
if (
request.user == self.event.created_by or request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
return self.form_invalid(form)
data = {
'error': "You don't have permission to comment for this account."}
return JsonResponse(data)
def form_valid(self, form):
comment = form.save(commit=False)
comment.commented_by = self.request.user
comment.event = self.event
comment.save()
comment_id = comment.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'events', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"comment_id": comment.id, "comment": comment.comment,
"commented_on": comment.commented_on,
"commented_by": comment.commented_by.email
})
def form_invalid(self, form):
return JsonResponse({"error": form['comment'].errors})
class UpdateCommentView(LoginRequiredMixin, View):
http_method_names = ["post"]
def post(self, request, *args, **kwargs):
self.comment_obj = get_object_or_404(
Comment, id=request.POST.get("commentid"))
if request.user == self.comment_obj.commented_by:
form = EventCommentForm(request.POST, instance=self.comment_obj)
if form.is_valid():
return self.form_valid(form)
return self.form_invalid(form)
data = {'error': "You don't have permission to edit this comment."}
return JsonResponse(data)
def form_valid(self, form):
self.comment_obj.comment = form.cleaned_data.get("comment")
self.comment_obj.save(update_fields=["comment"])
comment_id = self.comment_obj.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'events', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"comment_id": self.comment_obj.id,
"comment": self.comment_obj.comment,
})
def form_invalid(self, form):
return JsonResponse({"error": form['comment'].errors})
class DeleteCommentView(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
self.object = get_object_or_404(
Comment, id=request.POST.get("comment_id"))
if request.user == self.object.commented_by:
self.object.delete()
data = {"cid": request.POST.get("comment_id")}
return JsonResponse(data)
data = {'error': "You don't have permission to delete this comment."}
return JsonResponse(data)
class AddAttachmentView(LoginRequiredMixin, CreateView):
model = Attachments
form_class = EventAttachmentForm
http_method_names = ["post"]
def post(self, request, *args, **kwargs):
self.object = None
self.event = get_object_or_404(
Event, id=request.POST.get('event_id'))
if (
request.user == self.event.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
return self.form_invalid(form)
data = {
'error': "You don't have permission to add attachment \
for this account."}
return JsonResponse(data)
def form_valid(self, form):
attachment = form.save(commit=False)
attachment.created_by = self.request.user
attachment.file_name = attachment.attachment.name
attachment.event = self.event
attachment.save()
return JsonResponse({
"attachment_id": attachment.id,
"attachment": attachment.file_name,
"attachment_url": attachment.attachment.url,
"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,
"file_type": attachment.file_type()
})
def form_invalid(self, form):
return JsonResponse({"error": form['attachment'].errors})
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'
):
self.object.delete()
data = {"acd": request.POST.get("attachment_id")}
return JsonResponse(data)
data = {
'error': "You don't have permission to delete this attachment."}
return JsonResponse(data)

0
invoices/__init__.py Normal file
View file

3
invoices/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
invoices/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class InvoicesConfig(AppConfig):
name = 'invoices'

99
invoices/forms.py Normal file
View file

@ -0,0 +1,99 @@
from django import forms
from invoices.models import Invoice
from common.models import User, Comment, Attachments, Address
from django.db.models import Q
class InvoiceForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
request_user = kwargs.pop('request_user', None)
assigned_users = kwargs.pop('assigned_to', [])
super(InvoiceForm, self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs = {"class": "form-control"}
field.required = False
if request_user.role == 'ADMIN' or request_user.is_superuser:
self.fields['assigned_to'].queryset = User.objects.all()
elif request_user.google.all():
self.fields['assigned_to'].queryset = User.objects.none()
elif request_user.role == 'USER':
self.fields['assigned_to'].queryset = User.objects.filter(
role='ADMIN')
else:
pass
self.fields['phone'].widget.attrs.update({
'placeholder': '+91-123-456-7890'})
self.fields['invoice_title'].required = True
self.fields['invoice_number'].required = True
self.fields['currency'].required = True
self.fields['email'].required = True
self.fields['total_amount'].required = True
self.fields['due_date'].required = True
def clean_quantity(self):
quantity = self.cleaned_data.get('quantity')
if quantity in [None, '']:
raise forms.ValidationError('This field is required')
return quantity
def clean_invoice_number(self):
invoice_number = self.cleaned_data.get('invoice_number')
if Invoice.objects.filter(invoice_number=invoice_number).exclude(id=self.instance.id).exists():
raise forms.ValidationError(
'Invoice with this Invoice Number already exists.')
return invoice_number
class Meta:
model = Invoice
fields = ('invoice_title', 'invoice_number',
'from_address', 'to_address', 'name',
'email', 'phone', 'status', 'assigned_to',
'quantity', 'rate', 'total_amount',
'currency', 'details', 'due_date'
)
class InvoiceCommentForm(forms.ModelForm):
comment = forms.CharField(max_length=64, required=True)
class Meta:
model = Comment
fields = ('comment', 'task', 'commented_by')
class InvoiceAttachmentForm(forms.ModelForm):
attachment = forms.FileField(max_length=1001, required=True)
class Meta:
model = Attachments
fields = ('attachment', 'task')
class InvoiceAddressForm(forms.ModelForm):
class Meta:
model = Address
fields = ('address_line', 'street', 'city',
'state', 'postcode', 'country')
def __init__(self, *args, **kwargs):
super(InvoiceAddressForm, self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs = {"class": "form-control"}
self.fields['address_line'].widget.attrs.update({
'placeholder': 'Address Line'})
self.fields['street'].widget.attrs.update({
'placeholder': 'Street'})
self.fields['city'].widget.attrs.update({
'placeholder': 'City'})
self.fields['state'].widget.attrs.update({
'placeholder': 'State'})
self.fields['postcode'].widget.attrs.update({
'placeholder': 'Postcode'})
self.fields["country"].choices = [
("", "--Country--"), ] + list(self.fields["country"].choices)[1:]

View file

@ -0,0 +1,47 @@
# Generated by Django 2.1.7 on 2019-05-23 09:48
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import phonenumber_field.modelfields
class Migration(migrations.Migration):
initial = True
dependencies = [
('common', '0013_auto_20190508_1540'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Invoice',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('invoice_title', models.CharField(max_length=50, verbose_name='Invoice Title')),
('invoice_number', models.CharField(max_length=50, verbose_name='Invoice Number')),
('name', models.CharField(max_length=100, verbose_name='Name')),
('email', models.EmailField(max_length=254, verbose_name='Email')),
('quantity', models.PositiveIntegerField(default=0)),
('rate', models.DecimalField(decimal_places=2, default=0, max_digits=12)),
('total_amount', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True)),
('currency', models.CharField(blank=True, choices=[('AED', 'AED, Dirham'), ('AFN', 'AFN, Afghani'), ('ALL', 'ALL, Lek'), ('AMD', 'AMD, Dram'), ('ANG', 'ANG, Guilder'), ('AOA', 'AOA, Kwanza'), ('ARS', 'ARS, Peso'), ('AUD', 'AUD, Dollar'), ('AWG', 'AWG, Guilder'), ('AZN', 'AZN, Manat'), ('BAM', 'BAM, Marka'), ('BBD', 'BBD, Dollar'), ('BDT', 'BDT, Taka'), ('BGN', 'BGN, Lev'), ('BHD', 'BHD, Dinar'), ('BIF', 'BIF, Franc'), ('BMD', 'BMD, Dollar'), ('BND', 'BND, Dollar'), ('BOB', 'BOB, Boliviano'), ('BRL', 'BRL, Real'), ('BSD', 'BSD, Dollar'), ('BTN', 'BTN, Ngultrum'), ('BWP', 'BWP, Pula'), ('BYR', 'BYR, Ruble'), ('BZD', 'BZD, Dollar'), ('CAD', 'CAD, Dollar'), ('CDF', 'CDF, Franc'), ('CHF', 'CHF, Franc'), ('CLP', 'CLP, Peso'), ('CNY', 'CNY, Yuan Renminbi'), ('COP', 'COP, Peso'), ('CRC', 'CRC, Colon'), ('CUP', 'CUP, Peso'), ('CVE', 'CVE, Escudo'), ('CZK', 'CZK, Koruna'), ('DJF', 'DJF, Franc'), ('DKK', 'DKK, Krone'), ('DOP', 'DOP, Peso'), ('DZD', 'DZD, Dinar'), ('EGP', 'EGP, Pound'), ('ERN', 'ERN, Nakfa'), ('ETB', 'ETB, Birr'), ('EUR', 'EUR, Euro'), ('FJD', 'FJD, Dollar'), ('FKP', 'FKP, Pound'), ('GBP', 'GBP, Pound'), ('GEL', 'GEL, Lari'), ('GHS', 'GHS, Cedi'), ('GIP', 'GIP, Pound'), ('GMD', 'GMD, Dalasi'), ('GNF', 'GNF, Franc'), ('GTQ', 'GTQ, Quetzal'), ('GYD', 'GYD, Dollar'), ('HKD', 'HKD, Dollar'), ('HNL', 'HNL, Lempira'), ('HRK', 'HRK, Kuna'), ('HTG', 'HTG, Gourde'), ('HUF', 'HUF, Forint'), ('IDR', 'IDR, Rupiah'), ('ILS', 'ILS, Shekel'), ('INR', 'INR, Rupee'), ('IQD', 'IQD, Dinar'), ('IRR', 'IRR, Rial'), ('ISK', 'ISK, Krona'), ('JMD', 'JMD, Dollar'), ('JOD', 'JOD, Dinar'), ('JPY', 'JPY, Yen'), ('KES', 'KES, Shilling'), ('KGS', 'KGS, Som'), ('KHR', 'KHR, Riels'), ('KMF', 'KMF, Franc'), ('KPW', 'KPW, Won'), ('KRW', 'KRW, Won'), ('KWD', 'KWD, Dinar'), ('KYD', 'KYD, Dollar'), ('KZT', 'KZT, Tenge'), ('LAK', 'LAK, Kip'), ('LBP', 'LBP, Pound'), ('LKR', 'LKR, Rupee'), ('LRD', 'LRD, Dollar'), ('LSL', 'LSL, Loti'), ('LTL', 'LTL, Litas'), ('LVL', 'LVL, Lat'), ('LYD', 'LYD, Dinar'), ('MAD', 'MAD, Dirham'), ('MDL', 'MDL, Leu'), ('MGA', 'MGA, Ariary'), ('MKD', 'MKD, Denar'), ('MMK', 'MMK, Kyat'), ('MNT', 'MNT, Tugrik'), ('MOP', 'MOP, Pataca'), ('MRO', 'MRO, Ouguiya'), ('MUR', 'MUR, Rupee'), ('MVR', 'MVR, Rufiyaa'), ('MWK', 'MWK, Kwacha'), ('MXN', 'MXN, Peso'), ('MYR', 'MYR, Ringgit'), ('MZN', 'MZN, Metical'), ('NAD', 'NAD, Dollar'), ('NGN', 'NGN, Naira'), ('NIO', 'NIO, Cordoba'), ('NOK', 'NOK, Krone'), ('NPR', 'NPR, Rupee'), ('NZD', 'NZD, Dollar'), ('OMR', 'OMR, Rial'), ('PAB', 'PAB, Balboa'), ('PEN', 'PEN, Sol'), ('PGK', 'PGK, Kina'), ('PHP', 'PHP, Peso'), ('PKR', 'PKR, Rupee'), ('PLN', 'PLN, Zloty'), ('PYG', 'PYG, Guarani'), ('QAR', 'QAR, Rial'), ('RON', 'RON, Leu'), ('RSD', 'RSD, Dinar'), ('RUB', 'RUB, Ruble'), ('RWF', 'RWF, Franc'), ('SAR', 'SAR, Rial'), ('SBD', 'SBD, Dollar'), ('SCR', 'SCR, Rupee'), ('SDG', 'SDG, Pound'), ('SEK', 'SEK, Krona'), ('SGD', 'SGD, Dollar'), ('SHP', 'SHP, Pound'), ('SLL', 'SLL, Leone'), ('SOS', 'SOS, Shilling'), ('SRD', 'SRD, Dollar'), ('SSP', 'SSP, Pound'), ('STD', 'STD, Dobra'), ('SYP', 'SYP, Pound'), ('SZL', 'SZL, Lilangeni'), ('THB', 'THB, Baht'), ('TJS', 'TJS, Somoni'), ('TMT', 'TMT, Manat'), ('TND', 'TND, Dinar'), ('TOP', 'TOP, Paanga'), ('TRY', 'TRY, Lira'), ('TTD', 'TTD, Dollar'), ('TWD', 'TWD, Dollar'), ('TZS', 'TZS, Shilling'), ('UAH', 'UAH, Hryvnia'), ('UGX', 'UGX, Shilling'), ('USD', '$, Dollar'), ('UYU', 'UYU, Peso'), ('UZS', 'UZS, Som'), ('VEF', 'VEF, Bolivar'), ('VND', 'VND, Dong'), ('VUV', 'VUV, Vatu'), ('WST', 'WST, Tala'), ('XAF', 'XAF, Franc'), ('XCD', 'XCD, Dollar'), ('XOF', 'XOF, Franc'), ('XPF', 'XPF, Franc'), ('YER', 'YER, Rial'), ('ZAR', 'ZAR, Rand'), ('ZMK', 'ZMK, Kwacha'), ('ZWL', 'ZWL, Dollar')], max_length=3, null=True)),
('phone', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True)),
('created_on', models.DateTimeField(auto_now_add=True)),
('amount_due', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True)),
('amount_paid', models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True)),
('is_email_sent', models.BooleanField(default=False)),
('status', models.CharField(choices=[('Draft', 'Draft'), ('Sent', 'Sent'), ('Paid', 'Paid'), ('Cancel', 'Cancel'), ('Pending', 'Pending')], default='Draft', max_length=15)),
('assigned_to', models.ManyToManyField(related_name='invoice_assigned_to', to=settings.AUTH_USER_MODEL)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_created_by', to=settings.AUTH_USER_MODEL)),
('from_address', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_from_address', to='common.Address')),
('to_address', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='invoice_to_address', to='common.Address')),
],
options={
'verbose_name': 'Invoice',
'verbose_name_plural': 'Invoices',
},
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.1.7 on 2019-05-24 05:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('invoices', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='invoice',
name='status',
field=models.CharField(choices=[('Draft', 'Draft'), ('Sent', 'Sent'), ('Paid', 'Paid'), ('Pending', 'Pending'), ('Cancel', 'Cancel')], default='Draft', max_length=15),
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 2.1.7 on 2019-05-27 10:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('invoices', '0002_auto_20190524_1113'),
]
operations = [
migrations.AddField(
model_name='invoice',
name='details',
field=models.TextField(blank=True, null=True, verbose_name='Details'),
),
migrations.AddField(
model_name='invoice',
name='due_date',
field=models.DateField(blank=True, null=True),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.1.7 on 2019-06-03 13:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('invoices', '0003_auto_20190527_1620'),
]
operations = [
migrations.AlterField(
model_name='invoice',
name='status',
field=models.CharField(choices=[('Draft', 'Draft'), ('Sent', 'Sent'), ('Paid', 'Paid'), ('Pending', 'Pending'), ('Cancelled', 'Cancel')], default='Draft', max_length=15),
),
]

View file

95
invoices/models.py Normal file
View file

@ -0,0 +1,95 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from common.models import Address, User
from common.utils import CURRENCY_CODES
from phonenumber_field.modelfields import PhoneNumberField
class Invoice(models.Model):
"""Model definition for Invoice."""
INVOICE_STATUS = (
('Draft', 'Draft'),
('Sent', 'Sent'),
('Paid', 'Paid'),
('Pending', 'Pending'),
('Cancelled', 'Cancel'),
)
invoice_title = models.CharField(_('Invoice Title'), max_length=50)
invoice_number = models.CharField(_('Invoice Number'), max_length=50)
from_address = models.ForeignKey(
Address, related_name='invoice_from_address', on_delete=models.SET_NULL, null=True)
to_address = models.ForeignKey(
Address, related_name='invoice_to_address', on_delete=models.SET_NULL, null=True)
name = models.CharField(_('Name'), max_length=100)
email = models.EmailField(_('Email'))
assigned_to = models.ManyToManyField(
User, related_name='invoice_assigned_to')
# quantity is the number of hours worked
quantity = models.PositiveIntegerField(default=0)
# rate is the rate charged
rate = models.DecimalField(default=0, max_digits=12, decimal_places=2)
# total amount is product of rate and quantity
total_amount = models.DecimalField(
blank=True, null=True, max_digits=12, decimal_places=2)
currency = models.CharField(
max_length=3, choices=CURRENCY_CODES, blank=True, null=True)
phone = PhoneNumberField(null=True, blank=True)
created_on = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(
User, related_name='invoice_created_by',
on_delete=models.SET_NULL, null=True)
amount_due = models.DecimalField(
blank=True, null=True, max_digits=12, decimal_places=2)
amount_paid = models.DecimalField(
blank=True, null=True, max_digits=12, decimal_places=2)
is_email_sent = models.BooleanField(default=False)
status = models.CharField(choices=INVOICE_STATUS,
max_length=15, default="Draft")
details = models.TextField(_('Details'), null=True, blank=True)
due_date = models.DateField(blank=True, null=True)
class Meta:
"""Meta definition for Invoice."""
verbose_name = 'Invoice'
verbose_name_plural = 'Invoices'
def __str__(self):
"""Unicode representation of Invoice."""
return self.invoice_number
def formatted_total_amount(self):
return self.currency + ' ' + str(self.total_amount)
def formatted_rate(self):
return self.rate + ' ' + self.currency
def formatted_total_quantity(self):
return self.currency + ' ' + 'Hours'
def is_draft(self):
if self.status == 'Draft':
return True
else:
return False
def is_sent(self):
if self.status == 'Sent' and self.is_email_sent == False:
return True
else:
return False
def is_resent(self):
if self.status == 'Sent' and self.is_email_sent == True:
return True
else:
return False
def is_paid_or_cancelled(self):
if self.status in ['Paid', 'Cancelled']:
return True
else:
return False

67
invoices/tasks.py Normal file
View file

@ -0,0 +1,67 @@
from celery.task import task
from django.conf import settings
from django.core.mail import EmailMessage
from django.shortcuts import reverse
from django.template.loader import render_to_string
from common.models import User
from invoices.models import Invoice
@task
def send_email(invoice_id, domain='demo.django-crm.io', protocol='http'):
invoice = Invoice.objects.filter(id=invoice_id).first()
if invoice:
subject = 'Invoice : {0}'.format(invoice.invoice_title)
context = {}
context['invoice_title'] = invoice.invoice_title
context['invoice_id'] = invoice_id
context['invoice_created_by'] = invoice.created_by
context["url"] = protocol + '://' + domain + \
reverse('invoices:invoice_details', args=(invoice.id,))
recipients = invoice.assigned_to.all()
if recipients.count() > 0:
for recipient in recipients:
context['user'] = recipient.email
html_content = render_to_string(
'assigned_to_email_template.html', context=context)
msg = EmailMessage(
subject=subject, body=html_content, to=[recipient.email, ])
msg.content_subtype = "html"
msg.send()
@task
def send_invoice_email(invoice_id, domain='demo.django-crm.io', protocol='http'):
invoice = Invoice.objects.filter(id=invoice_id).first()
if invoice:
subject = 'CRM Invoice : {0}'.format(invoice.invoice_title)
recipients = [invoice.email]
context = {}
context['invoice'] = invoice
context['url'] = protocol + '://' + domain + \
reverse('invoices:invoice_details', args=(invoice.id,))
html_content = render_to_string(
'invoice_detail_email.html', context=context)
msg = EmailMessage(subject=subject, body=html_content,
to=recipients)
msg.content_subtype = "html"
msg.send()
@task
def send_invoice_email_cancel(invoice_id, domain='demo.django-crm.io', protocol='http'):
invoice = Invoice.objects.filter(id=invoice_id).first()
if invoice:
subject = 'CRM Invoice : {0}'.format(invoice.invoice_title)
recipients = [invoice.email]
context = {}
context['invoice'] = invoice
context['url'] = protocol + '://' + domain + \
reverse('invoices:invoice_details', args=(invoice.id,))
html_content = render_to_string(
'invoice_cancelled.html', context=context)
msg = EmailMessage(subject=subject, body=html_content,
to=recipients)
msg.content_subtype = "html"
msg.send()

View file

@ -0,0 +1,18 @@
{% extends 'root_email_template.html' %}
{% block heading %}
Hello {{ user }}
{% endblock heading %}
{% block content_body %}
<h2>You have been assigned to invoice {{invoice_title}} by {{invoice_created_by}}</h2>
{% endblock content_body %}
{% block button_link %}
<div style="margin-bottom:20px">
<a href="{{url}}"
style="display:inline-block;width:170px;background:#38abdd;padding:10px;text-align:center;color:#fff;font-size:1rem;font-weight:600;margin:0px auto;margin-bottom:20px;border-radius:5px;text-decoration:none;display:block">click here</a>
</div>
{% endblock button_link %}

View file

@ -0,0 +1,20 @@
{% extends 'root_email_template.html' %}
{% block content_body %}
<h3>
Invoice has been Cancelled
</h3>
{% endblock content_body %}
{% comment %}
{% block button_link %}
<div style="margin-bottom:20px">
<a href="{{url}}"
style="display:inline-block;width:170px;background:#38abdd;padding:10px;text-align:center;color:#fff;font-size:1rem;font-weight:600;margin:0px auto;margin-bottom:20px;border-radius:5px;text-decoration:none;display:block">click
here</a>
</div>
{% endblock button_link %}
{% endcomment %}

View file

@ -0,0 +1,17 @@
{% extends 'root_email_template.html' %}
{% block content_body %}
<h3>
An Invoice has been generated
</h3>
{% endblock content_body %}
{% block button_link %}
<div style="margin-bottom:20px">
<a href="{{url}}"
style="display:inline-block;width:170px;background:#38abdd;padding:10px;text-align:center;color:#fff;font-size:1rem;font-weight:600;margin:0px auto;margin-bottom:20px;border-radius:5px;text-decoration:none;display:block">click
here</a>
</div>
{% endblock button_link %}

View file

@ -0,0 +1,203 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Muli:300,400,600,700" rel="stylesheet">
<body style="padding:0;margin:0;font-family: 'Roboto', sans-serif;">
<table border="0" cellpadding="0" cellspacing="0" id="backgroundTable"
style="height:auto !important; margin:0; padding:30px 0;font-size:13px; width:800px !important; margin:0px auto; background:#fff;color:#353535;font-family: 'Muli', sans-serif;">
<tbody>
<tr>
<td colspan="2" align="right" style="padding:10px 0;">
<span
style="color:#F9210B;text-transform: uppercase;font-size: 1.5rem;line-height: initial;font-weight:bold;">{{ invoice.invoice_number }}</span>
</td>
</tr>
<tr>
<td align="left" style="padding:15px 0;">
<address style="line-height:20px;font-style:normal;">
{% if invoice.from_address.address_line %}
{{ invoice.from_address.address_line }}<br>
{% endif %}
{% if invoice.from_address.street %}
{{ invoice.from_address.street }},
{% endif %}
{% if invoice.from_address.city %}
{{ invoice.from_address.city }}<br>
{% endif %}
{% if invoice.from_address.state %}
{{ invoice.from_address.state }},
{% endif %}
{% if invoice.from_address.postcode %}
{{ invoice.from_address.postcode }}<br>
{% endif %}
{% if invoice.from_address.country %}
{{ invoice.from_address.country }}
{% endif %}
</address>
{% if invoice.created_on %}
<div style="margin-top:20px;">
<strong>Date</strong>
<p style="margin:2px 0;">{{ invoice.created_on|date:'d-m-Y' }}</p>
</div>
{% endif %}
</td>
<td align="right" style="padding:15px 0;">
<ul style="margin:0;padding:0;line-height:20px;">
<li style="list-style:none;">{{ invoice.email }}</li>
{% if invoice.name %}
<li style="list-style:none;">{{ invoice.name }}</li>
{% endif %}
{% if invoice.phone %}
<li style="list-style:none;">{{ invoice.phone }}</li>
{% endif %}
</ul>
<address style="line-height:20px;font-style:normal;">
{% if invoice.to_address.address_line %}
{{ invoice.to_address.address_line }}<br>
{% endif %}
{% if invoice.to_address.street %}
{{ invoice.to_address.street }},
{% endif %}
{% if invoice.to_address.city %}
{{ invoice.to_address.city }}<br>
{% endif %}
{% if invoice.to_address.state %}
{{ invoice.to_address.state }},
{% endif %}
{% if invoice.to_address.postcode %}
{{ invoice.to_address.postcode }}<br>
{% endif %}
{% if invoice.to_address.country %}
{{ invoice.to_address.country }}
{% endif %}
</address>
{% if invoice.due_date %}
<div style="margin-top:20px;">
<strong>Due Date</strong>
<p style="margin:2px 0;">{{ invoice.due_date|date:'d-m-Y' }}</p>
</div>
{% endif %}
</td>
</tr>
<tr>
<td colspan="2" style="border-top:1px solid #ddd;padding:10px 0;">
&nbsp;
</td>
</tr>
<tr>
<td colspan="2" style="padding:5px 50px;line-height:20px;">
<center>
{% if invoice.invoice_title %}
<div class="content" style="margin-bottom:20px 0;">
<h1>{{ invoice.invoice_title|upper }}</h2>
</div>
{% endif %}
</center>
</td>
</tr>
<tr>
<td colspan="2" style="padding:5px 50px;line-height:20px;">
<center>
{% if invoice.details %}
<div class="content" style="margin-bottom:20px 0;">
<p>{{ invoice.details }}</p>
</div>
{% endif %}
</center>
</td>
</tr>
<tr>
<td colspan="2">
<table class="table"
style="width:100%;border-radius: 5px; background: #f4f8f9; padding: 5px; width: 100%; background: #f4f8f9; display: table; border-collapse: separate; border-spacing: 0 6px; border-radius: 0;">
<thead style="border-collapse: separate; border-spacing: 0 6px;">
<!--
<tr>
<th
style="padding: 5px 10px;border-top: none;text-transform: uppercase;font-size:13px;border: none;text-align:left;">
ID</th>
<th
style="padding: 5px 10px;border-top: none;text-transform: uppercase;font-size:13px;border: none;text-align:left;">
Item Description</th>
<th
style="padding: 5px 10px;border-top: none;text-transform: uppercase;font-size:13px;border: none;text-align:left;">
Total</th>
</tr>
-->
</thead>
<tbody style="font-size:13px;font-weight:400;">
<!--
<tr>
<td style="padding:8px 10px;vertical-align: middle;background: #fff;font-weight: 550;border: none;">
</td>
<td style="padding:8px 10px;vertical-align: middle;background: #fff;font-weight: 550;border: none;">
</td>
<td style="padding:8px 10px;vertical-align: middle;background: #fff;font-weight: 550;border: none;">
</td>
</tr>
-->
<tr class="sub_total">
{% if invoice.quantity %}
<td colspan="2"
style="padding:8px 10px;vertical-align: middle;background: #dedede;font-weight: 800;border: none;">
<b>Invoice Quantity</b></td>
<td style="padding:8px 10px;vertical-align: middle;background: #dedede;font-weight: 800;border: none;">
{{ invoice.quantity }}</td>
{% endif %}
</tr>
<tr class="sub_total">
{% if invoice.rate %}
<td colspan="2"
style="padding:8px 10px;vertical-align: middle;background: #dedede;font-weight: 800;border: none;">
<b>Invoice Rate</b></td>
<td style="padding:8px 10px;vertical-align: middle;background: #dedede;font-weight: 800;border: none;">
{{ invoice.rate }}</td>
{% endif %}
</tr>
<tr class="sub_total">
<td colspan="2"
style="padding:8px 10px;vertical-align: middle;background: #dedede;font-weight: 800;border: none;">
<b>Invoice Total</b></td>
<td style="padding:8px 10px;vertical-align: middle;background: #dedede;font-weight: 800;border: none;">
{{ invoice.formatted_total_amount }}</td>
</tr>
<!--
<tr class="sub_total">
<td colspan="2"
style="padding:8px 10px;vertical-align: middle;background: #dedede;font-weight: 800;border: none;">
<b>Amount Paid</b></td>
<td style="padding:8px 10px;vertical-align: middle;background: #dedede;font-weight: 800;border: none;">
{{ invoice.formatted_amount_paid }}</td>
</tr>
<tr class="total">
<td colspan="2"
style="padding:8px 10px;vertical-align: middle;background: #cccccc;font-weight: 800;border: none;">
<b>Amount Due</b></td>
<td style="padding:8px 10px;vertical-align: middle;background: #cccccc;font-weight: 800;border: none;">
{{ invoice.formatted_amount_due }}</td>
</tr>
-->
</tbody>
</table>
</td>
</tr>
<tr>
{% if invoice.footer_text %}
<td colspan="2" style="padding:20px 50px;line-height:20px;">
<div class="content" style="margin:20px 0;">
{{ invoice.footer_text|safe }}
</div>
</td>
{% endif %}
</tr>
</tbody>
</table>
</body>

View file

@ -0,0 +1,277 @@
{% extends 'sales/base.html' %}
{% load staticfiles %}
{% block breadcrumb %}
{% block extralinks %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css"
href="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/themes/start/jquery-ui.css" />
{% endblock %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'invoices:invoices_list' %}">Invoices</a></li>
<li class="breadcrumb-item active">{% if invoice_obj %}Edit{% else %}Create{% endif %}</li>
</ol>
</nav>
{% endblock %}
{% block content %}
<form id="formid" action='' method="POST" novalidate enctype="multipart/form-data">
<div class="overview_form_block row marl justify-content-center">
<div class="col-md-9">
<div class="card">
<div class="card-body">
<div class="card-title text-center">
{% if invoice_obj %}EDIT{% else %}CREATE{% endif %} INVOICE
</div>
<div class="row marl">
<div class="col-md-4">
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Invoice
Title{% if form.invoice_title.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
{{ form.invoice_title }}
<span class="error" id="id__invoice_title">{{ form.invoice_title.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1">Invoice Number{% if form.invoice_number.field.required %}<span
class="error">*</span>{% endif %}</label>
{{ form.invoice_number }}
<span class="error" id="id__invoice_number">{{ form.invoice_number.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Name{% if form.name.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.name }}
<span class="error" id="id__name">{{ form.name.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Email{% if form.email.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.email }}
<span class="error" id="id__email">{{ form.email.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Currency{% if form.currency.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.currency }}
<span class="error" id="id__currency">{{ form.currency.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Phone{% if form.phone.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.phone }}
<span class="error" id="id__phone">{{ form.phone.errors }}</span>
</div>
</div>
</div>
<div class="col-md-4">
<div class="filter_col billing_block col-md-12" style="padding: 0px;">
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="exampleInputEmail1" class="required">Quantity{% if form.quantity.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.quantity }}
<span class="error" id="id__quantity">{{ form.quantity.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="id_contacts" class="required">Rate{% if form.rate.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.rate }}
<span class="error" id="id__rate">{{ form.rate.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="id_contacts" class="required">Total Amount{% if form.total_amount.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.total_amount }}
<span class="error" id="id__total_amount">{{ form.total_amount.errors }}</span>
</div>
</div>
<div class="filter_col billing_block col-md-12" style="padding: 0px;">
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="id_contacts" class="required">Status{% if form.status.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
{{ form.status }}
<span class="error" id="id__status">{{ form.status.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="exampleInputEmail1">Assign To</label>
{{form.assigned_to}}
<span class="error" id="id__assigned_to">{{ form.assigned_to.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="exampleInputEmail1">Due Date {% if form.due_date.field.required %}<span
class="error_marker" style="color:red">*</span>{% endif %}</label>
<div id="datepicker1" class='input-group date'>
{{ form.due_date }}
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
<span class="error" id="id__due_date">{{ form.due_date.errors }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="filter_col col-md-12">
<div class="form-group ">
<label for="exampleInputEmail1">Details</label>
{{form.details}}
<span class="error" id="id__details">{{ form.details.errors }}</span>
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1">From Address</label>
{{ from_address_form.address_line }}
<span class="error" id="id__address_line">{{ from_address_form.address_line.errors }}</span>
</div>
{{ from_address_form.street }}
<span class="error" id="id__street">{{ from_address_form.street.errors }}</span>
<div class="row" style="margin-top: 10px;">
<div class="col-md-4">
{{ from_address_form.city }}
{{ from_address_form.city.errors }}
<span class="error" id="id__city">{{ to_address_form.city.errors }}</span>
</div>
<div class="col-md-4">
{{ from_address_form.state }}
<span class="error" id="id__state">{{ from_address_form.state.errors }}</span>
</div>
<div class="col-md-4">
{{ from_address_form.postcode }}
<span class="error" id="id__postcode">{{ from_address_form.postcode.errors }}</span>
</div>
<div class="col-md-12" style="margin-top: 10px;">
{{ from_address_form.country }}
<span class="error" id="id__country">{{ from_address_form.country.errors }}</span>
</div>
</div>
<br>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1">To Address</label>
{{ to_address_form.address_line }}
<span class="error" id="id__address_line_">{{ to_address_form.address_line.errors }}</span>
</div>
{{ to_address_form.street }}
<span class="error" id="id__street_">{{ to_address_form.street.errors }}</span>
<div class="row" style="margin-top: 10px;">
<div class="col-md-4">
{{ to_address_form.city }}
{{ to_address_form.city.errors }}
<span class="error" id="id__city_">{{ to_address_form.city.errors }}</span>
</div>
<div class="col-md-4">
{{ to_address_form.state }}
<span class="error" id="id__state_">{{ to_address_form.state.errors }}</span>
</div>
<div class="col-md-4">
{{ to_address_form.postcode }}
<span class="error" id="id__postcode_">{{ to_address_form.postcode.errors }}</span>
</div>
<div class="col-md-12" style="margin-top: 10px;">
{{ to_address_form.country }}
<span class="error" id="id__country_">{{ to_address_form.country.errors }}</span>
</div>
</div>
<br>
</div>
</div>
</div>
<div class="col-md-12">
<div class="col-md-12">
<div class="row marl buttons_row form_btn_row text-center">
<button class="btn btn-default save" type="submit">Save</button>
<a href="{% url 'invoices:invoices_list' %}" class="btn btn-default clear"
id="create_user_cancel">Cancel</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- <input type="hidden" id="save_new_form" name="savenewform"> -->
</div>
<!-- overview ends here -->
</form>
{% endblock %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
{% block js_block %}
<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js'></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.js"></script>
<script src="https://malsup.github.io/jquery.form.js"></script>
<script type="text/javascript">
// $(document).ready(function() {
// $('.assigned_users').select2();
// });
$(document).ready(function () {
$("#id_contacts").select2();
$("#id_assigned_to").select2();
$('#id_currency').select2();
var date = new Date();
var today = new Date(date.getFullYear(), date.getMonth(), date.getDate());
$('#id_due_date').datetimepicker({
'format': 'YYYY-MM-DD',
minDate: today
})
$('#id_quantity').keyup(calculate);
$('#id_rate').keyup(calculate);
function calculate(e) {
$('#id_total_amount').val($('#id_quantity').val() * $('#id_rate').val());
}
});
$('form#formid').ajaxForm({
type: 'POST',
dataType: 'json',
url: ".",
data: $('#formid').serialize(),
success: function (data) {
if (data.error) {
$('.error').html('')
for (var key in data.errors) {
$('#id__' + key).html("<p>" + data.errors[key][0] + "</p>");
};
for (var key in data.from_address_errors) {
$('#id__' + key).html("<p>" + data.from_address_errors[key][0] + "</p>");
};
for (var key in data.to_address_errors) {
$('#id__' + key + '_').html("<p>" + data.to_address_errors[key][0] + "</p>");
console.log($('#id__' + key + '_'))
};
$('#submit_btn').removeAttr('disabled')
}
else {
window.location = data.success_url;
}
}
});
</script>
{% endblock js_block %}

View file

@ -0,0 +1,520 @@
{% extends 'sales/base.html' %}
{% load staticfiles %}
{% load thumbnail %}
{% block breadcrumb %}
{% block extralinks %}
<link rel="stylesheet" href="{% static 'css/bootstrap-suggest.css' %}">
<style>
.opacity_block {
opacity: 0.2;
}
</style>
{% endblock extralinks %}
<!-- breadcrumb starts here -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'invoices:invoices_list' %}">Invoice</a></li>
<li class="active breadcrumb-item">{{invoice.invoice_title}}</li>
</ol>
</nav>
{% endblock breadcrumb %}
{% block content %}
<!-- Task VIEW STARTS -->
<div class="main_container" id="maincontainer">
<div class="overview_form_block row marl justify-content-center">
<div class="col-md-8" id="opacity_block">
<div class="card">
<div class="card-body" id="datashow">
<div class="card-title text-right">
<h5>
<span class="float-left title">Overview</span>
<span style="margin-top: 0px">
<div class="dropdown buttons_row">
<a href="{% url 'invoices:invoice_download' invoice.id %}" id="download_btn_" class="btn btn-info"
style="padding: 0.12em 0.6em;border-radius: 0;">Download</a>
<button class="btn primary_btn dropdown-toggle" type="button" data-toggle="dropdown">Actions
<span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="{% url 'invoices:invoice_edit' invoice.id %}">Edit</a></li>
</ul>
</div>
</span>
</h5>
</div>
<div class="row marl">
<div class="col-md-4">
{% if invoice.invoice_title %}
<div class="filter_col col-md-12" id="iname">
<div class="form-group">
<label class="acc_field_label" for="id_name" data-name="name">Invoice Title</label>
<div class="account_field" id="account_name" data-name="name">{{ invoice.invoice_title }}</div>
</div>
</div>
{% endif %}
{% if invoice.invoice_number %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_phone" data-name="name">Invoice Number</label>
<div class="account_field" id="account_phone" data-name="name">{{ invoice.invoice_number }}</div>
</div>
</div>
{% endif %}
{% if invoice.name %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_status" data-name="name">Name</label>
<div class="account_field" id="account_status" data-name="name">{{ invoice.name }}</div>
</div>
</div>
{% endif %}
{% if invoice.email %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_status" data-name="name">Email</label>
<div class="account_field" id="account_status" data-name="name">{{ invoice.email }}</div>
</div>
</div>
{% endif %}
{% if invoice.phone %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_status" data-name="name">Phone</label>
<div class="account_field" id="account_status" data-name="name">{{ invoice.phone }}</div>
</div>
</div>
{% endif %}
</div>
<div class="col-md-4">
{% if invoice.quantity %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_due_date" data-name="name">Quantity</label>
<div class="account_field" id="account_email" data-name="name">{{ invoice.quantity }}</div>
</div>
</div>
{% endif %}
{% if invoice.rate %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_email" data-name="name">Rate</label>
<div class="account_field" id="account_email" data-name="name">{{ invoice.rate }}</div>
</div>
</div>
{% endif %}
{% if invoice.total_amount %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_email" data-name="name">Total Amount</label>
<div class="account_field" id="account_email" data-name="name">{{ invoice.total_amount }}</div>
</div>
</div>
{% endif %}
{% if invoice.currency %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_website" data-name="name">Currency</label>
<div class="account_field" id="account_website" data-name="name">{{ invoice.currency }}</div>
</div>
</div>
{% endif %}
{% if invoice.status %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_website" data-name="name">Status</label>
<div class="account_field" id="account_website" data-name="name">{{ invoice.status }}</div>
</div>
</div>
{% endif %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_website" data-name="name">Email Status</label>
{% if invoice.is_email_sent %}
<div class="account_field" id="account_website" data-name="name">Email was sent.</div>
{% else %}
<div class="account_field" id="account_website" data-name="name">No Email sent.</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4">
{% if invoice.due_date %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_website" data-name="name">Due Date</label>
<div class="account_field" id="account_website" data-name="name">{{ invoice.due_date }}</div>
</div>
</div>
{% endif %}
{% if invoice.details %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_website" data-name="name">Details</label>
<div class="account_field" id="account_website" data-name="name">{{ invoice.details }}</div>
</div>
</div>
{% endif %}
{% if invoice.assigned_to.all %}
<div class="filter_col col-md-12">
<div class="form-group">
<label class="acc_field_label" for="id_website" data-name="name">Assigned To</label>
{% for user in invoice.assigned_to.all %}
<div class="account_field" id="account_website" data-name="name">{{ user }}</div>
{% endfor %}
</div>
</div>
{% endif %}
<div class="filter_col col-md-12">
<div class="form-group">
{% if invoice.from_address.address_line or invoice.from_address.street or invoice.from_address.city or invoice.from_address.state or invoice.from_address.postcode or invoice.from_address.country %}
<label class="lead_field_label" for="id_address" data-name="name">From Billing Address</label>
<div class="lead_field" id="lead_address" data-name="name">
{{invoice.from_address.get_complete_address}}
</div>
{% endif %}
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
{% if invoice.to_address.address_line or invoice.to_address.street or invoice.to_address.city or invoice.to_address.state or invoice.to_address.postcode or invoice.to_address.country %}
<label class="lead_field_label" for="id_address" data-name="name">To Billing Address</label>
<div class="lead_field" id="lead_address" data-name="name">
{{invoice.to_address.get_complete_address}}
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-12">
<div class="created_information">
Created by <b>{{ invoice.created_by }}</b> created on
<b>{{ invoice.created_on }}</b>
</div>
</div>
<div class="col-md-12" style="padding:0;">
<div class="col-md-12 " style="padding:0">
<!-- Attachment Section start -->
<div class="card-body mar-top" id="datashow">
<div class="card-title view-pad">
<h5>Attachments</h5>
</div>
<div class="row marl">
<div class="col-md-12">
<form id="attachment_form" method="POST" enctype="multipart/form-data">
<div class="form-group ">
<input type="file" class="form-control-file" cols="40" id="id_attachments" name="attachment">
<div>
<p id="AttachmentError" style="display:none; color:red"></p>
</div>
<br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;"
id="attachment_submit">Save</button>
</div>
</div>
<input type="hidden" value="{{invoice.id}}" name="invoice_id">
</form>
<ul class="list-group" id="attachment_div">
{% for attachment in attachments %}
<li class="list-group-item list-row" id="attachment{{attachment.id}}">
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == attachment.created_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="action btn primary_btn"
onclick="remove_attachment({{attachment.id}})">Remove</button>
</div>
</div>
{% endif %}
<div class="stream-post-container" id="attachment_name{{attachment.id}}">
{% if attachment.attachment %}
{% if 'image' in attachment.file_type %}
{% thumbnail attachment.attachment "80x80" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" class="mb-2">
{% endthumbnail %}
{% endif %}
{% endif %}
<pre> <span class="icon"><i style="font-size:1.5rem;" class="{{attachment.get_file_type_display}}"></i> </span>{{attachment.file_name}} <a href="{% url 'common:download_attachment' attachment.id %}">Download</a></pre>
</div>
<div class="stream-container">
<pre class="float-left">Uploaded by :{{ attachment.created_by }}</pre>
<pre class="float-right date">{{ attachment.created_on|date:'m/d/Y, h:i A' }}</pre>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<!-- Attachment Section end -->
<!-- Comments Section starts-->
<div class="card-body" id="datashow">
<div class="card-title">
<h5>Comments</h5>
</div>
<div class="row marl">
<div class="col-md-12">
<form id="comment_form" method="POST" enctype="multipart/form-data">
<div class="form-group">
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_comments"
name="comment" placeholder="Submit Your Comments Here"></textarea>
<div>
<p id="CommentError" style="display:none; color:red"></p>
</div>
</br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;" id="comment_submit"
type="submit">Submit</button>
</div>
</div>
<input type="hidden" value="{{ invoice.id }}" name="invoice_id">
</form>
<ul class="list-group" id="comments_div">
{% for comment in comments %}
<li class="list-group-item list-row" id="comment{{comment.id}}">
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == comment.commented_by %}
<div class="list-row-buttons btn-group float-right">
<button class="btn primary_btn btn-sm dropdown-toggle" data-toggle="dropdown"
type="button">Actions<span class="caret"></span></button>
<ul class="dropdown-menu text-center">
<li>
<a class="action" onclick="edit_comment({{comment.id}})">Edit</a>
</li>
<li>
<a class="action" onclick="remove_comment({{comment.id}})">Remove</a>
</li>
</ul>
</div>
{% endif %}
<div class="stream-post-container" id="comment_name{{comment.id}}">
<pre>{{ comment.comment }}</pre>
</div>
<div class="stream-container">
<pre class="float-left">{{ comment.commented_by }}</pre>
<pre class="float-right date">{{ comment.commented_on|date:'m/d/Y, h:i:s A' }}</pre>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
<div class="modal fade" id="Comments_Accounts_Modal" role="dialog" data-keyboard="false"
data-backdrop="static">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Update Your Comment</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</button>
</div>
<form id="comment_edit_form" method="POST">
<div class="modal-body">
<div class="form-group">
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_editcomment"
name="comment" placeholder="Submit Your Comments" style="color:black;"></textarea><span>
<p id="CommentEditError" style="display:none; color:red"></p>
</span>
<input type="hidden" value="{{ invoice.id }}" name="invoice_id">
<input type="hidden" value="" name="commentid" id="commentid">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default save" id="comment_edit" type="submit">Update</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Comments Section Ends-->
</div>
</div>
</div>
</div>
<br>
</div>
</div>
<div class="overview_form_block row marl justify-content-center" id="loading_spinner"
style="z-index: 10; left: 0; right: 0; vertical-align: middle; position: absolute; margin-top: 15%;">
<div class="spinner-border text-primary" style="width: 5em; height: 5em;" role="status">
<span class="sr-only">Processing File...</span>
</div>
</div>
</div>
<!-- ACCOUNTS VIEW ENDS -->
</div>
</div>
</div>
</div>
<br>
</div>
</div>
</div>
<!-- ACCOUNTS VIEW ENDS -->
{% endblock content %}
{% block js_block %}
<script type="text/javascript">
$('#loading_spinner').hide()
$("#comment_form").submit(function (e) {
e.preventDefault()
var formData = new FormData($("#comment_form")[0]);
$.ajax({
url: "{% url 'invoices:add_comment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentError").html(data.error).show()
} else {
d = new Date(data.commented_on);
$("#comments_div").prepend("<li class='list-group-item list-row' id='comment" + data.comment_id + "'>" +
"<div class='float-right right-container'>" +
"<div class='list-row-buttons btn-group float-right'>" +
"<button class='btn primary_btn btn-sm dropdown-toggle' data-toggle='dropdown' type='button'><span class='caret'></span>Actions</button>" +
"<ul class='dropdown-menu text-center'>" +
"<li><a class='action' onclick='edit_comment(" + data.comment_id + ")'>Edit</a></li>" +
"<li><a class='action' onclick='remove_comment(" + data.comment_id + ")''>Remove</a></li></ul></div></div>" +
"<div class='stream-post-container' id='comment_name" + data.comment_id + "'><pre>" + data.comment + "</pre></div>" +
"<div class='stream-container'><pre class='float-left'>" + data.commented_by + "</pre><pre class='float-right'>" + d.toLocaleString('en-US', { hour12: true }) + "</pre></div>"
)
$("#id_comments").val("")
alert("Comment Submitted")
$("#CommentError").html("")
}
}
});
});
function edit_comment(x) {
$('#Comments_Accounts_Modal').modal('show');
comment = $("#comment_name" + x).text()
$("#commentid").val(x)
$("#id_editcomment").val(comment)
$("#edit_file_field").html($("#comment_file_div" + x).clone())
}
$("#comment_edit").click(function (e) {
e.preventDefault()
var formData = new FormData($("#comment_edit_form")[0]);
$.ajax({
url: "{% url 'invoices:edit_comment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentEditError").html(data.error).show()
} else {
console.log($("#comment_name" + data.comment_id))
$("#comment_name" + data.comment_id).html('<pre>' + data.comment + '</pre>')
$('#Comments_Accounts_Modal').modal('hide');
$("#id_editcomment").val("")
$("#CommentEditError").html('')
}
}
})
})
/*Comment Edit Js Ends*/
function HideError(e) {
$("#CommentError").hide()
$("#CommentEditError").hide()
}
/*Comment Remove Js Start*/
function remove_comment(x) {
var con = confirm("Do you want to Delete it for Sure!?")
if (con == true) {
$.post('{% url "accounts:remove_comment" %}', {
"comment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#comment" + data.cid).remove()
}
})
}
}
function remove_attachment(x) {
const con = confirm("Do you want to Delete it for Sure!?");
if (con == true) {
$.post('{% url "leads:remove_attachment" %}', {
"attachment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#attachment" + data.aid).remove()
}
})
}
}
$("#attachment_form").submit(function (e) {
e.preventDefault()
const formData = new FormData($("#attachment_form")[0]);
$.ajax({
url: "{% url 'invoices:add_attachment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
beforeSend: function () {
$('#loading_spinner').show()
$('#opacity_block').addClass('opacity_block')
},
success: function (data) {
if (data.error) {
$("#AttachmentError").html(data.error).show()
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
} else {
d = new Date(data.created_on)
$("#attachment_div").prepend(
"<li class='list-group-item list-row' id='attachment" + data.attachment_id + "'>" +
"<div class='float-right right-container'><div class='list-row-buttons btn-group float-right'><button class='action btn primary_btn' onclick='remove_attachment(" + data.attachment_id + ")''>Remove</button></div></div>" +
"<div class='stream-post-container' id='attachment_name" + data.attachment_id + "'>" +
`${(data.file_type[0]).startsWith('image') ? `<img src=${data.attachment_url} width="80px" height="80px" class="mb-2">` : ''}` +
"<pre><span class='icon'><i style='font-size:1.5rem;' class='" + data.attachment_display + "'></i>&nbsp;&nbsp;</span>" + data.attachment + "&nbsp; &nbsp;<a href='" + data.download_url + "'>Download</a></pre></div>" +
"<div class='stream-container'><pre class='float-left'>" + data.created_by + "</pre><pre class='float-right date'>" + d.toLocaleString('en-US', { hour12: true }) + "</pre></div>" +
"</li>"
)
$("#AttachmentError").html("")
$("#id_attachments").val("")
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
setTimeout(() => {
alert("Attachment Saved")
}, 100)
// alert("Attachment Saved")
}
}
})
});
</script>
<script src="{% static 'js/bootstrap-suggest.js' %}"></script>
<script>
$('.mentions').suggest('@', {
data: {{ users_mention| safe}},
map: function (user) {
return {
value: user.username,
text: '<strong>' + user.username + '</strong>'
}
}
})
</script>
{% endblock js_block %}

View file

@ -0,0 +1,369 @@
{% extends 'sales/base.html' %}
{% load staticfiles %}
{% load paginate %}
{% load thumbnail %}
{% block extralinks %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
{% endblock %}
{% block content %}
<!-- main_container starts here -->
<div class="main_container">
<!-- heading_create starts here -->
<div class="row marl">
<div class="col-lg-12 text-right">
<span class="d-inline"><a class="primary_btn" href="{% url 'invoices:invoices_create'%}"><i
class="fa fa-plus"></i>
Add New
Invoice</a></span>
</div>
</div>
<!-- heading_create ends here -->
<div class="filter_row list_filter_row row marl">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<form id="leads_filter" action="" method="POST">
<div class="card-body ">
<div class="card-title">Filters</div>
<div class="row marl">
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Invoice Title or Number</label>
<input type="text" class="form-control" placeholder="Invoice Title or Number"
name="invoice_title_number" value="{{request.POST.invoice_title_number}}">
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Created By</label>
<select name="created_by" id="" class="assigned_users_ form-control">
<option value="">
------------------</option>
{% for user in users %}
<option value="{{user.id}}" {% if user.id|slugify == request.POST.created_by %} selected="" {% endif %}>
{{user.email}}</option>
{% endfor %}
</select>
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Assigned Users</label>
<select class="assigned_users form-control" name="assigned_to" multiple="multiple">
{% for user in users %}
<option value="{{user.id}}" {% if user.id|slugify in assigned_to %} selected="" {% endif %}>
{{user.email}}</option>
{% endfor %}
</select>
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Status</label>
<select class="form-control" id="id_status" name="status">
<option value="">--Status of Invoice--</option>
{% for each_status in status %}
<option value="{{each_status.0}}" {% if request.POST.status %}
{% ifequal each_status.0 request.POST.status %}selected{% endifequal %}{% endif %}>
{{each_status.1}} </option>
{% endfor%}
</select>
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Total Amount</label>
<input type="number" class="form-control" placeholder="Total Amount" name="total_amount"
value="{{request.POST.total_amount}}">
</div>
</div>
<input type="hidden" name="tab_status" id="tab_status">
<div class="filter_col col-2">
<div class="form-group buttons_row">
<button class="btn btn-primary save" type="submit">Search</button>
<a href="{% url 'invoices:invoices_list' %}" class="btn btn-default clear">Clear</a>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="col-md-12 col-lg-12 col-xl-12">
<div class="table_container_row row marl no-gutters">
<div class="col-md-12">
<!-- <ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="open-tab" data-toggle="tab" href="#open" role="tab" aria-controls="open"
aria-selected="true">Open ({{open_leads|length}})</a>
</li>
<li class="nav-item">
<a class="nav-link" id="close-tab" data-toggle="tab" href="#close" role="tab" aria-controls="close"
aria-selected="false">Closed ({{close_leads|length}})</a>
</li>
</ul> -->
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="open" role="tabpanel" aria-labelledby="open">
<div class="card">
<div class="card-body">
<div class="card-title text-right">
<span class="float-left">Invoices - {% if show_pageitems %}
{% show_pageitems %}{% else %}
{{ invoices|length }}{% endif %}</span>
<span class="filter_toggle">
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
</span>
</div>
<div class="table-responsive">
<table class="table ">
<thead>
{% if invoices|length > 0 %}
<tr>
<th width="5%">ID</th>
<th width="5%">Invoice Title</th>
<th width="5%">Invoice Number</th>
<th width="5%">Email</th>
<th width="5%">Status</th>
<th width="10%">Assigned To</th>
<th width="5%">Created by</th>
<th width="5%">Created On</th>
<th width="10%">Actions</th>
</tr>
{% endif %}
</thead>
<tbody>
{% if per_page %}
{% paginate per_page invoices %}
{% else %}
{% paginate 10 invoices %}
{% endif %}
{% for invoice in invoices %}
<tr class="text-center">
<td scope="row">{{ forloop.counter }}</td>
<td><a href="{% url 'invoices:invoice_details' invoice.id %}">
{{ invoice.invoice_title }} </a></td>
<td>{{ invoice.invoice_number }} </td>
<td>{{ invoice.email }} </td>
<td>{{ invoice.status }} </td>
<td>
{% with users=invoice.assigned_to.all %}
{% for user in users %}
{% if user.profile_pic %}
{% thumbnail user.profile_pic "40x40" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"
title="{{ user.email }}">
{% endthumbnail %}
{% else %}
<img src="{% static 'images/user.png' %}" title="{{ user.email }}" width="40" height="40">
{% endif %}
{% empty %}
None
{% endfor %}
{% endwith %}
</td>
<td>
{% if invoice.created_by.profile_pic %}
{% thumbnail invoice.created_by.profile_pic "40x40" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"
title="{{ lead.created_by }}" style="border-radius: 50%" />
{% endthumbnail %}
{% else %}
<img src="{% static 'images/user.png' %}" alt="Micro profile pic"
style="width: 40px;height: 40px;border-radius: 50%;" />
{% endif %}
</td>
<td>{{ invoice.created_on|date:'d-m-Y' }}</td>
<td class="actions">
<a href="{% url 'invoices:invoice_edit' invoice.id %}" class="btn btn-success edit"><i
class="fas fa-pencil-alt"></i></a>
{% if request.user == invoice.created_by or request.user.role == "ADMIN" or request.user.is_superuser %}
<a href="{% url 'invoices:invoice_delete' invoice.id %}"
class="btn btn-danger delete remove_account"><i class="fas fa-trash-alt"></i></a>
{% if invoice.is_draft %}
<a href="{% url 'invoices:invoice_send_mail' invoice.id %}" title="send mail"
class="btn btn-info"><i class="fas fa-paper-plane"></i></a>
{% endif %}
{% if invoice.is_sent == True %}
<a href="{% url 'invoices:invoice_send_mail' invoice.id %}" title="re send mail"
class="btn btn-secondary"><i class="fas fa-paper-plane"></i></a>
<a href="{% url 'invoices:invoice_change_status_paid' invoice.id %}" title="Paid"
class="btn btn-info"><i class="fas fa-check-square"></i></a>
<a href="{% url 'invoices:invoice_change_status_cancelled' invoice.id %}" title="Cancel"
class="btn btn-danger " style="background-color: red;"><i class="fas fa-window-close"></i></a>
{% endif %}
{% if invoice.is_resent %}
<a href="{% url 'invoices:invoice_change_status_paid' invoice.id %}" title="Paid"
class="btn btn-info"><i class="fas fa-check-square"></i></a>
<a href="{% url 'invoices:invoice_change_status_cancelled' invoice.id %}" title="Cancel"
class="btn btn-danger " style="background-color: red;"><i class="fas fa-window-close"></i></a>
{% endif %}
{% if invoice.is_paid_or_cancelled == True %}
<!-- <a title="re send mail" class="btn btn-secondary">no actions</a> -->
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{%ifequal invoices|length 0%}
<h6 class="text-center">No Invoices Found</h6>
{%endifequal%}
<div class="marl row text-center">
{% show_pages %}
</div>
</div>
</div>
</div>
<!-- second tab for closed and other statuses -->
<!--
<div class="tab-pane fade show active" id="open" role="tabpanel" aria-labelledby="open">
<div class="card">
<div class="card-body">
<div class="card-title text-right">
<span class="float-left">Open Leads - {% if show_pageitems %}
{% show_pageitems %}{% else %}
{{ open_leads|length }}{% endif %}</span>
<span class="filter_toggle">
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
</span>
</div>
<div class="table-responsive">
<table class="table ">
<thead>
{% if invoices|length > 0 %}
<tr>
<th width="5%">ID</th>
<th width="10%">Invoice Title</th>
<th width="8%">Invoice Number</th>
<th width="10%">Email</th>
<th width="10%">Status</th>
<th width="20%">Assigned To</th>
<th width="15%">Created by</th>
<th width="10%">Created On</th>
<th width="10%">Actions</th>
</tr>
{% endif %}
</thead>
<tbody>
{% if per_page %}
{% paginate per_page invoices %}
{% else %}
{% paginate 10 invoices %}
{% endif %}
{% for invoice in invoices %}
<tr class="text-center">
<td scope="row">{{ forloop.counter }}</td>
<td><a href="">
{{ invoice.invoice_title }} </a></td>
<td>{{ invoice.invoice_number }} </td>
<td>{{ invoice.email }} </td>
<td>{{ invoice.status }} </td>
<td>
{% with users=invoice.assigned_to.all %}
{% for user in users %}
{% if user.profile_pic %}
{% thumbnail user.profile_pic "40x40" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"
title="{{ user.email }}">
{% endthumbnail %}
{% else %}
<img src="{% static 'images/user.png' %}" title="{{ user.email }}" width="40" height="40">
{% endif %}
{% empty %}
Not Assigned to Any
{% endfor %}
{% endwith %}
</td>
<td>
{% if invoice.created_by.profile_pic %}
{% thumbnail invoice.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>{{ invoice.created_on }}</td>
<td class="actions">
<a href="" class="btn btn-success edit"><i
class="fas fa-pencil-alt"></i></a>
{% if request.user == lead.created_by or request.user.role == "ADMIN" or request.user.is_superuser %}
<a href=""
class="btn btn-danger delete remove_account"><i class="fas fa-trash-alt"></i></a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{%ifequal invoices|length 0%}
<h6 class="text-center">No Invoices Found</h6>
{%endifequal%}
<div class="marl row text-center">
{% show_pages %}
</div>
</div>
</div>
</div>
-->
<!-- second tab div for closed or another status -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$('.assigned_users').select2();
$('#id_tag').select2();
$(".filter_toggle").click(function () {
$(".list_filter_row").toggle();
});
});
search = "{{search}}"
if (search == 'True') {
$(".list_filter_row").show();
}
$("#close-tab").click(function (e) {
$("#tab_status").val('Closed')
})
$("#open-tab").click(function (e) {
$("#tab_status").val('Open')
})
tab_status = "{{tab_status}}"
if (tab_status == 'Closed') {
$("#close-tab").click()
} else {
$("#open-tab").click()
}
$('.delete').click(function (e) {
e.preventDefault()
url = $(this).attr('href')
if (!confirm('Are you sure you want to delete?'))
return;
window.location = $(this).attr('href')
});
$("a[rel='page']").click(function (e) {
e.preventDefault();
$('#leads_filter').attr("action", $(this).attr("href"));
$('#leads_filter').submit();
});
</script>
{% endblock js_block %}

3
invoices/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

26
invoices/urls.py Normal file
View file

@ -0,0 +1,26 @@
from django.urls import path
from invoices.views import *
app_name = 'invoices'
urlpatterns = [
path('', invoices_list, name='invoices_list'),
path('create/', invoices_create, name='invoices_create'),
path('detail/<int:invoice_id>/', invoice_details, name='invoice_details'),
path('edit/<int:invoice_id>/', invoice_edit, name='invoice_edit'),
path('delete/<int:invoice_id>/', invoice_delete, name='invoice_delete'),
path('download/<int:invoice_id>/', invoice_download, name='invoice_download'),
path('send-mail/<int:invoice_id>/', invoice_send_mail, name='invoice_send_mail'),
path('cancelled-mail/<int:invoice_id>/', invoice_change_status_cancelled, name='invoice_change_status_cancelled'),
path('paid-mail/<int:invoice_id>/', invoice_change_status_paid, name='invoice_change_status_paid'),
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('attachment/add/', AddAttachmentView.as_view(), name="add_attachment"),
path('attachment/remove/', DeleteAttachmentsView.as_view(),
name="remove_attachment"),
]

415
invoices/views.py Normal file
View file

@ -0,0 +1,415 @@
import io
import os
import pdfkit
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.http.response import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render, reverse
from django.template.loader import render_to_string
from django.views.generic import (CreateView, DeleteView, DetailView, FormView,
TemplateView, UpdateView, View)
from common.models import Address, Attachments, Comment, User
from common.tasks import send_email_user_mentions
from invoices.forms import (InvoiceAddressForm, InvoiceAttachmentForm,
InvoiceCommentForm, InvoiceForm)
from invoices.models import Invoice
from invoices.tasks import send_email, send_invoice_email, send_invoice_email_cancel
@login_required
def invoices_list(request):
if request.user.role == 'ADMIN' or request.user.is_superuser:
users = User.objects.all()
elif request.user.google.all():
users = User.objects.none()
elif request.user.role == 'USER':
users = User.objects.filter(role='ADMIN')
status = (
('Draft', 'Draft'),
('Sent', 'Sent'),
('Paid', 'Paid'),
('Pending', 'Pending'),
('Cancel', 'Cancel'),
)
if request.method == 'GET':
context = {}
if request.user.role == 'ADMIN' or request.user.is_superuser:
invoices = Invoice.objects.all().distinct()
else:
invoices = Invoice.objects.filter(
Q(created_by=request.user) | Q(assigned_to=request.user)).distinct()
context['invoices'] = invoices.order_by('id')
context['status'] = status
context['users'] = users
return render(request, 'invoices_list.html', context)
if request.method == 'POST':
context = {}
context['status'] = status
context['users'] = users
invoices = Invoice.objects.filter()
if request.user.role == 'ADMIN' or request.user.is_superuser:
invoices = invoices
else:
invoices = invoices.filter(
Q(created_by=request.user) | Q(assigned_to=request.user)).distinct()
if request.POST.get('invoice_title_number', None):
invoices = invoices.filter(
Q(invoice_title__icontains=request.POST.get('invoice_title_number')) |
Q(invoice_number__icontains=request.POST.get('invoice_title_number')))
if request.POST.get('created_by', None):
invoices = invoices.filter(
created_by__id=request.POST.get('created_by'))
if request.POST.getlist('assigned_to', None):
invoices = invoices.filter(
assigned_to__in=request.POST.getlist('assigned_to'))
context['assigned_to'] = request.POST.getlist('assigned_to')
if request.POST.get('status', None):
invoices = invoices.filter(status=request.POST.get('status'))
if request.POST.get('total_amount', None):
invoices = invoices.filter(
total_amount=request.POST.get('total_amount'))
context['invoices'] = invoices.distinct().order_by('id')
return render(request, 'invoices_list.html', context)
@login_required
def invoices_create(request):
if request.method == 'GET':
context = {}
context["form"] = InvoiceForm(request_user=request.user)
# "prefix" use case in form, both from address and to address use the
# same model and same model form, so the name attribute in the form will
# be same for the both forms and the address will be same for both
# shipping and to address, so use prefix parameter to distinguish the
# forms, don't remove this comment
context["from_address_form"] = InvoiceAddressForm(
prefix='from')
context["to_address_form"] = InvoiceAddressForm(prefix='to')
return render(request, 'invoices_create.html', context)
if request.method == 'POST':
form = InvoiceForm(request.POST, request_user=request.user)
from_address_form = InvoiceAddressForm(
request.POST, prefix='from')
to_address_form = InvoiceAddressForm(request.POST, prefix='to')
if form.is_valid() and from_address_form.is_valid() and to_address_form.is_valid():
from_address_obj = from_address_form.save()
to_address_obj = to_address_form.save()
invoice_obj = form.save(commit=False)
invoice_obj.created_by = request.user
invoice_obj.from_address = from_address_obj
invoice_obj.to_address = to_address_obj
invoice_obj.save()
form.save_m2m()
kwargs = {'domain': request.get_host(), 'protocol': request.scheme}
send_email.delay(invoice_obj.id, **kwargs)
return JsonResponse({'error': False, 'success_url': reverse('invoices:invoices_list')})
else:
return JsonResponse({'error': True, 'errors': form.errors,
'from_address_errors': from_address_form.errors,
'to_address_errors': to_address_form.errors})
@login_required
def invoice_details(request, invoice_id):
invoice = get_object_or_404(Invoice.objects.select_related(
'from_address', 'to_address'), pk=invoice_id)
if not (request.user.role == 'ADMIN' or request.user.is_superuser or invoice.created_by == request.user or request.user in invoice.assigned_to.all()):
raise PermissionDenied
if request.method == 'GET':
context = {}
context['invoice'] = invoice
context['attachments'] = invoice.invoice_attachment.all()
context['comments'] = invoice.invoice_comments.all()
if request.user.is_superuser or request.user.role == 'ADMIN':
context['users_mention'] = list(
User.objects.all().values('username'))
elif request.user != invoice.created_by:
context['users_mention'] = [
{'username': invoice.created_by.username}]
else:
context['users_mention'] = list(
invoice.assigned_to.all().values('username'))
return render(request, 'invoices_detail.html', context)
@login_required
def invoice_edit(request, invoice_id):
invoice_obj = get_object_or_404(Invoice, pk=invoice_id)
if not (request.user.role == 'ADMIN' or request.user.is_superuser or invoice_obj.created_by == request.user or request.user in invoice_obj.assigned_to.all()):
raise PermissionDenied
if request.method == 'GET':
context = {}
context['invoice_obj'] = invoice_obj
context['form'] = InvoiceForm(
instance=invoice_obj, request_user=request.user)
context['from_address_form'] = InvoiceAddressForm(prefix='from',
instance=invoice_obj.from_address)
context['to_address_form'] = InvoiceAddressForm(prefix='to',
instance=invoice_obj.to_address)
return render(request, 'invoices_create.html', context)
if request.method == 'POST':
form = InvoiceForm(request.POST, instance=invoice_obj,
request_user=request.user)
from_address_form = InvoiceAddressForm(
request.POST, instance=invoice_obj.from_address, prefix='from')
to_address_form = InvoiceAddressForm(
request.POST, instance=invoice_obj.to_address, prefix='to')
if form.is_valid() and from_address_form.is_valid() and to_address_form.is_valid():
from_address_obj = from_address_form.save()
to_address_obj = to_address_form.save()
invoice_obj = form.save(commit=False)
invoice_obj.created_by = request.user
invoice_obj.from_address = from_address_obj
invoice_obj.to_address = to_address_obj
invoice_obj.save()
form.save_m2m()
kwargs = {'domain': request.get_host(), 'protocol': request.scheme}
send_email.delay(invoice_obj.id, **kwargs)
return JsonResponse({'error': False, 'success_url': reverse('invoices:invoices_list')})
else:
return JsonResponse({'error': True, 'errors': form.errors,
'from_address_errors': from_address_form.errors,
'to_address_errors': to_address_form.errors})
@login_required
def invoice_delete(request, invoice_id):
invoice = get_object_or_404(Invoice, pk=invoice_id)
if not (request.user.role == 'ADMIN' or request.user.is_superuser or invoice.created_by == request.user):
raise PermissionDenied
if request.method == 'GET':
invoice.delete()
return redirect('invoices:invoices_list')
@login_required
def invoice_send_mail(request, invoice_id):
invoice = get_object_or_404(Invoice, pk=invoice_id)
if not (request.user.role == 'ADMIN' or request.user.is_superuser or invoice.created_by == request.user):
raise PermissionDenied
if request.method == 'GET':
Invoice.objects.filter(id=invoice_id).update(
is_email_sent=True, status='Sent')
kwargs = {'domain': request.get_host(), 'protocol': request.scheme}
send_invoice_email.delay(invoice_id, **kwargs)
return redirect('invoices:invoices_list')
@login_required
def invoice_change_status_paid(request, invoice_id):
invoice = get_object_or_404(Invoice, pk=invoice_id)
if not (request.user.role == 'ADMIN' or request.user.is_superuser or invoice.created_by == request.user):
raise PermissionDenied
if request.method == 'GET':
Invoice.objects.filter(id=invoice_id).update(status='Paid')
kwargs = {'domain': request.get_host(), 'protocol': request.scheme}
# send_invoice_email.delay(invoice_id, **kwargs)
return redirect('invoices:invoices_list')
@login_required
def invoice_change_status_cancelled(request, invoice_id):
invoice = get_object_or_404(Invoice, pk=invoice_id)
if not (request.user.role == 'ADMIN' or request.user.is_superuser or invoice.created_by == request.user):
raise PermissionDenied
if request.method == 'GET':
Invoice.objects.filter(id=invoice_id).update(status='Cancelled')
kwargs = {'domain': request.get_host(), 'protocol': request.scheme}
send_invoice_email_cancel.delay(invoice_id, **kwargs)
return redirect('invoices:invoices_list')
@login_required
def invoice_download(request, invoice_id):
invoice = get_object_or_404(Invoice.objects.select_related(
'from_address', 'to_address'), pk=invoice_id)
if not (request.user.role == 'ADMIN' or request.user.is_superuser or invoice.created_by == request.user or request.user in invoice.assigned_to.all()):
raise PermissionDenied
if request.method == 'GET':
context = {}
context['invoice'] = invoice
html = render_to_string('invoice_download_pdf.html', context=context)
pdfkit.from_string(html, 'out.pdf', options={'encoding': "UTF-8"})
pdf = open("out.pdf", 'rb')
response = HttpResponse(pdf.read(), content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename=Invoice.pdf'
pdf.close()
os.remove("out.pdf")
return response
class AddCommentView(LoginRequiredMixin, CreateView):
model = Comment
form_class = InvoiceCommentForm
http_method_names = ["post"]
def post(self, request, *args, **kwargs):
self.object = None
self.invoice = get_object_or_404(
Invoice, id=request.POST.get('invoice_id'))
if (
request.user == self.invoice.created_by or request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
return self.form_invalid(form)
data = {
'error': "You don't have permission to comment for this account."}
return JsonResponse(data)
def form_valid(self, form):
comment = form.save(commit=False)
comment.commented_by = self.request.user
comment.invoice = self.invoice
comment.save()
comment_id = comment.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'invoices', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"comment_id": comment.id, "comment": comment.comment,
"commented_on": comment.commented_on,
"commented_by": comment.commented_by.email
})
def form_invalid(self, form):
return JsonResponse({"error": form['comment'].errors})
class UpdateCommentView(LoginRequiredMixin, View):
http_method_names = ["post"]
def post(self, request, *args, **kwargs):
self.comment_obj = get_object_or_404(
Comment, id=request.POST.get("commentid"))
if request.user == self.comment_obj.commented_by:
form = InvoiceCommentForm(request.POST, instance=self.comment_obj)
if form.is_valid():
return self.form_valid(form)
return self.form_invalid(form)
data = {'error': "You don't have permission to edit this comment."}
return JsonResponse(data)
def form_valid(self, form):
self.comment_obj.comment = form.cleaned_data.get("comment")
self.comment_obj.save(update_fields=["comment"])
comment_id = self.comment_obj.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'invoices', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"comment_id": self.comment_obj.id,
"comment": self.comment_obj.comment,
})
def form_invalid(self, form):
return JsonResponse({"error": form['comment'].errors})
class DeleteCommentView(LoginRequiredMixin, View):
def post(self, request, *args, **kwargs):
self.object = get_object_or_404(
Comment, id=request.POST.get("comment_id"))
if request.user == self.object.commented_by:
self.object.delete()
data = {"cid": request.POST.get("comment_id")}
return JsonResponse(data)
data = {'error': "You don't have permission to delete this comment."}
return JsonResponse(data)
class AddAttachmentView(LoginRequiredMixin, CreateView):
model = Attachments
form_class = InvoiceAttachmentForm
http_method_names = ["post"]
def post(self, request, *args, **kwargs):
self.object = None
self.invoice = get_object_or_404(
Invoice, id=request.POST.get('invoice_id'))
if (
request.user == self.invoice.created_by or
request.user.is_superuser or
request.user.role == 'ADMIN'
):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
return self.form_invalid(form)
data = {
'error': "You don't have permission to add attachment \
for this account."}
return JsonResponse(data)
def form_valid(self, form):
attachment = form.save(commit=False)
attachment.created_by = self.request.user
attachment.file_name = attachment.attachment.name
attachment.invoice = self.invoice
attachment.save()
return JsonResponse({
"attachment_id": attachment.id,
"attachment": attachment.file_name,
"attachment_url": attachment.attachment.url,
"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,
"file_type": attachment.file_type()
})
def form_invalid(self, form):
return JsonResponse({"error": form['attachment'].errors})
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'
):
self.object.delete()
data = {"acd": request.POST.get("attachment_id")}
return JsonResponse(data)
data = {
'error': "You don't have permission to delete this attachment."}
return JsonResponse(data)

View file

@ -13,11 +13,12 @@ class LeadForm(forms.ModelForm):
if self.data.get('status') == 'converted':
self.fields['account_name'].required = True
self.fields['email'].required = True
self.fields['assigned_to'].queryset = assigned_users
self.fields['assigned_to'].required = False
self.fields['first_name'].required = False
self.fields['last_name'].required = False
self.fields['title'].required = True
if assigned_users:
self.fields['assigned_to'].queryset = assigned_users
self.fields['assigned_to'].required = False
for key, value in self.fields.items():
if key == 'phone':
value.widget.attrs['placeholder'] =\

View file

@ -0,0 +1,18 @@
# Generated by Django 2.1.7 on 2019-05-29 06:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('leads', '0008_auto_20190315_1503'),
]
operations = [
migrations.AddField(
model_name='lead',
name='created_from_site',
field=models.BooleanField(default=False),
),
]

View file

@ -53,12 +53,16 @@ class Lead(models.Model):
enquery_type = models.CharField(max_length=255, blank=True, null=True)
tags = models.ManyToManyField(Tags, blank=True)
contacts = models.ManyToManyField(Contact, related_name="lead_contacts")
created_from_site = models.BooleanField(default=False)
class Meta:
ordering = ['-created_on']
def __str__(self):
return self.first_name + self.last_name
if self.first_name and self.last_name:
return self.first_name + self.last_name
else:
return self.title
def get_complete_address(self):
return return_complete_address(self)

View file

@ -1,8 +1,10 @@
from celery.task import task
from django.conf import settings
from django.core.mail import EmailMultiAlternatives, EmailMessage
from django.db.models import Q
from django.core.mail import EmailMultiAlternatives
from django.shortcuts import reverse
from django.template.loader import render_to_string
from accounts.models import User
from leads.models import Lead
@ -59,3 +61,31 @@ def send_lead_assigned_emails(lead_id, new_assigned_to_list, site_address):
mail_kwargs["html_content"] = html_content
mail_kwargs["recipients"] = [user.email]
send_email.delay(**mail_kwargs)
@task
def send_email_to_assigned_user(recipients, lead_id, domain='demo.django-crm.io', protocol='http'):
""" Send Mail To Users When they are assigned to a lead """
lead = Lead.objects.get(id=lead_id)
for user in recipients:
recipients_list = []
user = User.objects.filter(id=user).first()
if user:
recipients_list.append(user.email)
context = {}
context["url"] = protocol + '://' + domain + \
reverse('leads:view_lead', args=(lead.id,))
context["user"] = user
context["lead"] = lead
subject = 'Assigned to lead.'
html_content = render_to_string(
'assigned_to/leads_assigned.html', context=context)
msg = EmailMessage(
subject,
html_content,
to=recipients_list
)
msg.content_subtype = "html"
msg.send()

View file

@ -11,7 +11,8 @@
<!-- heading_create starts here -->
<div class="row marl">
<div class="col-lg-12 text-right">
<span class="d-inline"><a class="primary_btn" href="{% url 'leads:add_lead'%}"><i class="fa fa-plus"></i> Add New Lead</a></span>
<span class="d-inline"><a class="primary_btn" href="{% url 'leads:add_lead'%}"><i class="fa fa-plus"></i> Add New
Lead</a></span>
</div>
</div>
<!-- heading_create ends here -->
@ -26,16 +27,19 @@
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Name</label>
<input type="text" class="form-control" placeholder="First Name" name="name" value="{{request.POST.name}}">
<input type="text" class="form-control" placeholder="First Name" name="name"
value="{{request.POST.name}}">
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Source</label>
<select class="form-control" id="id_source" name="source" >
<select class="form-control" id="id_source" name="source">
<option value="">--Source of Lead--</option>
{% for each_source in source %}
<option value="{{each_source.0}}" {% if request.POST.source %} {% ifequal each_source.0 request.POST.source %}selected{% endifequal %}{% endif %} >{{each_source.1}} </option>
<option value="{{each_source.0}}" {% if request.POST.source %}
{% ifequal each_source.0 request.POST.source %}selected{% endifequal %}{% endif %}>
{{each_source.1}} </option>
{% endfor %}
</select>
</div>
@ -44,19 +48,22 @@
<div class="form-group">
<label for="exampleInputEmail1">Assigned Users</label>
<select class="assigned_users form-control" name="assigned_to" multiple="multiple">
{% for user in users %}
<option value="{{user.id}}" {% if user.id in assignedto_list %} selected="" {% endif %}>{{user.email}}</option>
{% endfor %}
{% for user in users %}
<option value="{{user.id}}" {% if user.id in assignedto_list %} selected="" {% endif %}>
{{user.email}}</option>
{% endfor %}
</select>
</div>
</div>
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Status</label>
<select class="form-control" id="id_status" name="status" >
<select class="form-control" id="id_status" name="status">
<option value="">--Status of Lead--</option>
{% for each_status in status %}
<option value="{{each_status.0}}" {% if request.POST.status %} {% ifequal each_status.0 request.POST.status %}selected{% endifequal %}{% endif %} >{{each_status.1}} </option>
<option value="{{each_status.0}}" {% if request.POST.status %}
{% ifequal each_status.0 request.POST.status %}selected{% endifequal %}{% endif %}>
{{each_status.1}} </option>
{% endfor%}
</select>
</div>
@ -64,10 +71,11 @@
<div class="filter_col col-md-2">
<div class="form-group">
<label for="exampleInputEmail1">Tags</label>
<select class="form-control" id="id_tag" name="tag" >
<option value="">--Select Tags--</option>
<select class="form-control" id="id_tag" name="tag" multiple>
<!-- <option value="">--Select Tags--</option> -->
{% for tag in tags %}
<option value="{{tag.id}}" {% if request.POST.tag %} {% ifequal tag.id request.POST.tag|add:"0" %}selected{% endifequal %}{% endif %} >{{ tag.name }} </option>
<option value="{{tag.id}}" {% if request_tags %}
{% if tag.id|slugify in request_tags %}selected{% endif %}{% endif %}>{{ tag.name }} </option>
{% endfor%}
</select>
</div>
@ -91,10 +99,12 @@
<div class="col-md-12">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="open-tab" data-toggle="tab" href="#open" role="tab" aria-controls="open" aria-selected="true">Open</a>
<a class="nav-link active" id="open-tab" data-toggle="tab" href="#open" role="tab" aria-controls="open"
aria-selected="true">Open ({{open_leads|length}})</a>
</li>
<li class="nav-item">
<a class="nav-link" id="close-tab" data-toggle="tab" href="#close" role="tab" aria-controls="close" aria-selected="false">Closed</a>
<a class="nav-link" id="close-tab" data-toggle="tab" href="#close" role="tab" aria-controls="close"
aria-selected="false">Closed ({{close_leads|length}})</a>
</li>
</ul>
<div class="tab-content" id="myTabContent">
@ -102,9 +112,10 @@
<div class="card">
<div class="card-body">
<div class="card-title text-right">
<span class="float-left">Open Leads - {% if show_pageitems %} {% show_pageitems %}{% else %} {{ open_leads|length }}{% endif %}</span>
<span class="float-left">Open Leads - {% if show_pageitems %} {% show_pageitems %}{% else %}
{{ open_leads|length }}{% endif %}</span>
<span class="filter_toggle">
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
</span>
</div>
<div class="table-responsive">
@ -117,7 +128,7 @@
<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%">Assigned To</th>
<th width="15%">Tags</th>
<th width="10%">Created On</th>
<th width="10%">Actions</th>
@ -134,14 +145,16 @@
<tr class="text-center">
<td scope="row">{{ forloop.counter }}</td>
<td><a href="{% url 'leads:view_lead' lead.id %}">
{{ lead.title }} </a></td>
{{ lead.title }} </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 %}
{% 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;"/>
<img src="{% static 'images/user.png' %}" alt="Micro profile pic"
style="width: 40px;height: 40px;" />
{% endif %}
</td>
<td>{{ lead.source }}</td>
@ -150,18 +163,19 @@
{% with lead_users=lead.assigned_to.all %}
{% for user in lead_users %}
{% if user.profile_pic %}
{% thumbnail user.profile_pic "40x40" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" title="{{ user.email }}">
{% endthumbnail %}
{% thumbnail user.profile_pic "40x40" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}"
title="{{ user.email }}">
{% endthumbnail %}
{% else %}
<img src="{% static 'images/user.png' %}" title="{{ user.email }}" width="40" height="40">
<img src="{% static 'images/user.png' %}" title="{{ user.email }}" width="40" height="40">
{% endif %}
{% empty %}
Not Assigned to Any
None
{% endfor %}
{% endwith %}
</td>
<td >
<td>
{% with tags=lead.tags.all %}
{% if tags %}
{% for tag in tags %}
@ -173,10 +187,12 @@
{% endwith %}
</td>
<td>{{ lead.created_on }}</td>
<td class="actions">
<a href="{% url 'leads:edit_lead' lead.id %}" class="btn btn-success edit"><i class="fas fa-pencil-alt"></i></a>
<td class="actions">
<a href="{% url 'leads:edit_lead' lead.id %}" class="btn btn-success edit"><i
class="fas fa-pencil-alt"></i></a>
{% if request.user == lead.created_by or request.user.role == "ADMIN" or request.user.is_superuser %}
<a href="{% url 'leads:remove_lead' lead.id %}" class="btn btn-danger delete remove_account"><i class="fas fa-trash-alt"></i></a>
<a href="{% url 'leads:remove_lead' lead.id %}"
class="btn btn-danger delete remove_account"><i class="fas fa-trash-alt"></i></a>
{% endif %}
</td>
</tr>
@ -197,9 +213,10 @@
<div class="card">
<div class="card-body">
<div class="card-title text-right">
<span class="float-left">Closed Leads - {% if show_pageitems %} {% show_pageitems %}{% else %} {{ close_leads|length }}{% endif %}</span>
<span class="float-left">Closed Leads - {% if show_pageitems %} {% show_pageitems %}{% else %}
{{ close_leads|length }}{% endif %}</span>
<span class="filter_toggle">
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
<a href="#" class="primary_btn"><i class="fas fa-filter"></i></a>
</span>
</div>
<div class="table-responsive">
@ -228,7 +245,7 @@
<tr class="text-center">
<td scope="row">{{ forloop.counter }}</td>
<td><a href="{% url 'leads:view_lead' lead.id %}">
{{ lead.title }}</a></td>
{{ lead.title }}</a></td>
<td>{{ lead.source }}</td>
<td>{{ lead.get_status_display }}</td>
<td>
@ -236,11 +253,11 @@
{% for user in lead_users %}
<span class="text-left">{{ user.email }}</span><br>
{% empty %}
Not Assigned to Any
None
{% endfor %}
{% endwith %}
</td>
<td >
<td>
{% with tags=lead.tags.all %}
{% if tags %}
{% for tag in tags %}
@ -252,10 +269,12 @@
{% endwith %}
</td>
<td>{{ lead.created_on }}</td>
<td class="actions">
<a href="{% url 'leads:edit_lead' lead.id %}" class="btn btn-success edit"><i class="fas fa-pencil-alt"></i></a>
<td class="actions">
<a href="{% url 'leads:edit_lead' lead.id %}" class="btn btn-success edit"><i
class="fas fa-pencil-alt"></i></a>
{% if request.user == lead.created_by %}
<a href="{% url 'leads:remove_lead' lead.id %}" class="btn btn-danger delete remove_account"><i class="fas fa-trash-alt"></i></a>
<a href="{% url 'leads:remove_lead' lead.id %}"
class="btn btn-danger delete remove_account"><i class="fas fa-trash-alt"></i></a>
{% endif %}
</td>
</tr>
@ -281,43 +300,44 @@
{% block js_block %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$(document).ready(function () {
$('.assigned_users').select2();
$(".filter_toggle").click(function(){
$(".list_filter_row").toggle();
});
$('#id_tag').select2();
$(".filter_toggle").click(function () {
$(".list_filter_row").toggle();
});
});
search = "{{search}}"
if (search == 'True'){
if (search == 'True') {
$(".list_filter_row").show();
}
$("#close-tab").click(function(e){
$("#close-tab").click(function (e) {
$("#tab_status").val('Closed')
})
$("#open-tab").click(function(e){
$("#open-tab").click(function (e) {
$("#tab_status").val('Open')
})
tab_status = "{{tab_status}}"
if(tab_status == 'Closed'){
if (tab_status == 'Closed') {
$("#close-tab").click()
}else{
} else {
$("#open-tab").click()
}
$('.delete').click(function(e){
$('.delete').click(function (e) {
e.preventDefault()
url = $(this).attr('href')
if (!confirm('Are you sure you want to delete?'))
return;
window.location = $(this).attr('href')
});
$("a[rel='page']").click(function(e){
$("a[rel='page']").click(function (e) {
e.preventDefault();
$('#leads_filter').attr("action", $(this).attr("href"));
$('#leads_filter').submit();

View file

@ -1,6 +1,16 @@
{% extends 'sales/base.html' %}
{% load staticfiles %}
{% load thumbnail %}
{% block breadcrumb %}
{% block extralinks %}
<link rel="stylesheet" href="{% static 'css/bootstrap-suggest.css' %}">
<style>
.opacity_block {
opacity: 0.2;
}
</style>
{% endblock extralinks %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'leads:list' %}">Leads</a></li>
@ -13,29 +23,30 @@
{% block content %}
<div class="main_container" id="maincontainer">
<div class="overview_form_block row marl justify-content-center">
<div class="col-md-8">
<div class="col-md-8" id="opacity_block">
<div class="card">
<div class="card-body" id="datashow">
<div class="card-title text-right">
<h5>
<span class="float-left title">Overview</span>
<span class="" style="margin-top: 0px">
<div class="dropdown buttons_row">
<button class="btn primary_btn dropdown-toggle" type="button" data-toggle="dropdown">Actions
<span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="{% url 'leads:edit_lead' lead_record.id %}" >Edit</a></li>
<li><a href="{% url 'leads:leads_convert' lead_record.id %}" class="btn btn-info" role="button">Convert</a></li>
</ul>
</div>
</span>
</h5>
</div>
<div class="card-title text-right">
<h5>
<span class="float-left title">Overview</span>
<span class="" style="margin-top: 0px">
<div class="dropdown buttons_row">
<button class="btn primary_btn dropdown-toggle" type="button" data-toggle="dropdown">Actions
<span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="{% url 'leads:edit_lead' lead_record.id %}">Edit</a></li>
<li><a href="{% url 'leads:leads_convert' lead_record.id %}" class="btn btn-info"
role="button">Convert</a></li>
</ul>
</div>
</span>
</h5>
</div>
<div class="row marl">
<div class="col-md-4">
<div class="filter_col col-md-12" id="iname">
<div class="form-group" >
<div class="form-group">
<label class="lead_field_label" for="id_name" data-name="name">Name</label>
<div class="lead_field" id="lead_name" data-name="name"> {% if lead_record.first_name %}
{{ lead_record.first_name }}{% endif %} {% if lead_record.last_name %}
@ -46,9 +57,9 @@
<div class="form-group">
<label class="lead_field_label" for="id_contact_account" data-name="name">Account Name</label>
{% if lead_record.account_name %}
<div class="lead_field" id ="contact_account" data-name="name">{{ lead_record.account_name }}</div>
<div class="lead_field" id="contact_account" data-name="name">{{ lead_record.account_name }}</div>
{% else %}
<div class="lead_field">Not Specified</div>
<div class="lead_field">Not Specified</div>
{% endif %}
</div>
</div>
@ -56,9 +67,9 @@
<div class="form-group">
<label class="lead_field_label" for="id_title" data-name="name">Title</label>
{% if lead_record.title %}
<div class="lead_field" id="lead_title" data-name="name">{{ lead_record.title }}</div>
<div class="lead_field" id="lead_title" data-name="name">{{ lead_record.title }}</div>
{% else %}
<div class="lead_field">Not Specified</div>
<div class="lead_field">Not Specified</div>
{% endif %}
</div>
</div>
@ -68,11 +79,11 @@
{% if lead_record.phone %}
<div class="lead_field" id="lead_phone" data-name="name">{{ lead_record.phone }}</div>
{% else %}
<div class="lead_field">Not specified</div>
<div class="lead_field">Not specified</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4">
<div class="filter_col col-md-12">
@ -89,39 +100,39 @@
<div class="form-group">
<label class="lead_field_label" for="id_status" data-name="name">Status</label>
{% if lead_record.status %}
<div class="lead_field" id="lead_status" data-name="name">{{ lead_record.status }}</div>
<div class="lead_field" id="lead_status" data-name="name">{{ lead_record.status }}</div>
{% else %}
<div class="lead_field">Not Specified</div>
<div class="lead_field">Not Specified</div>
{% endif %}
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label class="lead_field_label" for="id_source" data-name="name">Source</label>
{% if lead_record.source %}
{% if lead_record.source %}
<div class="lead_field" id="lead_source" data-name="name">{{ lead_record.source }}</div>
{% else %}
{% else %}
<div class="lead_field">Not Specified</div>
{% endif %}
{% endif %}
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label class="lead_field_label" for="id_website" data-name="name">Website</label>
{% if lead_record.website %}
{% if lead_record.website %}
<div class="lead_field" id="lead_website" data-name="name">{{ lead_record.website }}</div>
{% else %}
{% else %}
<div class="lead_field">Not Specified</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
<div class="col-md-4">
<div class="filter_col col-md-12">
<div class="form-group">
{% if lead_record.address_line or lead_record.street or lead_record.city or lead_record.state or lead_record.postcode or lead_record.country %}
<label class="lead_field_label" for="id_address" data-name="name" >Billing Address</label>
<label class="lead_field_label" for="id_address" data-name="name">Billing Address</label>
<div class="lead_field" id="lead_address" data-name="name">
{{lead_record.get_complete_address}}
</div>
@ -131,297 +142,350 @@
<div class="filter_col col-md-12">
{% if lead_record.description %}
<div class="form-group">
<label class="lead_field_label" for="id_description" data-name="name" >Description</label>
<label class="lead_field_label" for="id_description" data-name="name">Description</label>
<div class="lead_field" id="lead_description" data-name="name">{{ lead_record.description }}</div>
</div>
{% endif %}
</div>
</div>
<div class="col-md-12">
<div class="created_information">
Created by <b>{{ lead_record.created_by }}</b> created on <i><b>{{ lead_record.created_on }}</b></i>
</div>
<div class="created_information">
Created by <b>{{ lead_record.created_by }}</b> created on <b>{{ lead_record.created_on }}</b>
</div>
</div>
</div>
</div>
<!-- Attachment Section start -->
<div>
<div class="card-body mar-top" id="datashow">
<div class="card-title view-pad">
<div>
<div class="card-body mar-top" id="datashow">
<div class="card-title view-pad">
<h5>Attachments</h5>
</div>
<div class="row marl">
</div>
<div class="row marl">
<div class="col-md-12">
<form id="attachment_form" method="POST" enctype="multipart/form-data">
<div class="form-group ">
<input type="file" class="form-control-file" cols="40" id="id_attachments" name="attachment">
<div><i id="AttachmentError" style="display:none; color:red"></i></div>
<br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;" id="attachment_submit">Save</button>
</div>
</div>
<input type="hidden" value="{{lead_record.id}}" name="leadid">
</form>
<ul class="list-group" id="attachment_div">
{% for attachment in attachments %}
<li class="list-group-item list-row" id="attachment{{attachment.id}}">
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == attachment.created_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="action btn primary_btn" onclick="remove_attachment({{attachment.id}})">Remove</button>
</div>
</div>
{% endif %}
<div class="stream-post-container" id="attachment_name{{attachment.id}}">
<pre> <span class="icon"><i style="font-size:1.5rem;" class="{{attachment.get_file_type_display}}"></i> {{attachment.file_name}} </span> <a href="{% url 'common:download_attachment' attachment.id %}">Download</a></pre>
</div>
<div class="stream-container">
<pre class="float-left">Uploaded by :{{ attachment.created_by }}</pre>
<pre class="float-right">{{ attachment.created_on|date:'m/d/Y, h:i A' }}</pre>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<!-- Attachment Section end -->
<!-- Comments Section starts-->
<div>
<div class="card-body mar-top" id="datashow">
<div class="card-title view-pad">
<h5>Comments </h5>
</div>
<div class="row marl">
<div class="col-md-12">
<form id="comment_form" method="POST" enctype="multipart/form-data">
<div class="form-group ">
<textarea class="form-control" textarea cols="40" rows="3" id="id_comments" name="comment" placeholder="Submit Your Comments Here"></textarea><div><i id="CommentError" style="display:none; color:red"></i></div>
</br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;" id="comment_submit">Submit</button>
</div>
</div>
<input type="hidden" value="{{lead_record.id}}" name="leadid">
</form>
<ul class="list-group" id="comments_div">
{% for comment in comments %}
<li class="list-group-item list-row" id="comment{{comment.id}}">
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == comment.commented_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="btn primary_btn btn-sm dropdown-toggle" data-toggle="dropdown" type="button">Actions <span class="caret"></span></button>
<ul class="dropdown-menu text-center">
<li>
<a class="action" onclick="edit_comment({{comment.id}})">Edit</a>
</li>
<li>
<a class="action" onclick="remove_comment({{comment.id}})">Remove</a>
</li>
</ul>
<form id="attachment_form" method="POST" enctype="multipart/form-data">
<div class="form-group ">
<input type="file" class="form-control-file" cols="40" id="id_attachments" name="attachment">
<div>
<p id="AttachmentError" style="display:none; color:red"></p>
</div>
<br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;"
id="attachment_submit">Save</button>
</div>
</div>
{% endif %}
<div class="stream-post-container" id="comment_name{{comment.id}}"><pre>{{ comment.comment }}</pre></div>
<div class="stream-container">
<pre class="float-left">{{ comment.commented_by}}</pre>
<pre class="float-right date">{{ comment.commented_on|date:'m/d/Y, h:i:s A' }}</pre>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
<div class="modal fade" id="Comments_Leads_Modal" role="dialog" data-keyboard="false" data-backdrop="static">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Update Your Comment</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</button>
</div>
<form id="comment_edit_form" method="POST" >
<div class="modal-body">
<div class="form-group">
<textarea class="form-control" textarea cols="40" rows="3" id="id_editcomment" name="comment" placeholder="Submit Your Comments" style="color:black;"></textarea><span><i id="CommentEditError" style="display:none; color:red"></i></span>
<input type="hidden" value="{{ lead.id }}" name="leadid">
<input type="hidden" value="" name="commentid" id="commentid">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default save" id="comment_edit" type="submit">Update</button>
</div>
<input type="hidden" value="{{lead_record.id}}" name="leadid">
</form>
<ul class="list-group" id="attachment_div">
{% for attachment in attachments %}
<li class="list-group-item list-row" id="attachment{{attachment.id}}">
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == attachment.created_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="action btn primary_btn"
onclick="remove_attachment({{attachment.id}})">Remove</button>
</div>
</div>
{% endif %}
<div class="stream-post-container" id="attachment_name{{attachment.id}}">
{% if attachment.attachment %}
{% if 'image' in attachment.file_type %}
{% thumbnail attachment.attachment "80x80" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}" class="mb-2">
{% endthumbnail %}
{% endif %}
{% endif %}
<pre> <span class="icon"><i style="font-size:1.5rem;" class="{{attachment.get_file_type_display}}"></i> {{attachment.file_name}} </span> <a href="{% url 'common:download_attachment' attachment.id %}">Download</a></pre>
</div>
<div class="stream-container">
<pre class="float-left">Uploaded by :{{ attachment.created_by }}</pre>
<pre class="float-right">{{ attachment.created_on|date:'m/d/Y, h:i A' }}</pre>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div></div>
<!-- Comments Section Ends-->
<!-- Attachment Section end -->
<!-- Comments Section starts-->
<div>
<div class="card-body mar-top" id="datashow">
<div class="card-title view-pad">
<h5>Comments </h5>
</div>
<div class="row marl">
<div class="col-md-12">
<form id="comment_form" method="POST" enctype="multipart/form-data">
<div class="form-group ">
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_comments"
name="comment" placeholder="Submit Your Comments Here"></textarea>
<div>
<p id="CommentError" style="display:none; color:red"></p>
</div>
</br>
<div class="buttons_row">
<button class="btn btn-default save" style="text-align: center;"
id="comment_submit">Submit</button>
</div>
</div>
<input type="hidden" value="{{lead_record.id}}" name="leadid">
</form>
<ul class="list-group" id="comments_div">
{% for comment in comments %}
<li class="list-group-item list-row" id="comment{{comment.id}}">
{% if request.user.is_superuser or request.user.role == 'ADMIN' or request.user == comment.commented_by %}
<div class="float-right right-container">
<div class="list-row-buttons btn-group float-right">
<button class="btn primary_btn btn-sm dropdown-toggle" data-toggle="dropdown"
type="button">Actions <span class="caret"></span></button>
<ul class="dropdown-menu text-center">
<li>
<a class="action" onclick="edit_comment({{comment.id}})">Edit</a>
</li>
<li>
<a class="action" onclick="remove_comment({{comment.id}})">Remove</a>
</li>
</ul>
</div>
</div>
{% endif %}
<div class="stream-post-container" id="comment_name{{comment.id}}">
<pre>{{ comment.comment }}</pre>
</div>
<div class="stream-container">
<pre class="float-left">{{ comment.commented_by}}</pre>
<pre class="float-right date">{{ comment.commented_on|date:'m/d/Y, h:i:s A' }}</pre>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
<div class="modal fade" id="Comments_Leads_Modal" role="dialog" data-keyboard="false"
data-backdrop="static">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Update Your Comment</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</button>
</div>
<form id="comment_edit_form" method="POST">
<div class="modal-body">
<div class="form-group">
<textarea class="form-control mentions" textarea cols="40" rows="3" id="id_editcomment"
name="comment" placeholder="Submit Your Comments" style="color:black;"></textarea><span>
<p id="CommentEditError" style="display:none; color:red"></p>
</span>
<input type="hidden" value="{{ lead.id }}" name="leadid">
<input type="hidden" value="" name="commentid" id="commentid">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default save" id="comment_edit" type="submit">Update</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Comments Section Ends-->
</div>
</div>
<tr id="meeting{{ meeting.id }}">
</div>
<tr id="meeting{{ meeting.id }}">
</div>
<div class="overview_form_block row marl justify-content-center" id="loading_spinner"
style="z-index: 10; left: 0; right: 0; vertical-align: middle; position: absolute; margin-top: 15%;">
<div class="spinner-border text-primary" style="width: 5em; height: 5em;" role="status">
<span class="sr-only">Processing File...</span>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="{% static 'js/calls.js' %}"></script>
<script src="{% static 'js/meetings.js' %}"></script>
<script type="text/javascript">
<script src="{% static 'js/calls.js' %}"></script>
<script src="{% static 'js/meetings.js' %}"></script>
<script type="text/javascript">
$('#loading_spinner').hide()
var assigned_to_list = "{{ assigned_data }}";
assigned_to_list = JSON.parse(assigned_to_list.replace(/&(l|g|quo)t;/g, function(a,b){
return {
l : '<',
g : '>',
quo : '"'
}[b];
}));
assigned_to_list = JSON.parse(assigned_to_list.replace(/&(l|g|quo)t;/g, function (a, b) {
return {
l: '<',
g: '>',
quo: '"'
}[b];
}));
function resetForm() {
$('#CommentEditError').hide()
document.getElementById("CommentEditError").reset();
}
}
$("#comment_form").submit(function (e) {
e.preventDefault()
$("#comment_form").submit(function (e) {
e.preventDefault()
const formData = new FormData($("#comment_form")[0]);
$.ajax({
url: "{% url 'leads:add_comment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentError").html(data.error).show()
} else {
d = new Date(data.commented_on);
$("#comments_div").prepend("<li class='list-group-item list-row' id='comment" + data.comment_id + "'>" +
"<div class='float-right right-container'>" +
"<div class='list-row-buttons btn-group float-right'>"+
"<button class='btn primary_btn btn-sm dropdown-toggle' data-toggle='dropdown' type='button'><span class='caret'></span>Actions</button>"+
"<ul class='dropdown-menu text-center'>" +
"<li><a class='action' onclick='edit_comment(" + data.comment_id + ")'>Edit</a></li>" +
"<li><a class='action' onclick='remove_comment(" + data.comment_id + ")''>Remove</a></li></ul></div></div>" +
"<div class='stream-post-container' id='comment_name"+data.comment_id+"'><pre>"+data.comment+"</pre></div>"+
"<div class='stream-container'><pre class='float-left'>"+data.commented_by+"</pre><pre class='float-right'>"+d.toLocaleString('en-US', { hour12: true })+"</pre></div>"+
"<div class='stream-date-container' id='comment_file_div"+data.comment_id+"'><div id='new_comment"+data.comment_id+"'</div></div></li>"
);
$("#id_comments").val("")
alert("Comment Submitted")
$("#CommentError").html("")
url: "{% url 'leads:add_comment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentError").html(data.error).show()
} else {
d = new Date(data.commented_on);
$("#comments_div").prepend("<li class='list-group-item list-row' id='comment" + data.comment_id + "'>" +
"<div class='float-right right-container'>" +
"<div class='list-row-buttons btn-group float-right'>" +
"<button class='btn primary_btn btn-sm dropdown-toggle' data-toggle='dropdown' type='button'><span class='caret'></span>Actions</button>" +
"<ul class='dropdown-menu text-center'>" +
"<li><a class='action' onclick='edit_comment(" + data.comment_id + ")'>Edit</a></li>" +
"<li><a class='action' onclick='remove_comment(" + data.comment_id + ")''>Remove</a></li></ul></div></div>" +
"<div class='stream-post-container' id='comment_name" + data.comment_id + "'><pre>" + data.comment + "</pre></div>" +
"<div class='stream-container'><pre class='float-left'>" + data.commented_by + "</pre><pre class='float-right'>" + d.toLocaleString('en-US', { hour12: true }) + "</pre></div>" +
"<div class='stream-date-container' id='comment_file_div" + data.comment_id + "'><div id='new_comment" + data.comment_id + "'</div></div></li>"
);
$("#id_comments").val("")
alert("Comment Submitted")
$("#CommentError").html("")
}
}
}
})
});
})
});
function edit_comment(x) {
$('#Comments_Leads_Modal').modal('show');
comment = $("#comment_name" + x).text()
$("#commentid").val(x)
$("#id_editcomment").val(comment)
}
function edit_comment(x) {
$('#Comments_Leads_Modal').modal('show');
comment = $("#comment_name" + x).text()
$("#commentid").val(x)
$("#id_editcomment").val(comment)
}
$("#comment_edit").click(function (e) {
e.preventDefault()
$("#comment_edit").click(function (e) {
e.preventDefault()
const formData = new FormData($("#comment_edit_form")[0]);
$.ajax({
url: "{% url 'leads:edit_comment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentEditError").html(data.error).show()
} else {
$("#comment_name" + data.commentid).html('<pre>' + data.comment + '</pre>')
$('#Comments_Leads_Modal').modal('hide');
$("#id_editcomment").val("")
$("#CommentEditError").html("")
}
}
})
});
function HideError(e) {
$("#CommentError").hide()
$("#AttachmentError").hide()
}
function remove_comment(x) {
const con = confirm("Do you want to Delete it for Sure!?");
if (con == true) {
$.post('{% url "leads:remove_comment" %}', {
"comment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#comment" + data.cid).remove()
url: "{% url 'leads:edit_comment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#CommentEditError").html(data.error).show()
} else {
$("#comment_name" + data.commentid).html('<pre>' + data.comment + '</pre>')
$('#Comments_Leads_Modal').modal('hide');
$("#id_editcomment").val("")
$("#CommentEditError").html("")
}
}
})
}
}
});
$("#attachment_form").submit(function (e) {
e.preventDefault()
function HideError(e) {
$("#CommentError").hide()
$("#AttachmentError").hide()
}
function remove_comment(x) {
const con = confirm("Do you want to Delete it for Sure!?");
if (con == true) {
$.post('{% url "leads:remove_comment" %}', {
"comment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#comment" + data.cid).remove()
}
})
}
}
$("#attachment_form").submit(function (e) {
e.preventDefault()
const formData = new FormData($("#attachment_form")[0]);
$.ajax({
url: "{% url 'leads:add_attachment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
success: function (data) {
if (data.error) {
$("#AttachmentError").html(data.error).show()
} else {
d = new Date(data.created_on)
$("#attachment_div").prepend(
"<li class='list-group-item list-row' id='attachment" + data.attachment_id + "'>" +
"<div class='float-right right-container'><div class='list-row-buttons btn-group float-right'><button class='action btn primary_btn' onclick='remove_attachment("+data.attachment_id+")''>Remove</button></div></div>" +
"<div class='stream-post-container' id='attachment_name"+data.attachment_id+"'>"+
"<pre><span class='icon'><i style='font-size:1.5rem;' class='"+data.attachment_display+"'></i>&nbsp;&nbsp;</span><a href='"+data.download_url+"'>Download</a></pre></div>"+
"<div class='stream-container'><pre class='float-left'>"+data.created_by+"</pre><pre class='float-right date'>"+d.toLocaleString('en-US', { hour12: true })+"</pre></div>" +
"</li>"
);
$("#id_attachments").val("")
alert("Attachment Saved")
$("#AttachmentError").html("")
}
}
})
});
function remove_attachment(x) {
const con = confirm("Do you want to Delete it for Sure!?");
if (con == true) {
$.post('{% url "leads:remove_attachment" %}', {
"attachment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#attachment" + data.aid).remove()
url: "{% url 'leads:add_attachment' %}",
type: "POST",
data: formData,
cache: false,
contentType: false,
processData: false,
beforeSend: function () {
$('#loading_spinner').show()
$('#opacity_block').addClass('opacity_block')
},
success: function (data) {
if (data.error) {
$("#AttachmentError").html(data.error).show()
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
} else {
d = new Date(data.created_on)
$("#attachment_div").prepend(
"<li class='list-group-item list-row' id='attachment" + data.attachment_id + "'>" +
"<div class='float-right right-container'><div class='list-row-buttons btn-group float-right'><button class='action btn primary_btn' onclick='remove_attachment(" + data.attachment_id + ")''>Remove</button></div></div>" +
"<div class='stream-post-container' id='attachment_name" + data.attachment_id + "'>" +
`${(data.file_type[0]).startsWith('image') ? `<img src=${data.attachment_url} width="80px" height="80px" class="mb-2">` : ''}` +
"<pre><span class='icon'><i style='font-size:1.5rem;' class='" + data.attachment_display + "'></i>&nbsp;&nbsp;</span>" + data.attachment + "&nbsp; &nbsp;<a href='" + data.download_url + "'>Download</a></pre></div>" +
"<div class='stream-container'><pre class='float-left'>" + data.created_by + "</pre><pre class='float-right date'>" + d.toLocaleString('en-US', { hour12: true }) + "</pre></div>" +
"</li>"
);
$("#id_attachments").val("")
// alert("Attachment Saved")
$("#AttachmentError").html("")
$('#loading_spinner').hide()
$('#opacity_block').removeClass('opacity_block')
setTimeout(() => {
alert("Attachment Saved")
}, 100);
}
}
})
});
function remove_attachment(x) {
const con = confirm("Do you want to Delete it for Sure!?");
if (con == true) {
$.post('{% url "leads:remove_attachment" %}', {
"attachment_id": x
}, function (data) {
if (data.error) {
alert(data.error)
} else {
$("#attachment" + data.aid).remove()
}
})
}
}
}
</script>
<script src="{% static 'js/mention.js' %}"></script>
{% endblock js_block %}
<script src="{% static 'js/bootstrap-suggest.js' %}"></script>
<script>
$('.mentions').suggest('@', {
data: {{ users_mention| safe}},
map: function (user) {
return {
value: user.username,
text: '<strong>' + user.username + '</strong>'
}
}
})
</script>
{% endblock js_block %}

View file

@ -148,11 +148,11 @@ class LeadsPostrequestTestCase(TestLeadModel, TestCase):
# 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))
response = self.client.get('/leads/list/')
response = self.client.get(reverse('leads:list'))
self.assertEqual(response.status_code, 200)
def test_leads_list_html(self):
response = self.client.get('/leads/list/')
response = self.client.get(reverse('leads:list'))
self.assertTemplateUsed(response, 'leads.html')
@ -239,7 +239,7 @@ class LeadListTestCase(TestLeadModel, TestCase):
def test_leads_list(self):
self.lead = Lead.objects.all()
response = self.client.get('/leads/list/')
response = self.client.get(reverse('leads:list'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'leads.html')
@ -248,7 +248,7 @@ class LeadListTestCase(TestLeadModel, TestCase):
data = {'fist_name': "marphy", 'last_name': "racheal",
'city': "hyd", 'email': "contact@gmail.com",
'status': "Assigned"}
response = self.client.post('/leads/list/', data)
response = self.client.post(reverse('leads:list'), data)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'leads.html')
@ -265,7 +265,7 @@ class LeadsRemoveTestCase(TestLeadModel, TestCase):
def test_leads_remove(self):
response = self.client.get('/leads/' + str(self.lead.id) + '/delete/')
self.assertEqual(response['location'], '/leads/list/')
self.assertEqual(response['location'], '/leads/')
def test_leads_remove_status(self):
self.client.login(username='mp@micropyramid.com', password='mp')
@ -372,7 +372,7 @@ class AttachmentTestCase(TestLeadModel, TestCase):
class TestTemplates(TestLeadModel, TestCase):
def test_lead_list_view(self):
resp = self.client.post('/leads/list/', {'name': 'trevor',
resp = self.client.post(reverse('leads:list'), {'name': 'trevor',
'tag': "123",
'source': "call",
'assigned_to': '1',
@ -483,7 +483,7 @@ class TestLeadListView(TestCase):
self.client.login(username='g@mp.com', password='jorge2293')
def test_lead_list_view(self):
response = self.client.get('/leads/list/')
response = self.client.get(reverse('leads:list'))
self.assertEqual(response.status_code, 200)
# response = self.client.post('/leads/create/',{})

View file

@ -11,7 +11,7 @@ app_name = 'leads'
urlpatterns = [
path('list/', LeadListView.as_view(), name='list'),
path('', LeadListView.as_view(), name='list'),
path('create/', create_lead, name='add_lead'),
# create_lead_from_site
path('create/from-site/', create_lead_from_site,

View file

@ -22,7 +22,8 @@ from planner.models import Event, Reminder
from planner.forms import ReminderForm
from leads.tasks import send_lead_assigned_emails
from django.core.exceptions import PermissionDenied
from common.tasks import send_email_user_mentions
from leads.tasks import send_email_to_assigned_user
class LeadListView(LoginRequiredMixin, TemplateView):
model = Lead
@ -52,13 +53,13 @@ class LeadListView(LoginRequiredMixin, TemplateView):
if request_post.get('status'):
queryset = queryset.filter(status=request_post.get('status'))
if request_post.get('tag'):
queryset = queryset.filter(tags__in=request_post.get('tag'))
queryset = queryset.filter(tags__in=request_post.getlist('tag'))
if request_post.get('source'):
queryset = queryset.filter(source=request_post.get('source'))
if request_post.getlist('assigned_to'):
queryset = queryset.filter(
assigned_to__id__in=request_post.getlist('assigned_to'))
return queryset
return queryset.distinct()
def get_context_data(self, **kwargs):
context = super(LeadListView, self).get_context_data(**kwargs)
@ -74,6 +75,7 @@ class LeadListView(LoginRequiredMixin, TemplateView):
is_active=True).order_by('email')
context["assignedto_list"] = [
int(i) for i in self.request.POST.getlist('assigned_to', []) if i]
context["request_tags"] = self.request.POST.getlist('tag')
search = True if (
self.request.POST.get('name') or self.request.POST.get('city') or
@ -102,7 +104,13 @@ class LeadListView(LoginRequiredMixin, TemplateView):
def create_lead(request):
template_name = "create_lead.html"
users = User.objects.filter(is_active=True).order_by('email')
users = []
if request.user.role == 'ADMIN' or request.user.is_superuser:
users = User.objects.filter(is_active=True).order_by('email')
elif request.user.google.all():
users = []
else:
users = User.objects.filter(role='ADMIN').order_by('email')
form = LeadForm(assigned_to=users)
if request.POST:
@ -125,20 +133,23 @@ def create_lead(request):
lead_obj.assigned_to.add(*request.POST.getlist('assigned_to'))
assigned_to_list = request.POST.getlist('assigned_to')
current_site = get_current_site(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
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
recipients = assigned_to_list
send_email_to_assigned_user.delay(recipients, lead_obj.id, domain=current_site.domain,
protocol=request.scheme)
# 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
# })
# email = EmailMessage(
# mail_subject, message, to=[user.email])
# email.content_subtype = "html"
# email.send()
if request.FILES.get('lead_attachment'):
attachment = Attachments()
@ -169,20 +180,23 @@ def create_lead(request):
# account_object.assigned_to.add(*request.POST.getlist('assigned_to'))
assigned_to_list = request.POST.getlist('assigned_to')
current_site = get_current_site(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
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
recipients = assigned_to_list
send_email_to_assigned_user.delay(recipients, lead_obj.id, domain=current_site.domain,
protocol=request.scheme)
# 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
# })
# email = EmailMessage(
# mail_subject, message, to=[user.email])
# email.content_subtype = "html"
# email.send()
account_object.save()
success_url = reverse('leads:list')
@ -242,11 +256,19 @@ class LeadDetailView(LoginRequiredMixin, DetailView):
assigned_dict['name'] = each.email
assigned_data.append(assigned_dict)
if self.request.user.is_superuser or self.request.user.role == 'ADMIN':
users_mention = list(User.objects.all().values('username'))
elif self.request.user != context['object'].created_by:
users_mention = [{'username': context['object'].created_by.username}]
else:
users_mention = list(context['object'].assigned_to.all().values('username'))
context.update({
"attachments": attachments, "comments": comments,
"status": LEAD_STATUS, "countries": COUNTRIES,
"reminder_form_set": reminder_form_set,
"meetings": meetings, "calls": calls,
"users_mention": users_mention,
"assigned_data": json.dumps(assigned_data)})
return context
@ -254,7 +276,13 @@ class LeadDetailView(LoginRequiredMixin, DetailView):
def update_lead(request, pk):
lead_record = Lead.objects.filter(pk=pk).first()
template_name = "create_lead.html"
users = User.objects.filter(is_active=True).order_by('email')
users = []
if request.user.role == 'ADMIN' or request.user.is_superuser:
users = User.objects.filter(is_active=True).order_by('email')
elif request.user.google.all():
users = []
else:
users = User.objects.filter(role='ADMIN').order_by('email')
status = request.GET.get('status', None)
initial = {}
if status and status == "converted":
@ -303,21 +331,25 @@ def update_lead(request, pk):
all_members_list = 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
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
current_site = get_current_site(request)
recipients = all_members_list
send_email_to_assigned_user.delay(recipients, lead_obj.id, domain=current_site.domain,
protocol=request.scheme)
# 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
# })
# email = EmailMessage(
# mail_subject, message, to=[user.email])
# email.content_subtype = "html"
# email.send()
lead_obj.assigned_to.clear()
lead_obj.assigned_to.add(*request.POST.getlist('assigned_to'))
@ -353,21 +385,28 @@ def update_lead(request, pk):
# account_object.assigned_to.add(*request.POST.getlist('assigned_to'))
assigned_to_list = request.POST.getlist('assigned_to')
current_site = get_current_site(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
})
email = EmailMessage(
mail_subject, message, to=[user.email])
email.content_subtype = "html"
email.send()
recipients = assigned_to_list
send_email_to_assigned_user.delay(recipients, lead_obj.id, domain=current_site.domain,
protocol=request.scheme)
# current_site = get_current_site(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
# })
# email = EmailMessage(
# mail_subject, message, to=[user.email])
# email.content_subtype = "html"
# email.send()
for comment in lead_obj.leads_comments.all():
comment.account = account_object
comment.save()
account_object.save()
status = request.GET.get('status', None)
success_url = reverse('leads:list')
@ -435,7 +474,7 @@ def convert_lead(request, pk):
)
contacts_list = lead_obj.contacts.all().values_list('id', flat=True)
account_object.contacts.add(*contacts_list)
account_object.save()
account_obj = account_object.save()
current_site = get_current_site(request)
for assigned_to_user in lead_obj.assigned_to.all().values_list(
'id', flat=True):
@ -485,6 +524,10 @@ class AddCommentView(LoginRequiredMixin, CreateView):
comment.commented_by = self.request.user
comment.lead = self.lead
comment.save()
comment_id = comment.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'leads', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"comment_id": comment.id, "comment": comment.comment,
"commented_on": comment.commented_on,
@ -514,6 +557,10 @@ class UpdateCommentView(LoginRequiredMixin, View):
def form_valid(self, form):
self.comment_obj.comment = form.cleaned_data.get("comment")
self.comment_obj.save(update_fields=["comment"])
comment_id = self.comment_obj.id
current_site = get_current_site(self.request)
send_email_user_mentions.delay(comment_id, 'leads', domain=current_site.domain,
protocol=self.request.scheme)
return JsonResponse({
"commentid": self.comment_obj.id,
"comment": self.comment_obj.comment,
@ -579,7 +626,8 @@ class AddAttachmentsView(LoginRequiredMixin, CreateView):
"created_by": attachment.created_by.email,
"download_url": reverse(
'common:download_attachment', kwargs={'pk': attachment.id}),
"attachment_display": attachment.get_file_type_display()
"attachment_display": attachment.get_file_type_display(),
"file_type": attachment.file_type()
})
def form_invalid(self, form):

View file

@ -1,4 +1,5 @@
import csv
import datetime
import re
import json
import openpyxl
@ -108,7 +109,8 @@ def xls_doc_validate(document):
validated_rows = []
invalid_rows = []
for sheet_name in sheets:
validated_rows, invalid_rows = get_validated_rows(wb, sheet_name, validated_rows, invalid_rows)
validated_rows, invalid_rows = get_validated_rows(
wb, sheet_name, validated_rows, invalid_rows)
return {"error": False, "validated_rows": validated_rows, "invalid_rows": invalid_rows}
@ -118,11 +120,11 @@ def import_document_validator(document):
document.seek(0, 0)
return csv_doc_validate(document)
except Exception as e:
print (e)
print(e)
try:
return xls_doc_validate(document)
except Exception as e:
print (e)
print(e)
return {"error": True, "message": "Not a valid CSV/XLS file"}
@ -139,8 +141,9 @@ class ContactListForm(forms.ModelForm):
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
self.fields['contacts_file'].required = True
# 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",
@ -155,31 +158,10 @@ class ContactListForm(forms.ModelForm):
else:
self.validated_rows = data.get("validated_rows", [])
self.invalid_rows = data.get("invalid_rows", [])
if self.invalid_rows:
raise forms.ValidationError('Uploaded file is not valid')
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:
@ -203,26 +185,45 @@ class ContactListForm(forms.ModelForm):
class ContactForm(forms.ModelForm):
contact_list = forms.CharField(max_length=5000)
def __init__(self, *args, **kwargs):
request_user = kwargs.pop('request_user', None)
self.obj_instance = kwargs.get('instance', None)
super(ContactForm, self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs = {"class": "form-control"}
self.fields['name'].required = True
self.fields['last_name'].required = True
self.fields['city'].required = True
self.fields['state'].required = True
self.fields['email'].required = True
self.fields['contact_list'].required = False
class Meta:
model = Contact
fields = ["name", "email", "contact_number"]
fields = ["name", "email", "contact_number",
"last_name", "city", "state"]
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) == []:
def clean_email(self):
email = self.cleaned_data.get('email')
if Contact.objects.filter(email=email).exclude(id=self.instance.id).exists():
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")
'Contact with this Email already exists')
return email
return contact_list
# 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):
@ -234,6 +235,7 @@ class ContactsCSVUploadForm(forms.Form):
self.fields['contacts_file'].widget.attrs.update({
"accept": ".csv,.xls,.xlsx,.xlsm,.xlsb,.xml",
})
self.fields['contacts_file'].required = True
def clean_contacts_file(self):
document = self.cleaned_data.get("contacts_file")
@ -275,12 +277,14 @@ class SendCampaignForm(forms.ModelForm):
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)
tags = forms.CharField(max_length=5000, required=False)
class Meta:
model = Campaign
fields = ['title', 'subject', 'html', 'email_template', 'attachment']
fields = ['title', 'subject', 'html', 'email_template', 'attachment', ]
def __init__(self, *args, **kwargs):
self.contacts_list = kwargs.pop('contacts_list')
super(SendCampaignForm, self).__init__(*args, **kwargs)
if self.data.get('schedule_later') and self.data['schedule_later'] == 'true':
self.fields['timezone'].required = True
@ -288,16 +292,31 @@ class SendCampaignForm(forms.ModelForm):
if not self.data.get('reply_to_crm'):
self.fields['reply_to_email'].required = True
def clean_schedule_date_time(self):
schedule_date_time = self.cleaned_data.get('schedule_date_time')
if self.cleaned_data.get('schedule_later') == 'true':
schedule_date_time = datetime.datetime.strptime(
schedule_date_time, '%Y-%m-%d %H:%M')
if schedule_date_time < datetime.datetime.now():
raise forms.ValidationError(
'Schedule Date Time should be greater than current time')
return schedule_date_time
def clean_contact_list(self):
contact_list = self.cleaned_data.get("contact_list")
contact_list = self.contacts_list
if not contact_list or contact_list == '[]' or \
contact_list == []:
raise forms.ValidationError(
"Please choose any of the Contact List")
else:
for each in contact_list:
if not ContactList.objects.filter(id=each).first():
contacts_list_obj = ContactList.objects.filter(id=each).first()
if not contacts_list_obj:
raise forms.ValidationError(
"Please choose a valid Contact List")
return contact_list
if contacts_list_obj.contacts.count() < 1:
raise forms.ValidationError(
'The contact list "{}" does not have any contacts in it .'.format(contacts_list_obj.name))
return contact_list

View file

@ -0,0 +1,27 @@
# Generated by Django 2.1.7 on 2019-06-11 06:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('marketing', '0006_campaign_attachment'),
]
operations = [
migrations.CreateModel(
name='CampaignCompleted',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_completed', models.BooleanField(default=False)),
('campaign', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='campaign_is_completed', to='marketing.Campaign')),
],
),
migrations.AlterField(
model_name='campaignopen',
name='contact',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='contact_campaign', to='marketing.Contact'),
),
]

View file

@ -0,0 +1,45 @@
# Generated by Django 2.1.7 on 2019-06-12 13:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('marketing', '0007_auto_20190611_1226'),
]
operations = [
migrations.AlterModelOptions(
name='campaign',
options={'ordering': ('created_on',)},
),
migrations.AlterModelOptions(
name='contact',
options={'ordering': ['id']},
),
migrations.AlterModelOptions(
name='contactlist',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='emailtemplate',
options={'ordering': ['id']},
),
migrations.AddField(
model_name='campaign',
name='tags',
field=models.ManyToManyField(to='marketing.Tag'),
),
migrations.AlterField(
model_name='campaignopen',
name='campaign',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='campaign_open', to='marketing.Campaign'),
),
migrations.AlterField(
model_name='campaignopen',
name='contact',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='contact_campaign_open', to='marketing.Contact'),
),
]

View file

@ -35,6 +35,9 @@ class EmailTemplate(models.Model):
subject = models.CharField(max_length=5000)
html = models.TextField()
class Meta:
ordering = ['id', ]
@property
def created_by_user(self):
return self.created_by if self.created_by else None
@ -52,6 +55,9 @@ class ContactList(models.Model):
visible_to = models.ManyToManyField(
User, related_name="contact_lists_visible_to")
class Meta:
ordering = ('id',)
@property
def created_by_user(self):
return self.created_by if self.created_by else None
@ -126,6 +132,9 @@ class Contact(models.Model):
def __str__(self):
return self.email
class Meta:
ordering = ['id', ]
class FailedContact(models.Model):
phone_regex = RegexValidator(
@ -191,11 +200,15 @@ class Campaign(models.Model):
opens = models.IntegerField(default='0', blank=True)
opens_unique = models.IntegerField(default='0', blank=True)
bounced = models.IntegerField(default='0')
tags = models.ManyToManyField(Tag)
status = models.CharField(
default="Preparing", choices=STATUS_CHOICES, max_length=20)
attachment = models.FileField(
max_length=1000, upload_to=get_campaign_attachment_path, blank=True, null=True)
class Meta:
ordering = ('-created_on', )
@property
def no_of_unsubscribers(self):
unsubscribers = self.campaign_contacts.filter(
@ -225,12 +238,28 @@ class Campaign(models.Model):
@property
def sent_on_format(self):
if self.schedule_date_time:
c_schedule_date_time = convert_to_custom_timezone(self.schedule_date_time, self.timezone)
c_schedule_date_time = convert_to_custom_timezone(
self.schedule_date_time, self.timezone)
return c_schedule_date_time.strftime('%b %d, %Y %I:%M %p')
else:
c_created_on = convert_to_custom_timezone(self.created_on, self.timezone)
c_created_on = convert_to_custom_timezone(
self.created_on, self.timezone)
return c_created_on.strftime('%b %d, %Y %I:%M %p')
@property
def get_all_emails_count(self):
return self.contact_lists.exclude(contacts__email=None).values_list('contacts__email').count()
@property
def get_all_email_bounces_count(self):
return self.contact_lists.filter(contacts__is_bounced=True
).exclude(contacts__email=None).values_list('contacts__email').count()
@property
def get_all_emails_unsubscribed_count(self):
return self.contact_lists.filter(contacts__is_unsubscribed=True
).exclude(contacts__email=None).values_list('contacts__email').count()
@receiver(models.signals.pre_delete, sender=Campaign)
def comment_attachments_delete(sender, instance, **kwargs):
@ -274,9 +303,19 @@ class CampaignLinkClick(models.Model):
class CampaignOpen(models.Model):
campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE)
campaign = models.ForeignKey(
Campaign, on_delete=models.CASCADE, related_name='campaign_open')
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, blank=True, null=True, on_delete=models.CASCADE, related_name='contact_campaign_open')
class CampaignCompleted(models.Model):
""" This Model Is Used To Check If The Scheduled Later Emails Have Been Sent
related name : campaign_is_completed
"""
campaign = models.OneToOneField(
Campaign, on_delete=models.CASCADE, related_name='campaign_is_completed')
is_completed = models.BooleanField(default=False)

View file

@ -1,14 +1,18 @@
import datetime
import hashlib
from mimetypes import MimeTypes
import pytz
import requests
from mimetypes import MimeTypes
from celery.task import task
from django.conf import settings
from django.core.mail import EmailMessage
from django.template import Template, Context
from django.shortcuts import reverse
from django.template import Context, Template
from common.utils import convert_to_custom_timezone
from marketing.models import Contact, FailedContact, ContactList, Campaign, CampaignLog
from marketing.models import (Campaign, CampaignLog, Contact, ContactList,
FailedContact, CampaignCompleted)
@task
@ -44,7 +48,8 @@ def upload_csv_file(data, invalid_data, user, contact_lists):
contact.state = each['state']
contact.save()
for contact_list in contact_lists:
contact.contact_list.add(ContactList.objects.get(id=int(contact_list)))
contact.contact_list.add(
ContactList.objects.get(id=int(contact_list)))
for each in invalid_data:
contact = FailedContact.objects.filter(email=each['email']).first()
@ -62,7 +67,8 @@ def upload_csv_file(data, invalid_data, user, contact_lists):
contact.state = each['state']
contact.save()
for contact_list in contact_lists:
contact.contact_list.add(ContactList.objects.get(id=int(contact_list)))
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):
@ -78,7 +84,7 @@ def send_campaign_mail(subject, content, from_email, to_email, bcc, reply_to, at
msg.attach(*attachment)
msg.content_subtype = "html"
res = msg.send()
print (res)
print(res)
def get_campaign_message_id(campaign):
@ -92,7 +98,7 @@ def get_campaign_message_id(campaign):
@task
def run_campaign(campaign):
def run_campaign(campaign, domain='demo.django-crm.io', protocol='https'):
try:
campaign = Campaign.objects.get(id=campaign)
attachments = []
@ -123,26 +129,42 @@ def run_campaign(campaign):
from_email = campaign.from_email
else:
from_email = campaign.created_by.email
reply_to_email = str(from_email) + ' <' + str(message_id + '@' + domain_name + '') + '>'
reply_to_email = str(from_email) + ' <' + \
str(message_id + '@' + domain_name + '') + '>'
if not (each_contact.is_bounced or each_contact.is_unsubscribed):
domain_url = settings.URL_FOR_LINKS
link = '<img src="' + domain_url + '/m/cm/track-email/' + \
str(campaign_log.id) + '/contact/' + str(each_contact.id) + '/" height="1" width="1" />'
# domain_url = settings.URL_FOR_LINKS
domain_url = protocol + '://' + domain
img_src_url = domain_url + reverse('marketing:campaign_open', kwargs={
'campaign_log_id': campaign_log.id, 'email_id': each_contact.id})
# images can only be accessed over https
link = '<img src={img_src_url} alt="company_logo" title="company_logo" height="1" width="1" />'.format(
img_src_url=img_src_url)
# link = '<img src="' + domain_url + '/m/cm/track-email/' + \
# str(campaign_log.id) + '/contact/' + \
# str(each_contact.id) + '/" height="1" width="1" alt="company_logo" + \
# title="company_logo"/>'
unsubscribe_from_campaign_url = reverse(
'marketing:unsubscribe_from_campaign', kwargs={'contact_id': each_contact.id, })
unsubscribe_from_campaign_html = "<br><br/><a href={}>Unsubscribe</a>".format(
domain_url + unsubscribe_from_campaign_url)
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}
'email': each_contact.email, 'email_id': each_contact.id,
'unsubscribe_from_campaign_url': unsubscribe_from_campaign_url}
html = Template(html).render(Context(names_dict))
mail_html = html + link
from_email = str(campaign.from_name) + "<" + str(campaign.from_email) + '>'
mail_html = html + link + unsubscribe_from_campaign_html
from_email = str(campaign.from_name) + "<" + \
str(campaign.from_email) + '>'
to_email = [each_contact.email]
send_campaign_mail(
subject, mail_html, from_email, to_email, [], [reply_to_email], attachments)
except Exception as e:
print (e)
print(e)
pass
@ -159,18 +181,20 @@ 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()
if type(each) == dict:
contact = Contact.objects.filter(email=each.get('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()
if type(each) == dict:
contact = Contact.objects.filter(email=each.get('email')).first()
if contact:
contact.is_unsubscribed = True
contact.save()
@task
@ -178,16 +202,23 @@ def send_scheduled_campaigns():
from datetime import datetime
campaigns = Campaign.objects.filter(schedule_date_time__isnull=False)
for each in campaigns:
schedule_date_time = each.schedule_date_time
completed = CampaignCompleted.objects.filter(
is_completed=True).values_list('campaign_id', flat=True)
sent_time = datetime.now().strftime('%Y-%m-%d %H:%M')
sent_time = datetime.strptime(sent_time, '%Y-%m-%d %H:%M')
local_tz = pytz.timezone(settings.TIME_ZONE)
sent_time = local_tz.localize(sent_time)
sent_time = convert_to_custom_timezone(sent_time, each.timezone, to_utc=True)
if each.id not in completed:
schedule_date_time = each.schedule_date_time
if (
str(each.schedule_date_time.date()) == str(sent_time.date()) and
str(schedule_date_time.hour) == str(sent_time.hour)
):
run_campaign(each)
sent_time = datetime.now().strftime('%Y-%m-%d %H:%M')
sent_time = datetime.strptime(sent_time, '%Y-%m-%d %H:%M')
local_tz = pytz.timezone(settings.TIME_ZONE)
sent_time = local_tz.localize(sent_time)
sent_time = convert_to_custom_timezone(
sent_time, each.timezone, to_utc=True)
if (
str(each.schedule_date_time.date()) == str(sent_time.date()) and
str(schedule_date_time.hour) == str(sent_time.hour)
):
run_campaign.delay(each.id)
CampaignCompleted.objects.create(
campaign=each, is_completed=True)

View file

@ -0,0 +1,114 @@
{% extends 'marketing/base.html' %}
{% load staticfiles %}
{% load paginate %}
{% block content %}
{{ block.super }}
<div class="main_container ml-5 mr-5">
<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">
Contact Detail
</div>
<div class="row marl no-gutters justify-content-center">
<div class="col-md-12">
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">
Email{% if form.email.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
<br>
{{contact_obj.email}}
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">First
Name{% if form.name.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
<br>
{{contact_obj.name}}
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">Last
Name{% if form.last_name.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
<br>
{{contact_obj.last_name}}
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">City
{% if form.city.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
<br>
{{contact_obj.city}}
</div>
</div>
<div class="filter_col col-md-12">
<div class="form-group">
<label for="exampleInputEmail1" class="required">State
{% if form.state.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
<br>
{{contact_obj.state}}
</div>
</div>
</div>
<!-- <div class="col-md-12">
<div class="row marl buttons_row text-center form_btn_row">
<button class="btn btn-default save" type="submit">Update</button>
<a href="{% url 'marketing:contacts_list' %}" class="btn btn-default save">Cancel</a>
</div>
</div> -->
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js'></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.js"></script>
<script src="https://malsup.github.io/jquery.form.js"></script>
<script type="text/javascript">
// $(document).ready(function() {
// $('.assigned_users').select2();
// });
$(document).ready(function () {
});
$('form#add_form').ajaxForm({
type: 'POST',
dataType: 'json',
url: ".",
data: $('#add_form').serialize(),
success: function (data) {
if (data.error) {
$('.error').html('')
for (var key in data.errors) {
$('#id__' + key).html("<p>" + data.errors[key][0] + "</p>");
};
$('#submit_btn').removeAttr('disabled')
}
else {
window.location = data.success_url;
}
}
});
</script>
{% endblock js_block %}

View file

@ -0,0 +1,14 @@
{% block body %}
{% block content %}
{{html|safe}}
{% endblock content %}
{% block unsubscribe_link %}
<a href="#">Unsubscribe</a>
{% endblock unsubscribe_link %}
{% endblock body %}

View file

@ -1,6 +1,7 @@
from django.test import TestCase
from django.test import Client
from common.models import User
from django.shortcuts import reverse
import os
# from marketing.models import Tag, Document, ContactList, EmailTemplate, Contact, Campaign, CampaignLinkClick, CampaignLog, Link, CampaignOpen
# from .models import *
@ -22,51 +23,30 @@ class TestMarketingModel(object):
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/'
url1 = reverse('marketing:dashboard')
url2 = reverse('marketing:contact_lists')
url4 = reverse('marketing:contacts_list')
url5 = reverse('marketing:contact_list_new')
url8 = reverse('marketing:email_template_list')
url9 = reverse('marketing:email_template_new')
url12 = reverse('marketing:campaign_list')
url13 = reverse('marketing:campaign_new')
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)
@ -84,7 +64,7 @@ class TestCreateContacts(TestMarketingModel, TestCase):
fp.writelines(data)
with open('marketing/test_file.csv') as fp:
response = self.client.post('/m/cl/list/cnew/',
response = self.client.post(reverse('marketing:contact_list_new'),
{'name': 'sample_test', 'attachment': fp})
self.assertEqual(response.status_code, 200)
@ -99,13 +79,13 @@ class TestViewContactList(TestMarketingModel, TestCase):
# self.assertTemplateUsed(response, 'marketing/lists/detail.html')
def test_contact_list_pagination(self):
response = self.client.get('/m/cl/all/?page=1')
response = self.client.get(reverse('marketing:contact_lists') + '?page=1')
self.assertEqual(response.status_code, 200)
response = self.client.get('/m/cl/all/?page=asdf')
response = self.client.get(reverse('marketing:contact_lists') + '?page=asdf')
self.assertEqual(response.status_code, 200)
response = self.client.get('/m/cl/all/?page=')
response = self.client.get(reverse('marketing:contact_lists') + '?page=')
self.assertEqual(response.status_code, 200)
@ -113,13 +93,13 @@ class TestEmailTemplateList(TestMarketingModel, TestCase):
def test_email_template_list_pagination(self):
response = self.client.get('/m/et/list/?page=1')
response = self.client.get(reverse('marketing:email_template_list') + '?page=1')
self.assertEqual(response.status_code, 200)
response = self.client.get('/m/et/list/?page=asdf')
response = self.client.get(reverse('marketing:email_template_list') + '?page=asdf')
self.assertEqual(response.status_code, 200)
response = self.client.get('/m/et/list/?page=')
response = self.client.get(reverse('marketing:email_template_list') + '?page=')
self.assertEqual(response.status_code, 200)

Some files were not shown because too many files have changed in this diff Show more