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:
parent
12de7e6c62
commit
4f08cf7573
160 changed files with 14026 additions and 2665 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 && \
|
||||
|
|
|
|||
22
README.rst
22
README.rst
|
|
@ -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
|
||||
=========
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
26
accounts/migrations/0007_email.py
Normal file
26
accounts/migrations/0007_email.py
Normal 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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
20
accounts/migrations/0008_account_assigned_to.py
Normal file
20
accounts/migrations/0008_account_assigned_to.py
Normal 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),
|
||||
),
|
||||
]
|
||||
|
|
@ -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
28
accounts/tasks.py
Normal 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()
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
178
accounts/templates/create_mail_accounts.html
Normal file
178
accounts/templates/create_mail_accounts.html
Normal 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 %}
|
||||
|
|
@ -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">×</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> </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> </span>" + data.attachment + " <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 %}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
36
cases/tasks.py
Normal 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()
|
||||
|
|
@ -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">×</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> </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> </span>" + data.attachment + " <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 %}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
101
cases/views.py
101
cases/views.py
|
|
@ -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.
|
|
@ -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)
|
||||
|
|
|
|||
25
common/migrations/0013_auto_20190508_1540.py
Normal file
25
common/migrations/0013_auto_20190508_1540.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
25
common/migrations/0014_auto_20190524_1113.py
Normal file
25
common/migrations/0014_auto_20190524_1113.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
25
common/migrations/0015_auto_20190604_1830.py
Normal file
25
common/migrations/0015_auto_20190604_1830.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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
129
common/tasks.py
Normal 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()
|
||||
18
common/templates/comment_email.html
Normal file
18
common/templates/comment_email.html
Normal 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 %}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">×</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 %}
|
||||
|
|
@ -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 %}
|
||||
|
|
|
|||
14
common/templates/user_status.html
Normal file
14
common/templates/user_status.html
Normal 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 %}
|
||||
17
common/templates/user_status_in.html
Normal file
17
common/templates/user_status_in.html
Normal 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 %}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
109
common/views.py
109
common/views.py
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
34
contacts/tasks.py
Normal 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()
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">×</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> </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> </span>" + data.attachment + " <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 %}
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
0
events/__init__.py
Normal file
3
events/admin.py
Normal file
3
events/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
5
events/apps.py
Normal file
5
events/apps.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class EventsConfig(AppConfig):
|
||||
name = 'events'
|
||||
129
events/forms.py
Normal file
129
events/forms.py
Normal 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')
|
||||
38
events/migrations/0001_initial.py
Normal file
38
events/migrations/0001_initial.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
||||
18
events/migrations/0002_event_date_of_meeting.py
Normal file
18
events/migrations/0002_event_date_of_meeting.py
Normal 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),
|
||||
),
|
||||
]
|
||||
0
events/migrations/__init__.py
Normal file
0
events/migrations/__init__.py
Normal file
45
events/models.py
Normal file
45
events/models.py
Normal 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
44
events/tasks.py
Normal 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
|
||||
19
events/templates/assigned_to_email_template_event.html
Normal file
19
events/templates/assigned_to_email_template_event.html
Normal 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 %}
|
||||
251
events/templates/event_create.html
Normal file
251
events/templates/event_create.html
Normal 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 %}
|
||||
474
events/templates/event_detail.html
Normal file
474
events/templates/event_detail.html
Normal 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">×</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> </span>" + data.attachment + " <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 %}
|
||||
366
events/templates/events_list.html
Normal file
366
events/templates/events_list.html
Normal 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
3
events/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
21
events/urls.py
Normal file
21
events/urls.py
Normal 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
356
events/views.py
Normal 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
0
invoices/__init__.py
Normal file
3
invoices/admin.py
Normal file
3
invoices/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
5
invoices/apps.py
Normal file
5
invoices/apps.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class InvoicesConfig(AppConfig):
|
||||
name = 'invoices'
|
||||
99
invoices/forms.py
Normal file
99
invoices/forms.py
Normal 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:]
|
||||
47
invoices/migrations/0001_initial.py
Normal file
47
invoices/migrations/0001_initial.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
||||
18
invoices/migrations/0002_auto_20190524_1113.py
Normal file
18
invoices/migrations/0002_auto_20190524_1113.py
Normal 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),
|
||||
),
|
||||
]
|
||||
23
invoices/migrations/0003_auto_20190527_1620.py
Normal file
23
invoices/migrations/0003_auto_20190527_1620.py
Normal 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),
|
||||
),
|
||||
]
|
||||
18
invoices/migrations/0004_auto_20190603_1844.py
Normal file
18
invoices/migrations/0004_auto_20190603_1844.py
Normal 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),
|
||||
),
|
||||
]
|
||||
0
invoices/migrations/__init__.py
Normal file
0
invoices/migrations/__init__.py
Normal file
95
invoices/models.py
Normal file
95
invoices/models.py
Normal 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
67
invoices/tasks.py
Normal 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()
|
||||
18
invoices/templates/assigned_to_email_template.html
Normal file
18
invoices/templates/assigned_to_email_template.html
Normal 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 %}
|
||||
20
invoices/templates/invoice_cancelled.html
Normal file
20
invoices/templates/invoice_cancelled.html
Normal 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 %}
|
||||
17
invoices/templates/invoice_detail_email.html
Normal file
17
invoices/templates/invoice_detail_email.html
Normal 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 %}
|
||||
203
invoices/templates/invoice_download_pdf.html
Normal file
203
invoices/templates/invoice_download_pdf.html
Normal 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;">
|
||||
|
||||
</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>
|
||||
277
invoices/templates/invoices_create.html
Normal file
277
invoices/templates/invoices_create.html
Normal 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 %}
|
||||
520
invoices/templates/invoices_detail.html
Normal file
520
invoices/templates/invoices_detail.html
Normal 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">×</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> </span>" + data.attachment + " <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 %}
|
||||
369
invoices/templates/invoices_list.html
Normal file
369
invoices/templates/invoices_list.html
Normal 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
3
invoices/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
26
invoices/urls.py
Normal file
26
invoices/urls.py
Normal 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
415
invoices/views.py
Normal 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)
|
||||
|
|
@ -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'] =\
|
||||
|
|
|
|||
18
leads/migrations/0009_lead_created_from_site.py
Normal file
18
leads/migrations/0009_lead_created_from_site.py
Normal 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),
|
||||
),
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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">×</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">×</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> </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> </span>" + data.attachment + " <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 %}
|
||||
|
|
@ -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/',{})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
176
leads/views.py
176
leads/views.py
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
27
marketing/migrations/0007_auto_20190611_1226.py
Normal file
27
marketing/migrations/0007_auto_20190611_1226.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
45
marketing/migrations/0008_auto_20190612_1905.py
Normal file
45
marketing/migrations/0008_auto_20190612_1905.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
114
marketing/templates/contact_detail.html
Normal file
114
marketing/templates/contact_detail.html
Normal 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 %}
|
||||
14
marketing/templates/html_message_campaign.html
Normal file
14
marketing/templates/html_message_campaign.html
Normal 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 %}
|
||||
|
|
@ -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
Loading…
Reference in a new issue