Merge pull request #176 from MicroPyramid/dev
permission for admin to add attachments
This commit is contained in:
commit
4b7831ac6c
19 changed files with 371 additions and 64 deletions
26
accounts/migrations/0003_auto_20190201_1840.py
Normal file
26
accounts/migrations/0003_auto_20190201_1840.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 2.1.5 on 2019-02-01 13:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0002_auto_20190128_1237'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Tags',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=20)),
|
||||
('slug', models.CharField(blank=True, max_length=20, unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='account',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, to='accounts.Tags'),
|
||||
),
|
||||
]
|
||||
|
|
@ -5,6 +5,17 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from common.models import User, Address, Team
|
||||
from common.utils import INDCHOICES
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
from django.utils.text import slugify
|
||||
|
||||
|
||||
class Tags(models.Model):
|
||||
name = models.CharField(max_length=20)
|
||||
slug = models.CharField(max_length=20, unique = True, blank=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.slug = slugify(self.name)
|
||||
super(Tags, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
class Account(models.Model):
|
||||
|
|
@ -21,6 +32,7 @@ class Account(models.Model):
|
|||
created_by = models.ForeignKey(User, related_name='account_created_by', on_delete=models.CASCADE)
|
||||
created_on = models.DateTimeField(_("Created on"), auto_now_add=True)
|
||||
is_active = models.BooleanField(default=False)
|
||||
tags = models.ManyToManyField(Tags, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
|
|
|||
|
|
@ -50,6 +50,17 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter_col col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail1">Tags</label>
|
||||
<select class="form-control" id="id_tag" name="tag" >
|
||||
<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>
|
||||
{% endfor%}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter_col text-center col-12">
|
||||
<div class="form-group buttons_row">
|
||||
<button class="btn btn-primary save" type="submit">Search</button>
|
||||
|
|
@ -76,10 +87,11 @@
|
|||
{% if accounts_list|length > 0 %}
|
||||
<tr>
|
||||
<th width="5%">ID</th>
|
||||
<th width="25%">Name</th>
|
||||
<th width="20%">Name</th>
|
||||
<th width="15%">City</th>
|
||||
<th width="15%">State</th>
|
||||
<th width="20%">Industry</th>
|
||||
<th width="15%">Tags</th>
|
||||
<th width="10%">Actions</th>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
|
@ -97,6 +109,17 @@
|
|||
<td>{% if each_account.billing_address.city %}{{ each_account.billing_address.city }} {% else %} Not specified {% endif %}</td>
|
||||
<td>{% if each_account.billing_address.state %}{{ each_account.billing_address.state }} {% else %} Not specified {% endif %}</td>
|
||||
<td>{% if each_account.industry %}{{ each_account.industry }} {% else %} Not specified {% endif %}</td>
|
||||
<td style="display: block;">
|
||||
{% with tags=each_account.tags.all %}
|
||||
{% if tags %}
|
||||
{% for tag in tags %}
|
||||
<span class="text-left" id="list_tag">{{ tag.name }}</span>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
No Tags
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@
|
|||
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="//xoxco.com/projects/code/tagsinput/jquery.tagsinput.css" />
|
||||
<script type="text/javascript" src="//xoxco.com/projects/code/tagsinput/jquery.tagsinput.js"></script>
|
||||
<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js'></script>
|
||||
<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/themes/start/jquery-ui.css" />
|
||||
</head>
|
||||
<form id="formid" action='' method="POST" novalidate>
|
||||
<div class="overview_form_block row marl ">
|
||||
<div class="col-md-12">
|
||||
|
|
@ -107,7 +113,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h6 style="color:white;">Copy Address</h6>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -131,6 +144,8 @@
|
|||
{% endblock %}
|
||||
{% block js_block %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.css" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$("#copy_billing_btn").click(function(){
|
||||
var address_line = $("#id_address_line").val()
|
||||
|
|
@ -155,5 +170,47 @@
|
|||
$(document).ready(function() {
|
||||
$('.assigned_users').select2();
|
||||
});
|
||||
|
||||
function onAddTag(tag) {
|
||||
alert("Added a tag: " + tag);
|
||||
}
|
||||
function onRemoveTag(tag) {
|
||||
alert("Removed a tag: " + tag);
|
||||
}
|
||||
|
||||
function onChangeTag(input,tag) {
|
||||
alert("Changed a tag: " + tag);
|
||||
}
|
||||
|
||||
$(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)
|
||||
$(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
|
||||
});
|
||||
|
||||
|
||||
// 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});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock js_block %}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from django.template.loader import render_to_string
|
|||
from django.views.generic import (
|
||||
CreateView, UpdateView, DetailView, TemplateView, View, DeleteView)
|
||||
from accounts.forms import AccountForm, AccountCommentForm, AccountAttachmentForm
|
||||
from accounts.models import Account
|
||||
from accounts.models import Account, Tags
|
||||
from common.models import User, Address, Team, Comment, Attachments
|
||||
from common.utils import INDCHOICES, COUNTRIES, CURRENCY_CODES, CASE_TYPE, PRIORITY_CHOICE, STATUS_CHOICE
|
||||
from contacts.models import Contact
|
||||
|
|
@ -34,6 +34,8 @@ class AccountsListView(LoginRequiredMixin, TemplateView):
|
|||
city__contains=request_post.get('city'))])
|
||||
if request_post.get('industry'):
|
||||
queryset = queryset.filter(industry__icontains=request_post.get('industry'))
|
||||
if request_post.get('tag'):
|
||||
queryset = queryset.filter(tags__in=request_post.get('tag'))
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
|
@ -41,6 +43,7 @@ class AccountsListView(LoginRequiredMixin, TemplateView):
|
|||
context["accounts_list"] = self.get_queryset()
|
||||
context["industries"] = INDCHOICES
|
||||
context["per_page"] = self.request.POST.get('per_page')
|
||||
context['tags'] = Tags.objects.all()
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
|
@ -103,6 +106,16 @@ class CreateAccountView(LoginRequiredMixin, CreateView):
|
|||
email.send()
|
||||
if self.request.POST.getlist('teams', []):
|
||||
account_object.teams.add(*self.request.POST.getlist('teams'))
|
||||
if self.request.POST.get('tags', ''):
|
||||
tags = self.request.POST.get("tags")
|
||||
splitted_tags = tags.split(",")
|
||||
for t in splitted_tags:
|
||||
tag = Tags.objects.filter(name=t.lower())
|
||||
if tag:
|
||||
tag = tag[0]
|
||||
else:
|
||||
tag = Tags.objects.create(name=t.lower())
|
||||
account_object.tags.add(tag)
|
||||
if self.request.POST.get("savenewform"):
|
||||
return redirect("accounts:new_account")
|
||||
else:
|
||||
|
|
@ -208,6 +221,8 @@ class AccountUpdateView(LoginRequiredMixin, UpdateView):
|
|||
return self.form_invalid(form, billing_form, shipping_form)
|
||||
|
||||
def form_valid(self, form, billing_form, shipping_form):
|
||||
assigned_to_ids = self.get_object().assigned_to.all().values_list('id', flat=True)
|
||||
|
||||
# Save Billing & Shipping Address
|
||||
billing_address_object = billing_form.save()
|
||||
shipping_address_object = shipping_form.save()
|
||||
|
|
@ -216,22 +231,15 @@ class AccountUpdateView(LoginRequiredMixin, UpdateView):
|
|||
account_object.billing_address = billing_address_object
|
||||
account_object.shipping_address = shipping_address_object
|
||||
account_object.save()
|
||||
|
||||
assigned_to_ids = account_object.assigned_to.all().values_list('id', flat=True)
|
||||
|
||||
account_object.assigned_to.clear()
|
||||
account_object.teams.clear()
|
||||
all_members_list = []
|
||||
if self.request.POST.getlist('assigned_to', []):
|
||||
account_object.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
assigned_to_list = self.request.POST.getlist('assigned_to')
|
||||
current_site = get_current_site(self.request)
|
||||
|
||||
assigned_form_users = form.cleaned_data.get('assigned_to').values_list('id', flat=True)
|
||||
all_members_list = list(set(list(assigned_form_users)) - set(list(assigned_to_ids)))
|
||||
|
||||
if len(all_members_list):
|
||||
for assigned_to_user in assigned_to_list:
|
||||
for assigned_to_user in all_members_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', {
|
||||
|
|
@ -242,8 +250,22 @@ class AccountUpdateView(LoginRequiredMixin, UpdateView):
|
|||
})
|
||||
email = EmailMessage(mail_subject, message, to=[user.email])
|
||||
email.send()
|
||||
|
||||
account_object.assigned_to.clear()
|
||||
account_object.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
if self.request.POST.getlist('teams', []):
|
||||
account_object.teams.add(*self.request.POST.getlist('teams'))
|
||||
account_object.tags.clear()
|
||||
if self.request.POST.get('tags', ''):
|
||||
tags = self.request.POST.get("tags")
|
||||
splitted_tags = tags.split(",")
|
||||
for t in splitted_tags:
|
||||
tag = Tags.objects.filter(name=t.lower())
|
||||
if tag:
|
||||
tag = tag[0]
|
||||
else:
|
||||
tag = Tags.objects.create(name=t.lower())
|
||||
account_object.tags.add(tag)
|
||||
return redirect("accounts:list")
|
||||
|
||||
def form_invalid(self, form, billing_form, shipping_form):
|
||||
|
|
@ -307,7 +329,8 @@ class AddCommentView(LoginRequiredMixin, CreateView):
|
|||
self.account = get_object_or_404(Account, id=request.POST.get('accountid'))
|
||||
if (
|
||||
request.user in self.account.assigned_to.all() or
|
||||
request.user == self.account.created_by
|
||||
request.user == self.account.created_by or request.user.is_superuser or
|
||||
request.user.role == 'ADMIN'
|
||||
):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
|
|
@ -383,7 +406,8 @@ class AddAttachmentView(LoginRequiredMixin, CreateView):
|
|||
self.account = get_object_or_404(Account, id=request.POST.get('accountid'))
|
||||
if (
|
||||
request.user in self.account.assigned_to.all() or
|
||||
request.user == self.account.created_by
|
||||
request.user == self.account.created_by or request.user.is_superuser or
|
||||
request.user.role == 'ADMIN'
|
||||
):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
|
|
@ -416,7 +440,8 @@ 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:
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
<div class="filter_col col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="id_account">Account{% if case_form.account.field.required %}<span class="error">*</span>{% endif %}</label>
|
||||
{{ case_form.account }}
|
||||
<input type="text" name="account" class="form-control" readonly="">
|
||||
<span class="error">{{case_form.account.errors}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -183,21 +183,25 @@ class UpdateCaseView(LoginRequiredMixin, UpdateView):
|
|||
return self.form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
case_obj = form.save()
|
||||
|
||||
assigned_to_ids = case_obj.assigned_to.all().values_list('id', flat=True)
|
||||
case_obj.assigned_to.clear()
|
||||
assigned_to_ids = self.get_object().assigned_to.all().values_list('id', flat=True)
|
||||
print(assigned_to_ids, 'aaaa')
|
||||
print(assigned_to_ids, 'asso')
|
||||
case_obj = form.save(commit=False)
|
||||
case_obj.teams.clear()
|
||||
case_obj.contacts.clear()
|
||||
case_obj.save()
|
||||
all_members_list = []
|
||||
|
||||
if self.request.POST.getlist('assigned_to', []):
|
||||
case_obj.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
assigned_to_list = self.request.POST.getlist('assigned_to')
|
||||
current_site = get_current_site(self.request)
|
||||
|
||||
assigned_form_users = form.cleaned_data.get('assigned_to').values_list('id', flat=True)
|
||||
print(assigned_form_users, 'assigned_form_users')
|
||||
all_members_list = list(set(list(assigned_form_users)) - set(list(assigned_to_ids)))
|
||||
print(all_members_list, 'alll')
|
||||
|
||||
if len(all_members_list):
|
||||
for assigned_to_user in assigned_to_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', {
|
||||
|
|
@ -207,7 +211,12 @@ class UpdateCaseView(LoginRequiredMixin, UpdateView):
|
|||
'case': case_obj
|
||||
})
|
||||
email = EmailMessage(mail_subject, message, to=[user.email])
|
||||
print(email.to, 'emmmm')
|
||||
email.send()
|
||||
|
||||
case_obj.assigned_to.clear()
|
||||
case_obj.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
|
||||
if self.request.POST.getlist('teams', []):
|
||||
case_obj.teams.add(*self.request.POST.getlist('teams'))
|
||||
if self.request.POST.getlist('contacts', []):
|
||||
|
|
@ -302,7 +311,7 @@ class AddCommentView(LoginRequiredMixin, CreateView):
|
|||
if (
|
||||
request.user in self.case.assigned_to.all() or
|
||||
request.user == self.case.created_by or request.user.is_superuser or
|
||||
request.user.role == 'admin'
|
||||
request.user.role == 'ADMIN'
|
||||
):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
|
|
@ -334,7 +343,7 @@ class UpdateCommentView(LoginRequiredMixin, View):
|
|||
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 or request.user.is_superuser or
|
||||
request.user.role == 'admin'):
|
||||
request.user.role == 'ADMIN'):
|
||||
form = CaseCommentForm(request.POST, instance=self.comment_obj)
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
|
|
@ -361,7 +370,7 @@ 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 or request.user.is_superuser or
|
||||
request.user.role == 'admin'):
|
||||
request.user.role == 'ADMIN'):
|
||||
self.object.delete()
|
||||
data = {"cid": request.POST.get("comment_id")}
|
||||
return JsonResponse(data)
|
||||
|
|
@ -381,7 +390,7 @@ class AddAttachmentView(LoginRequiredMixin, CreateView):
|
|||
if (
|
||||
request.user in self.case.assigned_to.all() or
|
||||
request.user == self.case.created_by or request.user.is_superuser or
|
||||
request.user.role == 'admin'
|
||||
request.user.role == 'ADMIN'
|
||||
):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
|
|
@ -419,7 +428,7 @@ 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'):
|
||||
request.user.role == 'ADMIN'):
|
||||
self.object.delete()
|
||||
data = {"attachment_object": request.POST.get("attachment_id"), "error": False}
|
||||
return JsonResponse(data)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
from django.contrib.auth import logout, authenticate, login
|
||||
from django.core.mail import EmailMessage
|
||||
from django.contrib.auth.hashers import check_password
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, AccessMixin
|
||||
from django.http import HttpResponseRedirect, JsonResponse, HttpResponse, Http404
|
||||
|
|
@ -11,6 +12,7 @@ from common.forms import UserForm, LoginForm, ChangePasswordForm, PasswordResetE
|
|||
from django.contrib.auth.views import PasswordResetView
|
||||
from django.urls import reverse_lazy
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
|
||||
def handler404(request, exception):
|
||||
|
|
@ -157,6 +159,16 @@ class CreateUserView(AdminRequiredMixin, CreateView):
|
|||
if form.cleaned_data.get("password"):
|
||||
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
|
||||
|
||||
})
|
||||
email = EmailMessage(mail_subject, message, to=[user.email])
|
||||
email.send()
|
||||
|
||||
if self.request.is_ajax():
|
||||
data = {'success_url': reverse_lazy('common:users_list'), 'error': False}
|
||||
return JsonResponse(data)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
<div class="filter_col col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail1">Account{% if contact_form.account.field.required %}<span class="error">*</span>{% endif %}</label>
|
||||
{{ contact_form.account }}
|
||||
<input type="text" name="account" class="form-control" readonly="">
|
||||
<span class="error">{{ contact_form.account.errors }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -190,22 +190,17 @@ class UpdateContactView(LoginRequiredMixin, UpdateView):
|
|||
return self.form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
assigned_to_ids = self.get_object().assigned_to.all().values_list('id', flat=True)
|
||||
|
||||
contact_obj = form.save(commit=False)
|
||||
|
||||
assigned_to_ids = contact_obj.assigned_to.all().values_list('id', flat=True)
|
||||
|
||||
contact_obj.assigned_to.clear()
|
||||
contact_obj.teams.clear()
|
||||
all_members_list = []
|
||||
if self.request.POST.getlist('assigned_to', []):
|
||||
contact_obj.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
assigned_to_list = self.request.POST.getlist('assigned_to')
|
||||
current_site = get_current_site(self.request)
|
||||
|
||||
assigned_form_users = form.cleaned_data.get('assigned_to').values_list('id', flat=True)
|
||||
all_members_list = list(set(list(assigned_form_users)) - set(list(assigned_to_ids)))
|
||||
if len(all_members_list):
|
||||
for assigned_to_user in assigned_to_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', {
|
||||
|
|
@ -216,6 +211,10 @@ class UpdateContactView(LoginRequiredMixin, UpdateView):
|
|||
})
|
||||
email = EmailMessage(mail_subject, message, to=[user.email])
|
||||
email.send()
|
||||
|
||||
contact_obj.assigned_to.clear()
|
||||
contact_obj.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
|
||||
if self.request.POST.getlist('teams', []):
|
||||
contact_obj.teams.add(*self.request.POST.getlist('teams'))
|
||||
if self.request.is_ajax():
|
||||
|
|
@ -283,7 +282,8 @@ class AddCommentView(LoginRequiredMixin, CreateView):
|
|||
self.contact = get_object_or_404(Contact, id=request.POST.get('contactid'))
|
||||
if (
|
||||
request.user in self.contact.assigned_to.all() or
|
||||
request.user == self.contact.created_by
|
||||
request.user == self.contact.created_by or request.user.is_superuser or
|
||||
request.user.role == 'ADMIN'
|
||||
):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
|
|
@ -370,7 +370,8 @@ class AddAttachmentsView(LoginRequiredMixin, CreateView):
|
|||
self.contact = get_object_or_404(Contact, id=request.POST.get('contactid'))
|
||||
if (
|
||||
request.user in self.contact.assigned_to.all() or
|
||||
request.user == self.contact.created_by
|
||||
request.user == self.contact.created_by or request.user.is_superuser or
|
||||
request.user.role == 'ADMIN'
|
||||
):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
|
|
@ -403,7 +404,8 @@ 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:
|
||||
if (request.user == self.object.created_by or request.user.is_superuser or
|
||||
request.user.role == 'ADMIN'):
|
||||
self.object.delete()
|
||||
data = {"aid": request.POST.get("attachment_id")}
|
||||
return JsonResponse(data)
|
||||
|
|
|
|||
19
leads/migrations/0002_lead_tags.py
Normal file
19
leads/migrations/0002_lead_tags.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 2.1.5 on 2019-02-01 13:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0003_auto_20190201_1840'),
|
||||
('leads', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='lead',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, to='accounts.Tags'),
|
||||
),
|
||||
]
|
||||
|
|
@ -6,6 +6,7 @@ from accounts.models import Account
|
|||
from common.models import User, Address, Team
|
||||
from common.utils import LEAD_STATUS, LEAD_SOURCE
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
from accounts.models import Tags
|
||||
|
||||
|
||||
class Lead(models.Model):
|
||||
|
|
@ -34,6 +35,7 @@ class Lead(models.Model):
|
|||
created_on = models.DateTimeField(_("Created on"), auto_now_add=True)
|
||||
is_active = models.BooleanField(default=False)
|
||||
enquery_type = models.CharField(max_length=255, blank=True, null=True)
|
||||
tags = models.ManyToManyField(Tags, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering =['-created_on']
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@
|
|||
|
||||
{% endblock %}
|
||||
{%block content%}
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="//xoxco.com/projects/code/tagsinput/jquery.tagsinput.css" />
|
||||
<script type="text/javascript" src="//xoxco.com/projects/code/tagsinput/jquery.tagsinput.js"></script>
|
||||
<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js'></script>
|
||||
<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/themes/start/jquery-ui.css" />
|
||||
</head>
|
||||
<form id="add_form" method="POST" action="" novalidate>
|
||||
<div class="overview_form_block row marl">
|
||||
<div class="col-md-12">
|
||||
|
|
@ -132,9 +138,15 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 lead_obj.tags.all %}{{t.name}}, {% endfor %}" class="tags"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter_col col-md-12">
|
||||
<div class="filter_col col-md-6">
|
||||
<div class="form-group col-md-12">
|
||||
<label for="exampleInputEmail1" >Description{% if lead_form.description.field.required %}<span class="error">*</span>{% endif %}</label>
|
||||
{{ lead_form.description }}
|
||||
|
|
@ -159,6 +171,8 @@
|
|||
{% endblock %}
|
||||
{% block js_block %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.css" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(".savenew").on('click', function(e){
|
||||
e.preventDefault()
|
||||
|
|
@ -168,5 +182,35 @@
|
|||
$(document).ready(function() {
|
||||
$('.assigned_users').select2();
|
||||
});
|
||||
$(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)
|
||||
$(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
|
||||
});
|
||||
|
||||
|
||||
// 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});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock js_block %}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,17 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter_col col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail1">Tags</label>
|
||||
<select class="form-control" id="id_tag" name="tag" >
|
||||
<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>
|
||||
{% endfor%}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter_col text-center col-12">
|
||||
<div class="form-group buttons_row">
|
||||
<button class="btn btn-primary save" type="submit">Search</button>
|
||||
|
|
@ -80,10 +91,11 @@
|
|||
{% if lead_obj|length > 0 %}
|
||||
<tr>
|
||||
<th width="5%">ID</th>
|
||||
<th width="25%" >Name</th>
|
||||
<th width="15%" >Status</th>
|
||||
<th width="15%" >Assigned User</th>
|
||||
<th width="20%" >Created On</th>
|
||||
<th width="20%" >Name</th>
|
||||
<th width="5%" >Status</th>
|
||||
<th width="20%" >Assigned User</th>
|
||||
<th width="25%" >Tags</th>
|
||||
<th width="15%" >Created On</th>
|
||||
<th width="10%" >Actions</th>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
|
@ -110,6 +122,17 @@
|
|||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td style="display: block;">
|
||||
{% with tags=lead.tags.all %}
|
||||
{% if tags %}
|
||||
{% for tag in tags %}
|
||||
<span class="text-left" id="list_tag">{{ tag.name }}</span><br>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
No Tags
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td>{{ lead.created_on }}</td>
|
||||
<td class="actions">
|
||||
{# <a href="{% url 'leads:view_lead' lead.id %}" class="btn btn-info view"><i class="fas fa-eye"></i> </a> #}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from django.urls import reverse
|
|||
from django.views.generic import (
|
||||
CreateView, UpdateView, DetailView, ListView, TemplateView, View)
|
||||
|
||||
from accounts.models import Account
|
||||
from accounts.models import Account, Tags
|
||||
from common.forms import BillingAddressForm
|
||||
from common.models import User, Comment, Team, Attachments
|
||||
from common.utils import LEAD_STATUS, LEAD_SOURCE, COUNTRIES
|
||||
|
|
@ -40,6 +40,8 @@ class LeadListView(LoginRequiredMixin, TemplateView):
|
|||
queryset = queryset.filter(email__icontains=request_post.get('email'))
|
||||
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'))
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
|
@ -47,6 +49,7 @@ class LeadListView(LoginRequiredMixin, TemplateView):
|
|||
context["lead_obj"] = self.get_queryset()
|
||||
context["status"] = LEAD_STATUS
|
||||
context["per_page"] = self.request.POST.get('per_page')
|
||||
context['tags'] = Tags.objects.all()
|
||||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
|
@ -83,6 +86,16 @@ class CreateLeadView(LoginRequiredMixin, CreateView):
|
|||
lead_obj.address = address_object
|
||||
lead_obj.created_by = self.request.user
|
||||
lead_obj.save()
|
||||
if self.request.POST.get('tags', ''):
|
||||
tags = self.request.POST.get("tags")
|
||||
splitted_tags = tags.split(",")
|
||||
for t in splitted_tags:
|
||||
tag = Tags.objects.filter(name=t)
|
||||
if tag:
|
||||
tag = tag[0]
|
||||
else:
|
||||
tag = Tags.objects.create(name=t)
|
||||
lead_obj.tags.add(tag)
|
||||
if self.request.POST.getlist('assigned_to', []):
|
||||
lead_obj.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
assigned_to_list = self.request.POST.getlist('assigned_to')
|
||||
|
|
@ -105,9 +118,12 @@ class CreateLeadView(LoginRequiredMixin, CreateView):
|
|||
created_by=self.request.user, name=lead_obj.account_name,
|
||||
email=lead_obj.email, phone=lead_obj.phone,
|
||||
description=self.request.POST.get('description'),
|
||||
website=self.request.POST.get('website')
|
||||
website=self.request.POST.get('website'),
|
||||
)
|
||||
account_object.billing_address = address_object
|
||||
for tag in lead_obj.tags.all():
|
||||
account_object.tags.add(tag)
|
||||
account_object.tags.add(address_object)
|
||||
if self.request.POST.getlist('assigned_to', []):
|
||||
account_object.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
assigned_to_list = self.request.POST.getlist('assigned_to')
|
||||
|
|
@ -238,24 +254,33 @@ class UpdateLeadView(LoginRequiredMixin, UpdateView):
|
|||
return self.form_invalid(form, address_form)
|
||||
|
||||
def form_valid(self, form, address_form):
|
||||
assigned_to_ids = self.get_object().assigned_to.all().values_list('id', flat=True)
|
||||
address_obj = address_form.save()
|
||||
lead_obj = form.save(commit=False)
|
||||
assigned_to_ids = lead_obj.assigned_to.all().values_list('id', flat=True)
|
||||
lead_obj.address = address_obj
|
||||
lead_obj.save()
|
||||
lead_obj.assigned_to.clear()
|
||||
lead_obj.teams.clear()
|
||||
lead_obj.tags.clear()
|
||||
all_members_list = []
|
||||
if self.request.POST.get('tags', ''):
|
||||
tags = self.request.POST.get("tags")
|
||||
splitted_tags = tags.split(",")
|
||||
for t in splitted_tags:
|
||||
tag = Tags.objects.filter(name=t)
|
||||
if tag:
|
||||
tag = tag[0]
|
||||
else:
|
||||
tag = Tags.objects.create(name=t)
|
||||
lead_obj.tags.add(tag)
|
||||
if self.request.POST.getlist('assigned_to', []):
|
||||
lead_obj.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
if self.request.POST.get('status') != "converted":
|
||||
assigned_to_list = self.request.POST.getlist('assigned_to')
|
||||
|
||||
current_site = get_current_site(self.request)
|
||||
|
||||
assigned_form_users = form.cleaned_data.get('assigned_to').values_list('id', flat=True)
|
||||
all_members_list = list(set(list(assigned_form_users)) - set(list(assigned_to_ids)))
|
||||
if len(all_members_list):
|
||||
for assigned_to_user in assigned_to_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', {
|
||||
|
|
@ -266,6 +291,10 @@ class UpdateLeadView(LoginRequiredMixin, UpdateView):
|
|||
})
|
||||
email = EmailMessage(mail_subject, message, to=[user.email])
|
||||
email.send()
|
||||
|
||||
lead_obj.assigned_to.clear()
|
||||
lead_obj.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
|
||||
if self.request.POST.getlist('teams', []):
|
||||
lead_obj.teams.add(*self.request.POST.getlist('teams'))
|
||||
if self.request.POST.get('status') == "converted":
|
||||
|
|
@ -276,6 +305,8 @@ class UpdateLeadView(LoginRequiredMixin, UpdateView):
|
|||
website=self.request.POST.get('website')
|
||||
)
|
||||
account_object.billing_address = address_obj
|
||||
for tag in lead_obj.tags.all():
|
||||
account_object.tags.add(tag)
|
||||
if self.request.POST.getlist('assigned_to', []):
|
||||
account_object.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
assigned_to_list = self.request.POST.getlist('assigned_to')
|
||||
|
|
@ -389,7 +420,8 @@ class AddCommentView(LoginRequiredMixin, CreateView):
|
|||
self.lead = get_object_or_404(Lead, id=request.POST.get('leadid'))
|
||||
if (
|
||||
request.user in self.lead.assigned_to.all() or
|
||||
request.user == self.lead.created_by
|
||||
request.user == self.lead.created_by or request.user.is_superuser or
|
||||
request.user.role == 'ADMIN'
|
||||
):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
|
|
@ -476,7 +508,8 @@ class AddAttachmentsView(LoginRequiredMixin, CreateView):
|
|||
self.lead = get_object_or_404(Lead, id=request.POST.get('leadid'))
|
||||
if (
|
||||
request.user in self.lead.assigned_to.all() or
|
||||
request.user == self.lead.created_by
|
||||
request.user == self.lead.created_by or request.user.is_superuser or
|
||||
request.user.role == 'ADMIN'
|
||||
):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
|
|
@ -509,7 +542,8 @@ 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:
|
||||
if (request.user == self.object.created_by or request.user.is_superuser or
|
||||
request.user.role == 'ADMIN'):
|
||||
self.object.delete()
|
||||
data = {"aid": request.POST.get("attachment_id")}
|
||||
return JsonResponse(data)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
<div class="filter_col col-md-12">
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail1">Account{% if opportunity_form.account.field.required %}<span class="error">*</span>{% endif %}</label>
|
||||
{{ opportunity_form.account }}
|
||||
<input type="text" name="account" class="form-control" readonly="">
|
||||
<span class="error">{{opportunity_form.account.errors}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -185,24 +185,22 @@ class UpdateOpportunityView(LoginRequiredMixin, UpdateView):
|
|||
return self.form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
assigned_to_ids = self.get_object().assigned_to.all().values_list('id', flat=True)
|
||||
opportunity_obj = form.save(commit=False)
|
||||
if self.request.POST.get('stage') in ['CLOSED WON', 'CLOSED LOST']:
|
||||
opportunity_obj.closed_by = self.request.user
|
||||
opportunity_obj.save()
|
||||
assigned_to_ids = opportunity_obj.assigned_to.all().values_list('id', flat=True)
|
||||
opportunity_obj.assigned_to.clear()
|
||||
opportunity_obj.teams.clear()
|
||||
opportunity_obj.contacts.clear()
|
||||
all_members_list = []
|
||||
if self.request.POST.getlist('assigned_to', []):
|
||||
opportunity_obj.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
assigned_to_list = self.request.POST.getlist('assigned_to')
|
||||
current_site = get_current_site(self.request)
|
||||
print(assigned_to_ids, 'aasiigned to ids')
|
||||
|
||||
assigned_form_users = form.cleaned_data.get('assigned_to').values_list('id', flat=True)
|
||||
all_members_list = list(set(list(assigned_form_users)) - set(list(assigned_to_ids)))
|
||||
if len(all_members_list):
|
||||
for assigned_to_user in assigned_to_list:
|
||||
for assigned_to_user in all_members_list:
|
||||
user = get_object_or_404(User, pk=assigned_to_user)
|
||||
mail_subject = 'Assigned to opportunity.'
|
||||
message = render_to_string('assigned_to/opportunity_assigned.html', {
|
||||
|
|
@ -213,6 +211,10 @@ class UpdateOpportunityView(LoginRequiredMixin, UpdateView):
|
|||
})
|
||||
email = EmailMessage(mail_subject, message, to=[user.email])
|
||||
email.send()
|
||||
|
||||
opportunity_obj.assigned_to.clear()
|
||||
opportunity_obj.assigned_to.add(*self.request.POST.getlist('assigned_to'))
|
||||
|
||||
if self.request.POST.getlist('teams', []):
|
||||
opportunity_obj.teams.add(*self.request.POST.getlist('teams'))
|
||||
if self.request.POST.getlist('contacts', []):
|
||||
|
|
@ -283,7 +285,8 @@ class AddCommentView(LoginRequiredMixin, CreateView):
|
|||
self.opportunity = get_object_or_404(Opportunity, id=request.POST.get('opportunityid'))
|
||||
if (
|
||||
request.user in self.opportunity.assigned_to.all() or
|
||||
request.user == self.opportunity.created_by
|
||||
request.user == self.opportunity.created_by or request.user.is_superuser or
|
||||
request.user.role == 'ADMIN'
|
||||
):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
|
|
@ -370,7 +373,8 @@ class AddAttachmentsView(LoginRequiredMixin, CreateView):
|
|||
self.opportunity = get_object_or_404(Opportunity, id=request.POST.get('opportunityid'))
|
||||
if (
|
||||
request.user in self.opportunity.assigned_to.all() or
|
||||
request.user == self.opportunity.created_by
|
||||
request.user == self.opportunity.created_by or request.user.is_superuser or
|
||||
request.user.role == 'ADMIN'
|
||||
):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
|
|
@ -403,7 +407,8 @@ 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:
|
||||
if (request.user == self.object.created_by or request.user.is_superuser or
|
||||
request.user.role == 'ADMIN'):
|
||||
self.object.delete()
|
||||
data = {"aid": request.POST.get("attachment_id")}
|
||||
return JsonResponse(data)
|
||||
|
|
|
|||
|
|
@ -806,3 +806,14 @@ padding:0px !important;
|
|||
padding:3px 10px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#list_tag{
|
||||
background: #e5e5e5;
|
||||
color: #000;
|
||||
font-weight: 500;
|
||||
padding: 1px 5px;
|
||||
margin-right: 5px;
|
||||
font-size: 0.9rem;
|
||||
display: inline-block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
3
templates/new_user.html
Normal file
3
templates/new_user.html
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
hello {{user.email}},
|
||||
|
||||
<p>Your account has been successfully created in Django CRM by {{created_by.email}}</p>
|
||||
Loading…
Reference in a new issue