From 15bbb94818f02ea30bfb3eee35f7ed255e39d3be Mon Sep 17 00:00:00 2001 From: Ashwin Date: Fri, 5 Jul 2019 10:57:48 +0530 Subject: [PATCH] towards 0.4 and some fixes --- accounts/forms.py | 13 +- accounts/templates/create_account.html | 18 ++- accounts/templates/view_account.html | 31 ++-- accounts/views.py | 14 ++ cases/forms.py | 6 + cases/templates/create_cases.html | 11 ++ cases/views.py | 38 ++++- common/access_decorators_mixins.py | 3 +- common/forms.py | 30 +++- common/templates/create.html | 40 ++++- common/templates/doc_create.html | 11 ++ common/templates/doc_detail.html | 31 +++- common/templates/doc_list.html | 137 ++++++++++-------- common/templates/list.html | 7 +- common/tests.py | 6 +- common/views.py | 38 ++++- contacts/forms.py | 5 + contacts/templates/contacts.html | 8 +- contacts/templates/create_contact.html | 11 ++ contacts/tests.py | 3 - contacts/views.py | 34 +++-- docs/source/conf.py | 2 +- events/forms.py | 24 ++- events/templates/event_create.html | 29 ++-- events/templates/event_detail.html | 6 +- events/templates/events_list.html | 16 +- events/views.py | 25 ++++ invoices/forms.py | 5 + invoices/templates/invoice_create_1.html | 47 +++--- invoices/templates/invoices_detail.html | 6 +- invoices/templates/invoices_detail_1.html | 4 +- invoices/templates/invoices_list.html | 10 +- invoices/views.py | 17 +++ leads/forms.py | 5 + leads/templates/create_lead.html | 12 +- leads/templates/leads.html | 35 ++++- leads/views.py | 40 ++++- marketing/forms.py | 28 +++- marketing/models.py | 4 +- marketing/tasks.py | 1 + marketing/urls.py | 4 +- marketing/views.py | 51 +++++-- opportunity/forms.py | 5 + opportunity/templates/create_opportunity.html | 11 ++ opportunity/templates/opportunity.html | 17 ++- opportunity/views.py | 44 ++++-- tasks/forms.py | 5 + tasks/templates/task_create.html | 25 +++- tasks/templates/task_detail.html | 6 +- tasks/templates/tasks_tasks_list.html | 61 +++++--- tasks/views.py | 15 ++ teams/templates/team_detail.html | 8 +- teams/templates/teams.html | 20 ++- templates/marketing/campaign/details.html | 16 +- templates/marketing/campaign/new.html | 84 ++++++----- templates/marketing/email_template/new.html | 6 +- templates/marketing/lists/all.html | 8 +- templates/marketing/lists/detail.html | 2 +- templates/root.html | 35 ++++- templates/sales/index.html | 4 +- 60 files changed, 930 insertions(+), 308 deletions(-) diff --git a/accounts/forms.py b/accounts/forms.py index 2815fbf9..1e16342b 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -4,9 +4,12 @@ from common.models import Comment, Attachments, User from leads.models import Lead from contacts.models import Contact from django.db.models import Q +from teams.models import Teams class AccountForm(forms.ModelForm): + teams_queryset = [] + teams = forms.MultipleChoiceField(choices=teams_queryset) def __init__(self, *args, **kwargs): account_view = kwargs.pop('account', False) @@ -39,13 +42,17 @@ class AccountForm(forms.ModelForm): # 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["lead"].queryset = Lead.objects.filter().exclude( + status='closed') self.fields["contacts"].queryset = Contact.objects.filter() + self.fields["teams"].choices = [(team.get('id'), team.get('name')) for team in Teams.objects.all().values('id', 'name')] + self.fields["teams"].required = False 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["teams"].required = False self.fields['assigned_to'].required = False if account_view: @@ -56,6 +63,10 @@ class AccountForm(forms.ModelForm): self.fields['billing_postcode'].required = True self.fields['billing_country'].required = True + # lead is not mandatory while editing + if self.instance.id: + self.fields['lead'].required = False + class Meta: model = Account fields = ('name', 'phone', 'email', 'website', 'industry', diff --git a/accounts/templates/create_account.html b/accounts/templates/create_account.html index ef087b88..c7c587ea 100644 --- a/accounts/templates/create_account.html +++ b/accounts/templates/create_account.html @@ -67,7 +67,7 @@ {% for lead in leads %} + {{lead.first_name}} {{lead.last_name}} {% endfor %} {{ account_form.lead.errors }} @@ -142,11 +142,20 @@
+ {% if request.user.is_superuser or request.user.role == 'ADMIN' %} +
+
+ + {{ account_form.teams }} + {{ account_form.teams.errors }} +
+
+ {% endif %}
- {% for user in users %} @@ -229,7 +238,8 @@ $(document).ready(function () { $("#id_contacts").select2(); $("#id_assigned_to").select2() - $("#id_assigned_to").change(function(){ + $("#id_teams").select2() + $("#id_assigned_to").change(function () { $("#id_assigned_to").attr('name', 'assigned_to') }) // $("#id_assigned_to") diff --git a/accounts/templates/view_account.html b/accounts/templates/view_account.html index 5178e4eb..174988d6 100644 --- a/accounts/templates/view_account.html +++ b/accounts/templates/view_account.html @@ -6,7 +6,7 @@ {% block extralinks %} @@ -121,7 +121,9 @@
{% for user in account_record.assigned_to.all %} -
{{ user }}
+
+ {{user}} +
{% endfor %}
@@ -331,7 +333,8 @@ {{ contact.phone }} {{ contact.email }} - View + View Edit {{opportunity.stage}} {{opportunity.probability}} {% if opportunity.probability %} % {% endif %} - View + View Edit {{ case.status }} {{ case.priority }} - View + View Edit {{task.status}} {{task.priority}} {% if task.probability %} % {% endif %} - View + View Edit
- -
- Processing File... + style="z-index: 10; left: 0; right: 0; vertical-align: middle; position: absolute; margin-top: 15%;"> + +
+ Processing File... +
-
{% endblock content %} @@ -637,7 +642,7 @@ cache: false, contentType: false, processData: false, - beforeSend: function(){ + beforeSend: function () { $('#loading_spinner').show() $('#opacity_block').addClass('opacity_block') }, diff --git a/accounts/views.py b/accounts/views.py index 994638d8..01d455c5 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -22,6 +22,7 @@ from common.tasks import send_email_user_mentions from django.contrib.sites.shortcuts import get_current_site from common.access_decorators_mixins import ( sales_access_required, marketing_access_required, SalesAccessRequiredMixin, MarketingAccessRequiredMixin) +from teams.models import Teams class AccountsListView(SalesAccessRequiredMixin, LoginRequiredMixin, TemplateView): model = Account @@ -156,6 +157,13 @@ class CreateAccountView(SalesAccessRequiredMixin, LoginRequiredMixin, CreateView attachment.attachment = self.request.FILES.get( 'account_attachment') attachment.save() + if self.request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=self.request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = account_object.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + account_object.assigned_to.add(user_id) + if self.request.POST.get("savenewform"): return redirect("accounts:new_account") @@ -304,6 +312,12 @@ class AccountUpdateView(SalesAccessRequiredMixin, LoginRequiredMixin, UpdateView attachment.attachment = self.request.FILES.get( 'account_attachment') attachment.save() + if self.request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=self.request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = account_object.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + account_object.assigned_to.add(user_id) if self.request.is_ajax(): data = {'success_url': reverse_lazy( diff --git a/cases/forms.py b/cases/forms.py index 8c4861ff..1df2f2d3 100644 --- a/cases/forms.py +++ b/cases/forms.py @@ -1,9 +1,12 @@ from django import forms from cases.models import Case from common.models import Comment, Attachments +from teams.models import Teams class CaseForm(forms.ModelForm): + teams_queryset = [] + teams = forms.MultipleChoiceField(choices=teams_queryset) def __init__(self, *args, **kwargs): assigned_users = kwargs.pop('assigned_to', []) @@ -23,6 +26,9 @@ class CaseForm(forms.ModelForm): for key, value in self.fields.items(): value.widget.attrs['placeholder'] = value.label + self.fields["teams"].choices = [(team.get('id'), team.get('name')) for team in Teams.objects.all().values('id', 'name')] + self.fields["teams"].required = False + class Meta: model = Case fields = ('assigned_to', 'name', 'status', diff --git a/cases/templates/create_cases.html b/cases/templates/create_cases.html index 448f7c2d..192d2b57 100644 --- a/cases/templates/create_cases.html +++ b/cases/templates/create_cases.html @@ -64,6 +64,16 @@ {{ case_form.closed_on.errors }} + {% if request.user.is_superuser or request.user.role == 'ADMIN' %} +
+
+ + {{ case_form.teams }} + {{ case_form.teams.errors }} +
+
+ {% endif %}
@@ -231,6 +241,7 @@ $(document).ready(function() { $('.assigned_users').select2(); $("#id_contacts").select2(); + $("#id_teams").select2(); }); $('#id_account').change(function(){ diff --git a/cases/views.py b/cases/views.py index a37955b9..62a4e1a3 100644 --- a/cases/views.py +++ b/cases/views.py @@ -22,6 +22,7 @@ from common.tasks import send_email_user_mentions from cases.tasks import send_email_to_assigned_user from common.access_decorators_mixins import ( sales_access_required, marketing_access_required, SalesAccessRequiredMixin, MarketingAccessRequiredMixin) +from teams.models import Teams class CasesListView(SalesAccessRequiredMixin, LoginRequiredMixin, TemplateView): @@ -111,10 +112,10 @@ def create_case(request): if request.POST.getlist('assigned_to', []): case.assigned_to.add(*request.POST.getlist('assigned_to')) assigned_to_list = request.POST.getlist('assigned_to') - current_site = get_current_site(request) - recipients = assigned_to_list - send_email_to_assigned_user.delay(recipients, case.id, domain=current_site.domain, - protocol=request.scheme) + # current_site = get_current_site(request) + # 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.' @@ -132,6 +133,17 @@ def create_case(request): if request.POST.getlist('contacts', []): case.contacts.add(*request.POST.getlist('contacts')) + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = case.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + case.assigned_to.add(user_id) + + current_site = get_current_site(request) + recipients = list(case.assigned_to.all().values_list('id', flat=True)) + send_email_to_assigned_user.delay(recipients, case.id, domain=current_site.domain, + protocol=request.scheme) if request.FILES.get('case_attachment'): attachment = Attachments() attachment.created_by = request.user @@ -248,9 +260,9 @@ def update_case(request, pk): all_members_list = list( set(list(assigned_form_users)) - set(list(assigned_to_ids))) - recipients = all_members_list - send_email_to_assigned_user.delay(recipients, case_obj.id, domain=current_site.domain, - protocol=request.scheme) + # 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) @@ -272,6 +284,18 @@ def update_case(request, pk): else: case_obj.assigned_to.clear() + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = case_obj.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + case_obj.assigned_to.add(user_id) + + current_site = get_current_site(request) + recipients = list(case_obj.assigned_to.all().values_list('id', flat=True)) + send_email_to_assigned_user.delay(recipients, case_obj.id, domain=current_site.domain, + protocol=request.scheme) + if request.POST.getlist('contacts', []): case_obj.contacts.add(*request.POST.getlist('contacts')) diff --git a/common/access_decorators_mixins.py b/common/access_decorators_mixins.py index ddb04c0f..68533240 100644 --- a/common/access_decorators_mixins.py +++ b/common/access_decorators_mixins.py @@ -26,7 +26,7 @@ def marketing_access_required(function): class SalesAccessRequiredMixin(AccessMixin): - """ """ # TODO add docs + """ Mixin used to authorize if a user has sales access """ def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: @@ -39,6 +39,7 @@ class SalesAccessRequiredMixin(AccessMixin): class MarketingAccessRequiredMixin(AccessMixin): + """ Mixin used to authorize if a user has marketing access """ def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: diff --git a/common/forms.py b/common/forms.py index 6c7ea655..6ca29871 100644 --- a/common/forms.py +++ b/common/forms.py @@ -79,8 +79,8 @@ class UserForm(forms.ModelForm): 'has_sales_access', 'has_marketing_access'] def __init__(self, *args, **kwargs): + self.request_user = kwargs.pop('request_user', None) super(UserForm, self).__init__(*args, **kwargs) - self.fields['first_name'].required = True if not self.instance.pk: self.fields['password'].required = True @@ -107,11 +107,29 @@ class UserForm(forms.ModelForm): def clean_has_sales_access(self): sales = self.cleaned_data.get('has_sales_access', False) - marketing = self.data.get('has_marketing_access', False) - if not sales and not marketing: - raise forms.ValidationError('Select atleast one option') + user_role = self.cleaned_data.get('role') + if user_role == 'ADMIN': + is_admin = True + else: + is_admin = False + if self.request_user.role == 'ADMIN' or self.request_user.is_superuser: + if not is_admin: + marketing = self.data.get('has_marketing_access', False) + if not sales and not marketing: + raise forms.ValidationError('Select atleast one option.') + # if not (self.instance.role == 'ADMIN' or self.instance.is_superuser): + # marketing = self.data.get('has_marketing_access', False) + # if not sales and not marketing: + # raise forms.ValidationError('Select atleast one option.') + if self.request_user.role == 'USER': + sales = self.instance.has_sales_access return sales + def clean_has_marketing_access(self): + marketing = self.cleaned_data.get('has_marketing_access', False) + if self.request_user.role == 'USER': + marketing = self.instance.has_marketing_access + return marketing def clean_email(self): email = self.cleaned_data.get("email") @@ -198,6 +216,8 @@ class PasswordResetEmailForm(PasswordResetForm): class DocumentForm(forms.ModelForm): + teams_queryset = [] + teams = forms.MultipleChoiceField(choices=teams_queryset) def __init__(self, *args, **kwargs): self.instance = kwargs.get('instance', None) @@ -214,6 +234,8 @@ class DocumentForm(forms.ModelForm): if users: self.fields['shared_to'].queryset = users self.fields['shared_to'].required = False + self.fields["teams"].choices = [(team.get('id'), team.get('name')) for team in Teams.objects.all().values('id', 'name')] + self.fields["teams"].required = False class Meta: model = Document diff --git a/common/templates/create.html b/common/templates/create.html index 77124f56..8161fdeb 100644 --- a/common/templates/create.html +++ b/common/templates/create.html @@ -70,14 +70,14 @@ {% if request.user.role == 'ADMIN' or request.user.is_superuser %} - {% for key, value in form.role.field.choices %} {% endfor %} {% else %} - {% endif %} @@ -109,18 +109,21 @@ {% if request.user.is_superuser or request.user.role == 'ADMIN' %}
- +
@@ -163,6 +166,27 @@ -{% endblock js_block %} - +{% endblock js_block %} \ No newline at end of file diff --git a/common/templates/list.html b/common/templates/list.html index 674ff2b9..85063da9 100644 --- a/common/templates/list.html +++ b/common/templates/list.html @@ -58,7 +58,7 @@
+ value="{{request.POST.email}}" {%endif%} minlength="2"/>
@@ -78,7 +78,7 @@
@@ -144,7 +144,7 @@ {% if user.is_superuser or user.role == 'ADMIN' %} Sales, Marketing - {% endif %} + {% else %} {% if user.has_sales_access and user.has_marketing_access %} Sales, Marketing {% elif user.has_sales_access and not user.has_marketing_access %} @@ -154,6 +154,7 @@ {% else %} {{''}} {% endif %} + {% endif %} {% ifnotequal request.user user %} diff --git a/common/tests.py b/common/tests.py index 1488c9e7..57b2cc43 100644 --- a/common/tests.py +++ b/common/tests.py @@ -341,7 +341,7 @@ class CreateCommentFile(TestCase): 'username': user1.username, 'role': user1.role, 'profile_pic': user1.profile_pic, 'password': user1.password} - form = UserForm(data=data) + form = UserForm(data=data, request_user=user1) userr = User.objects.get(username='robert') # print(userr) self.assertEqual(len(userr.password), 3) @@ -616,7 +616,7 @@ class TestUserUpdate(ObjectsCreation, TestCase): json.dumps({"error": True, "errors": {"email": ["This field is required."], "first_name": ["This field is required."], "username": ["This field is required."], "role": ["This field is required."], - 'has_sales_access': ['Select atleast one option']}})) + 'has_sales_access': ['Select atleast one option.']}})) def test_user_update_permissions(self): self.user_obj = User.objects.create( @@ -729,7 +729,7 @@ class TestUserCreationView(ObjectsCreation, TestCase): "first_name": ["This field is required."], "username": ["This field is required."], "role": ["This field is required."], - "has_sales_access": ["Select atleast one option"], + "has_sales_access": ["Select atleast one option."], "password": ["This field is required."]}})) response=self.client.post(reverse('common:create_user'), { diff --git a/common/views.py b/common/views.py index 2ca21429..f4015394 100644 --- a/common/views.py +++ b/common/views.py @@ -163,7 +163,12 @@ class LoginView(TemplateView): if user is not None: login(request, user) - return HttpResponseRedirect('/') + if user.has_sales_access: + return HttpResponseRedirect('/') + elif user.has_marketing_access: + return redirect('marketing:dashboard') + else: + return HttpResponseRedirect('/') return render(request, "login.html", { "ENABLE_GOOGLE_LOGIN": settings.ENABLE_GOOGLE_LOGIN, "GP_CLIENT_SECRET": settings.GP_CLIENT_SECRET, @@ -227,7 +232,7 @@ class UsersListView(AdminRequiredMixin, TemplateView): username__icontains=request_post.get('username')) if request_post.get('email'): queryset = queryset.filter( - email=request_post.get('email')) + email__icontains=request_post.get('email')) if request_post.get('role'): queryset = queryset.filter( role=request_post.get('role')) @@ -291,6 +296,12 @@ class CreateUserView(AdminRequiredMixin, CreateView): return JsonResponse({'error': True, 'errors': form.errors}) return response + def get_form_kwargs(self): + kwargs = super(CreateUserView, self).get_form_kwargs() + kwargs.update({"request_user": self.request.user}) + return kwargs + + def get_context_data(self, **kwargs): context = super(CreateUserView, self).get_context_data(**kwargs) context["user_form"] = context["form"] @@ -372,6 +383,11 @@ class UpdateUserView(LoginRequiredMixin, UpdateView): return JsonResponse({'error': True, 'errors': form.errors}) return response + def get_form_kwargs(self): + kwargs = super(UpdateUserView, self).get_form_kwargs() + kwargs.update({"request_user": self.request.user}) + return kwargs + def get_context_data(self, **kwargs): context = super(UpdateUserView, self).get_context_data(**kwargs) context["user_obj"] = self.object @@ -423,6 +439,14 @@ def document_create(request): doc.save() if request.POST.getlist('shared_to'): doc.shared_to.add(*request.POST.getlist('shared_to')) + + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = doc.shared_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + doc.shared_to.add(user_id) + data = {'success_url': reverse_lazy( 'common:doc_list'), 'error': False} return JsonResponse(data) @@ -534,6 +558,13 @@ def document_update(request, pk): if request.POST.getlist('shared_to'): doc.shared_to.add(*request.POST.getlist('shared_to')) + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = doc.shared_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + doc.shared_to.add(user_id) + data = {'success_url': reverse_lazy( 'common:doc_list'), 'error': False} return JsonResponse(data) @@ -891,7 +922,8 @@ def google_login(request): first_name=first_name, last_name=last_name, role="USER", - has_sales_access=True + has_sales_access=True, + has_marketing_access=True, ) google, _ = Google.objects.get_or_create(user=user) diff --git a/contacts/forms.py b/contacts/forms.py index c5025412..83b6e16f 100644 --- a/contacts/forms.py +++ b/contacts/forms.py @@ -1,9 +1,12 @@ from django import forms from contacts.models import Contact from common.models import Comment, Attachments +from teams.models import Teams class ContactForm(forms.ModelForm): + teams_queryset = [] + teams = forms.MultipleChoiceField(choices=teams_queryset) def __init__(self, *args, **kwargs): assigned_users = kwargs.pop('assigned_to', []) @@ -21,6 +24,8 @@ class ContactForm(forms.ModelForm): value.widget.attrs['placeholder'] = "+91-123-456-7890" else: value.widget.attrs['placeholder'] = value.label + self.fields["teams"].choices = [(team.get('id'), team.get('name')) for team in Teams.objects.all().values('id', 'name')] + self.fields["teams"].required = False class Meta: model = Contact diff --git a/contacts/templates/contacts.html b/contacts/templates/contacts.html index e0513160..a88aaa3a 100644 --- a/contacts/templates/contacts.html +++ b/contacts/templates/contacts.html @@ -101,10 +101,14 @@ {% for user in contact_users %} {% if user.profile_pic %} {% thumbnail user.profile_pic "40x40" crop="center" as im %} - + + + {% endthumbnail %} {% else %} - + + + {% endif %} {% empty %} None diff --git a/contacts/templates/create_contact.html b/contacts/templates/create_contact.html index f48236b1..28222285 100644 --- a/contacts/templates/create_contact.html +++ b/contacts/templates/create_contact.html @@ -52,6 +52,16 @@ {{ contact_form.email.errors }}
+ {% if request.user.is_superuser or request.user.role == 'ADMIN' %} +
+
+ + {{ contact_form.teams }} + {{ contact_form.teams.errors }} +
+
+ {% endif %}
@@ -148,6 +158,7 @@ }); $(document).ready(function() { $('.assigned_users').select2(); + $('#id_teams').select2(); }); function remove_attachment(x) { const con = confirm("Do you want to Delete it for Sure!?"); diff --git a/contacts/tests.py b/contacts/tests.py index 70166efe..9a59113a 100644 --- a/contacts/tests.py +++ b/contacts/tests.py @@ -308,9 +308,6 @@ class TestContactCreateContact(ContactObjectsCreation, TestCase): def test_contact_detail_view_error(self): self.client.login(username='mp@mp.com', password='mp') self.contact = Contact.objects.create(created_by=self.user) - # Todo - # response = self.client.get(reverse('contacts:view_contact', args=(self.contact.id,))) - # self.assertEqual(403, response.status_code) def test_contact_update_view(self): self.client.login(username='n@mp.com', password='navi123') diff --git a/contacts/views.py b/contacts/views.py index a6dbcc4a..5231ba7e 100644 --- a/contacts/views.py +++ b/contacts/views.py @@ -21,6 +21,7 @@ from common.tasks import send_email_user_mentions from contacts.tasks import send_email_to_assigned_user from common.access_decorators_mixins import ( sales_access_required, marketing_access_required, SalesAccessRequiredMixin, MarketingAccessRequiredMixin) +from teams.models import Teams class ContactsListView(SalesAccessRequiredMixin, LoginRequiredMixin, TemplateView): @@ -126,11 +127,6 @@ class CreateContactView(SalesAccessRequiredMixin, LoginRequiredMixin, CreateView 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) - 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.' @@ -144,6 +140,18 @@ class CreateContactView(SalesAccessRequiredMixin, LoginRequiredMixin, CreateView # email = EmailMessage(mail_subject, message, to=[user.email]) # email.content_subtype = "html" # email.send() + if self.request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=self.request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = contact_obj.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + contact_obj.assigned_to.add(user_id) + + assigned_to_list = list(contact_obj.assigned_to.all().values_list('id', flat=True)) + current_site = get_current_site(self.request) + recipients = assigned_to_list + send_email_to_assigned_user.delay(recipients, contact_obj.id, domain=current_site.domain, + protocol=self.request.scheme) if self.request.FILES.get('contact_attachment'): attachment = Attachments() @@ -278,10 +286,6 @@ class UpdateContactView(SalesAccessRequiredMixin, LoginRequiredMixin, UpdateView 'assigned_to').values_list('id', flat=True) all_members_list = list( set(list(assigned_form_users)) - set(list(assigned_to_ids))) - 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) @@ -304,6 +308,18 @@ class UpdateContactView(SalesAccessRequiredMixin, LoginRequiredMixin, UpdateView else: contact_obj.assigned_to.clear() + if self.request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=self.request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = contact_obj.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + contact_obj.assigned_to.add(user_id) + + current_site = get_current_site(self.request) + recipients = list(contact_obj.assigned_to.all().values_list('id', flat=True)) + send_email_to_assigned_user.delay(recipients, contact_obj.id, domain=current_site.domain, + protocol=self.request.scheme) + if self.request.FILES.get('contact_attachment'): attachment = Attachments() attachment.created_by = self.request.user diff --git a/docs/source/conf.py b/docs/source/conf.py index b5afe8be..00c667ed 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -263,4 +263,4 @@ texinfo_documents = [ #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False \ No newline at end of file +#texinfo_no_detailmenu = False diff --git a/events/forms.py b/events/forms.py index d28cbd5a..7d398e95 100644 --- a/events/forms.py +++ b/events/forms.py @@ -3,6 +3,7 @@ from contacts.models import Contact from common.models import User, Attachments, Comment from django.db.models import Q from events.models import Event +from teams.models import Teams class EventForm(forms.ModelForm): @@ -18,6 +19,9 @@ class EventForm(forms.ModelForm): recurring_days = forms.MultipleChoiceField( required=False, choices=WEEKDAYS) + teams_queryset = [] + teams = forms.MultipleChoiceField(choices=teams_queryset) + def __init__(self, *args, **kwargs): request_user = kwargs.pop('request_user', None) self.obj_instance = kwargs.get('instance', None) @@ -29,6 +33,7 @@ class EventForm(forms.ModelForm): self.fields['assigned_to'].queryset = User.objects.all() self.fields["contacts"].queryset = Contact.objects.filter() self.fields['assigned_to'].required = True + self.fields["teams"].choices = [(team.get('id'), team.get('name')) for team in Teams.objects.all().values('id', 'name')] elif request_user.google.all(): self.fields['assigned_to'].queryset = User.objects.none() self.fields["contacts"].queryset = Contact.objects.filter( @@ -47,6 +52,7 @@ class EventForm(forms.ModelForm): self.fields['start_date'].widget.attrs['readonly'] = True self.fields['end_date'].widget.attrs['readonly'] = True + self.fields["teams"].required = False self.fields['name'].required = True self.fields['event_type'].required = True self.fields['contacts'].required = True @@ -58,9 +64,10 @@ class EventForm(forms.ModelForm): 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') + if not self.obj_instance: + if self.cleaned_data.get('event_type') == 'Recurring': + if len(recurring_days) < 1: + raise forms.ValidationError('Choose atleast one recurring day') def clean_name(self): name = self.cleaned_data.get('name') @@ -81,10 +88,13 @@ class EventForm(forms.ModelForm): def clean_start_date(self): start_date = self.cleaned_data.get('start_date') - if self.obj_instance: - return self.obj_instance.start_date + if start_date: + if self.obj_instance: + return self.obj_instance.start_date + else: + return start_date else: - return start_date + raise forms.ValidationError('Enter a valid Start date.') def clean_end_date(self): end_date = self.cleaned_data.get('end_date') @@ -100,6 +110,8 @@ class EventForm(forms.ModelForm): def clean_end_time(self): end_time = self.cleaned_data.get('end_time') + if not self.cleaned_data.get('start_time'): + raise forms.ValidationError('Enter a valid start time.') if self.cleaned_data.get('start_time') > end_time: raise forms.ValidationError( 'End Time cannot be less than Start Time') diff --git a/events/templates/event_create.html b/events/templates/event_create.html index 237f3ea1..4c807aa1 100644 --- a/events/templates/event_create.html +++ b/events/templates/event_create.html @@ -50,15 +50,6 @@ {{ form.contacts.errors }}
-
-
- - {{ form.assigned_to }} - {{ form.assigned_to.errors }} -
-
@@ -101,6 +92,25 @@
+ {% if request.user.is_superuser or request.user.role == 'ADMIN' %} +
+
+ + {{ form.teams }} + {{ form.teams.errors }} +
+
+ {% endif %} +
+
+ + {{ form.assigned_to }} + {{ form.assigned_to.errors }} +
+
{% if users %} {% for user in users %} - + {% endfor %} {% else %} diff --git a/events/templates/events_list.html b/events/templates/events_list.html index 8e7d889f..9b3cf3f3 100644 --- a/events/templates/events_list.html +++ b/events/templates/events_list.html @@ -61,7 +61,7 @@
+ value="{{request.POST.date_of_meeting}}">
@@ -161,11 +161,15 @@ {% for user in users %} {% if user.profile_pic %} {% thumbnail user.profile_pic "40x40" crop="center" as im %} - + + + {% endthumbnail %} {% else %} - + + + {% endif %} {% empty %} None @@ -181,11 +185,11 @@ {% if event.created_by.profile_pic %} {% thumbnail event.created_by.profile_pic "40x40" crop="center" as im %} + title="{{ event.created_by }}" style="border-radius: 50%" /> {% endthumbnail %} {% else %} Micro profile pic + style="width: 40px;height: 40px;border-radius: 50%;" title="{{ event.created_by }}" /> {% endif %} {% else %} None diff --git a/events/views.py b/events/views.py index 56d5cee6..4acee5cf 100644 --- a/events/views.py +++ b/events/views.py @@ -17,6 +17,7 @@ from events.models import Event from events.tasks import send_email from common.access_decorators_mixins import ( sales_access_required, marketing_access_required, SalesAccessRequiredMixin, MarketingAccessRequiredMixin) +from teams.models import Teams @login_required @@ -95,6 +96,12 @@ def event_create(request): event.created_by = request.user event.save() form.save_m2m() + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = event.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + event.assigned_to.add(user_id) send_email.delay( event.id, domain=request.get_host(), protocol=request.scheme) @@ -120,6 +127,12 @@ def event_create(request): ) event.contacts.add(*request.POST.getlist('contacts')) event.assigned_to.add(*request.POST.getlist('assigned_to')) + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = event.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + event.assigned_to.add(user_id) send_email.delay( event.id, domain=request.get_host(), protocol=request.scheme) @@ -186,6 +199,12 @@ def event_update(request, event_id): # event.created_by = request.user event.save() form.save_m2m() + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = event.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + event.assigned_to.add(user_id) send_email.delay( event.id, domain=request.get_host(), protocol=request.scheme) @@ -193,6 +212,12 @@ def event_update(request, event_id): event = form.save(commit=False) event.save() form.save_m2m() + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = event.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + event.assigned_to.add(user_id) send_email.delay( event.id, domain=request.get_host(), protocol=request.scheme) diff --git a/invoices/forms.py b/invoices/forms.py index 7b01c46b..c40ba699 100644 --- a/invoices/forms.py +++ b/invoices/forms.py @@ -2,9 +2,12 @@ from django import forms from invoices.models import Invoice from common.models import User, Comment, Attachments, Address from django.db.models import Q +from teams.models import Teams class InvoiceForm(forms.ModelForm): + teams_queryset = [] + teams = forms.MultipleChoiceField(choices=teams_queryset) def __init__(self, *args, **kwargs): request_user = kwargs.pop('request_user', None) @@ -16,6 +19,7 @@ class InvoiceForm(forms.ModelForm): if request_user.role == 'ADMIN' or request_user.is_superuser: self.fields['assigned_to'].queryset = User.objects.all() + self.fields["teams"].choices = [(team.get('id'), team.get('name')) for team in Teams.objects.all().values('id', 'name')] elif request_user.google.all(): self.fields['assigned_to'].queryset = User.objects.none() elif request_user.role == 'USER': @@ -24,6 +28,7 @@ class InvoiceForm(forms.ModelForm): else: pass + self.fields["teams"].required = False self.fields['phone'].widget.attrs.update({ 'placeholder': '+91-123-456-7890'}) self.fields['invoice_title'].required = True diff --git a/invoices/templates/invoice_create_1.html b/invoices/templates/invoice_create_1.html index aeb2d58b..81ef34ab 100644 --- a/invoices/templates/invoice_create_1.html +++ b/invoices/templates/invoice_create_1.html @@ -183,24 +183,6 @@
-
-
-
- -
- {{ form.currency }} - {{ form.currency.errors }} -
-
-
- -
- - {{form.assigned_to}} - {{ form.assigned_to.errors }} -
-
+
+
+ +
+ {{ form.currency }} + {{ form.currency.errors }} +
+
+ {% if request.user.is_superuser or request.user.role == 'ADMIN' %} +
+ +
+ + {{form.teams}} + {{ form.teams.errors }} +
+
+ {% endif %} +
+ +
+ + {{form.assigned_to}} + {{ form.assigned_to.errors }} +
+
+
@@ -279,6 +289,7 @@ $("#id_contacts").select2(); $("#id_assigned_to").select2(); $('#id_currency').select2(); + $('#id_teams').select2(); var date = new Date(); var today = new Date(date.getFullYear(), date.getMonth(), date.getDate()); $('#id_due_date').datetimepicker({ diff --git a/invoices/templates/invoices_detail.html b/invoices/templates/invoices_detail.html index c13ca362..9095099c 100644 --- a/invoices/templates/invoices_detail.html +++ b/invoices/templates/invoices_detail.html @@ -161,7 +161,11 @@
{% for user in invoice.assigned_to.all %} - + {% endfor %}
diff --git a/invoices/templates/invoices_detail_1.html b/invoices/templates/invoices_detail_1.html index 27f55104..ee3c8c66 100644 --- a/invoices/templates/invoices_detail_1.html +++ b/invoices/templates/invoices_detail_1.html @@ -175,7 +175,9 @@
{% for user in invoice.assigned_to.all %}
- {{user}} + + {{user}} +
{% endfor %}
diff --git a/invoices/templates/invoices_list.html b/invoices/templates/invoices_list.html index d81d6c06..24babbcb 100644 --- a/invoices/templates/invoices_list.html +++ b/invoices/templates/invoices_list.html @@ -153,11 +153,15 @@ {% for user in users %} {% if user.profile_pic %} {% thumbnail user.profile_pic "40x40" crop="center" as im %} - + + + {% endthumbnail %} {% else %} - + + + {% endif %} {% empty %} None diff --git a/invoices/views.py b/invoices/views.py index a9d41b77..52a5208f 100644 --- a/invoices/views.py +++ b/invoices/views.py @@ -21,6 +21,7 @@ from invoices.models import Invoice from invoices.tasks import send_email, send_invoice_email, send_invoice_email_cancel from common.access_decorators_mixins import ( sales_access_required, marketing_access_required, SalesAccessRequiredMixin, MarketingAccessRequiredMixin) +from teams.models import Teams @login_required @@ -121,6 +122,14 @@ def invoices_create(request): invoice_obj.to_address = to_address_obj invoice_obj.save() form.save_m2m() + + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = invoice_obj.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + invoice_obj.assigned_to.add(user_id) + 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')}) @@ -190,6 +199,14 @@ def invoice_edit(request, invoice_id): invoice_obj.to_address = to_address_obj invoice_obj.save() form.save_m2m() + + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = invoice_obj.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + invoice_obj.assigned_to.add(user_id) + 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')}) diff --git a/leads/forms.py b/leads/forms.py index 9465c5f9..49860f8b 100644 --- a/leads/forms.py +++ b/leads/forms.py @@ -1,9 +1,12 @@ from django import forms from leads.models import Lead from common.models import Comment, Attachments +from teams.models import Teams class LeadForm(forms.ModelForm): + teams_queryset = [] + teams = forms.MultipleChoiceField(choices=teams_queryset) def __init__(self, *args, **kwargs): assigned_users = kwargs.pop('assigned_to', []) @@ -48,6 +51,8 @@ class LeadForm(forms.ModelForm): 'placeholder': 'Postcode'}) self.fields["country"].choices = [ ("", "--Country--"), ] + list(self.fields["country"].choices)[1:] + self.fields["teams"].choices = [(team.get('id'), team.get('name')) for team in Teams.objects.all().values('id', 'name')] + self.fields["teams"].required = False class Meta: model = Lead diff --git a/leads/templates/create_lead.html b/leads/templates/create_lead.html index 580da1f2..dbc51245 100644 --- a/leads/templates/create_lead.html +++ b/leads/templates/create_lead.html @@ -109,7 +109,16 @@ {{ lead_form.description.errors }}
- + {% if request.user.is_superuser or request.user.role == 'ADMIN' %} +
+
+ + {{ lead_form.teams }} + {{ lead_form.teams.errors }} +
+
+ {% endif %}
@@ -271,6 +280,7 @@ }); $(document).ready(function() { $('.assigned_users').select2(); + $('#id_teams').select2(); }); $(function() { diff --git a/leads/templates/leads.html b/leads/templates/leads.html index ab8b3ee8..0342c213 100644 --- a/leads/templates/leads.html +++ b/leads/templates/leads.html @@ -150,12 +150,16 @@ {% if lead.created_by %} {% if lead.created_by.profile_pic %} {% thumbnail lead.created_by.profile_pic "40x40" crop="center" as im %} - + + + {% endthumbnail %} {% else %} - Micro profile pic + + Micro profile pic + {% endif %} {% else %} None @@ -168,11 +172,15 @@ {% for user in lead_users %} {% if user.profile_pic %} {% thumbnail user.profile_pic "40x40" crop="center" as im %} - + + + {% endthumbnail %} {% else %} - + + + {% endif %} {% empty %} None @@ -255,7 +263,18 @@ {% with lead_users=lead.assigned_to.all %} {% for user in lead_users %} - {{ user.email }}
+ {% if user.profile_pic %} + {% thumbnail user.profile_pic "40x40" crop="center" as im %} + + + + {% endthumbnail %} + {% else %} + + + + {% endif %} {% empty %} None {% endfor %} diff --git a/leads/views.py b/leads/views.py index 8d668328..d7aea95d 100644 --- a/leads/views.py +++ b/leads/views.py @@ -27,6 +27,7 @@ from common.tasks import send_email_user_mentions from leads.tasks import send_email_to_assigned_user from common.access_decorators_mixins import ( sales_access_required, marketing_access_required, SalesAccessRequiredMixin, MarketingAccessRequiredMixin) +from teams.models import Teams class LeadListView(SalesAccessRequiredMixin, LoginRequiredMixin, TemplateView): @@ -138,10 +139,10 @@ def create_lead(request): if request.POST.getlist('assigned_to', []): lead_obj.assigned_to.add(*request.POST.getlist('assigned_to')) assigned_to_list = request.POST.getlist('assigned_to') - current_site = get_current_site(request) - 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) + # 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.' @@ -156,6 +157,17 @@ def create_lead(request): # mail_subject, message, to=[user.email]) # email.content_subtype = "html" # email.send() + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = lead_obj.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + lead_obj.assigned_to.add(user_id) + + current_site = get_current_site(request) + recipients = list(lead_obj.assigned_to.all().values_list('id', flat=True)) + send_email_to_assigned_user.delay(recipients, lead_obj.id, domain=current_site.domain, + protocol=request.scheme) if request.FILES.get('lead_attachment'): attachment = Attachments() @@ -339,10 +351,10 @@ def update_lead(request, pk): all_members_list = list( set(list(assigned_form_users)) - set(list(assigned_to_ids))) - 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) + # 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) @@ -364,6 +376,18 @@ def update_lead(request, pk): else: lead_obj.assigned_to.clear() + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = lead_obj.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + lead_obj.assigned_to.add(user_id) + + current_site = get_current_site(request) + recipients = list(lead_obj.assigned_to.all().values_list('id', flat=True)) + send_email_to_assigned_user.delay(recipients, lead_obj.id, domain=current_site.domain, + protocol=request.scheme) + if request.FILES.get('lead_attachment'): attachment = Attachments() attachment.created_by = request.user diff --git a/marketing/forms.py b/marketing/forms.py index d5675d4a..fa71c06a 100644 --- a/marketing/forms.py +++ b/marketing/forms.py @@ -205,12 +205,12 @@ class ContactForm(forms.ModelForm): fields = ["name", "email", "contact_number", "last_name", "city", "state", "company_name"] - 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( - 'Contact with this Email already exists') - return email + # 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( + # 'Contact with this Email already exists') + # return email # def clean_contact_list(self): # contact_list = self.cleaned_data.get("contact_list") @@ -320,4 +320,18 @@ class SendCampaignForm(forms.ModelForm): raise forms.ValidationError( 'The contact list "{}" does not have any contacts in it .'.format(contacts_list_obj.name)) - return contact_list \ No newline at end of file + return contact_list + + def clean_html(self): + html = self.cleaned_data.get('html') + count = 0 + for i in html: + if i == "{": + count += 1 + elif i == "}": + count -= 1 + if count < 0: + raise forms.ValidationError('Brackets do not match, Enter valid tags.') + if count != 0: + raise forms.ValidationError('Brackets do not match, Enter valid tags.') + return html diff --git a/marketing/models.py b/marketing/models.py index a0f65e32..e69aa02f 100644 --- a/marketing/models.py +++ b/marketing/models.py @@ -293,6 +293,8 @@ class Link(models.Model): clicks = models.IntegerField(default='0') unique = models.IntegerField(default='0') + class Meta: + ordering = ('id',) class CampaignLog(models.Model): created_on = models.DateTimeField(auto_now_add=True) @@ -305,7 +307,7 @@ class CampaignLog(models.Model): class CampaignLinkClick(models.Model): - campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE) + campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE, related_name="campaign_link_click") link = models.ForeignKey( Link, blank=True, null=True, on_delete=models.CASCADE) ip_address = models.GenericIPAddressField() diff --git a/marketing/tasks.py b/marketing/tasks.py index acff0e36..7fd854a1 100644 --- a/marketing/tasks.py +++ b/marketing/tasks.py @@ -155,6 +155,7 @@ def run_campaign(campaign, domain='demo.django-crm.io', protocol='https'): 'state': each_contact.state if each_contact.state else '', 'first_name': each_contact.name, 'email': each_contact.email, 'email_id': each_contact.id, + 'name': each_contact.name + ' ' + each_contact.last_name if each_contact.last_name else '', 'unsubscribe_from_campaign_url': unsubscribe_from_campaign_url} html = Template(html).render(Context(names_dict)) diff --git a/marketing/urls.py b/marketing/urls.py index a28066c5..4cee2610 100644 --- a/marketing/urls.py +++ b/marketing/urls.py @@ -5,7 +5,8 @@ from .views import ( email_template_detail, campaign_list, campaign_new, campaign_edit, campaign_details, campaign_delete, edit_contact_list, delete_contact_list, failed_contact_list_detail, failed_contact_list_download_delete, campaign_link_click, campaign_open, demo_file_download, delete_contact, unsubscribe_from_campaign, contact_detail, - edit_failed_contact, delete_failed_contact, download_contacts_for_campaign, create_campaign_from_template + edit_failed_contact, delete_failed_contact, download_contacts_for_campaign, create_campaign_from_template, + download_links_clicked ) app_name = 'marketing' @@ -46,4 +47,5 @@ urlpatterns = [ path('contacts//view/', contact_detail, name="contact_detail"), path('download-contacts-for-campaign//', download_contacts_for_campaign, name="download_contacts_for_campaign"), path('create-campaign-from-template//', create_campaign_from_template, name="create_campaign_from_template"), + path('download-links-clicked//', download_links_clicked, name="download_links_clicked"), ] diff --git a/marketing/views.py b/marketing/views.py index a7dcc4cf..3aac86fa 100644 --- a/marketing/views.py +++ b/marketing/views.py @@ -137,7 +137,7 @@ def contacts_list(request): name__icontains=data.get('contact_name')) if data.get('contact_email'): - contacts = contacts.filter(email=data.get('contact_email')) + contacts = contacts.filter(email__icontains=data.get('contact_email')) if data.get('created_by'): contacts = contacts.filter(created_by=data.get('created_by')) @@ -303,16 +303,21 @@ def edit_contact(request, pk): if contact_list_obj: contact_obj.contact_list.remove(contact_list_obj) updated_contact = ContactForm(request.POST) - updated_contact_obj = updated_contact.save() - updated_contact_obj.contact_list.add(contact_list_obj) + if updated_contact.is_valid(): + updated_contact_obj = updated_contact.save() + updated_contact_obj.created_by = request.user + updated_contact_obj.save() + updated_contact_obj.contact_list.add(contact_list_obj) else: contact = form.save(commit=False) - contact.save() - form.save_m2m() + if form.is_valid(): + contact.save() + form.save_m2m() else: contact = form.save(commit=False) - contact.save() - form.save_m2m() + if form.is_valid(): + contact.save() + form.save_m2m() if request.POST.get('from_url'): return JsonResponse({'error': False, 'success_url': reverse('marketing:contact_list_detail', args=(request.POST.get('from_url'),))}) @@ -347,7 +352,7 @@ def contact_list_detail(request, pk): name__icontains=request.POST.get('name')) if request.POST.get('email'): contacts_list = contacts_list.filter( - email=request.POST.get('email')) + email__icontains=request.POST.get('email')) if request.POST.get('company_name'): contacts_list = contacts_list.filter( company_name=request.POST.get('company_name')) @@ -554,13 +559,14 @@ def campaign_new(request): # Replace Links with new One # domain_url = '%s://%s' % (request.scheme, request.META['HTTP_HOST']) - domain_url = settings.URL_FOR_LINKS + domain_url = request.scheme + '://' + request.get_host() if Link.objects.filter(campaign_id=camp.id): Link.objects.filter(campaign_id=camp.id).delete() for l in links: link = Link.objects.create(campaign_id=camp.id, original=l) html = html.replace( - 'href="' + l + '"', 'href="' + domain_url + '/m/cm/link/' + str(link.id) + '/e/{{email_id}}"') + 'href="' + l + '"', 'href="' + domain_url + '/marketing/cm/link/' + str(link.id) + '/e/{{email_id}}"') + camp.html_processed = html camp.sent_status = "scheduled" camp.save() @@ -910,3 +916,28 @@ def create_campaign_from_template(request, template_id): url_query_params = '?email_template={}'.format(template_id) url = url_location + url_query_params return HttpResponseRedirect(url) + + +def download_links_clicked(request, campaign_id): + campaign_obj = get_object_or_404(Campaign, pk=campaign_id) + campaign_link_click_objects = campaign_obj.campaign_link_click.all().select_related('link', 'campaign', 'contact') + csv_row_headers = ['email', 'company', 'first name', 'last name', 'city', 'state', 'original link', 'unique clicks', 'total clicks', 'campaign title'] + data = [] + data.append(','.join(csv_row_headers) + '\n') + if campaign_link_click_objects: + for campaign_link_click_object in campaign_link_click_objects: + data.append( + campaign_link_click_object.contact.email + ',' + + campaign_link_click_object.contact.company_name + ',' + + campaign_link_click_object.contact.name + ',' + + campaign_link_click_object.contact.last_name + ',' + + campaign_link_click_object.contact.city + ',' + + campaign_link_click_object.contact.state + ',' + + campaign_link_click_object.link.original + ',' + + str(campaign_link_click_object.link.unique) + ',' + + str(campaign_link_click_object.link.clicks) + ',' + + campaign_link_click_object.link.campaign.title + '\n' + ) + response = HttpResponse(data, content_type='text/plain') + response['Content-Disposition'] = 'attachment; filename={}'.format('data_downloads.csv') + return response diff --git a/opportunity/forms.py b/opportunity/forms.py index c8ef7c0d..609472fc 100644 --- a/opportunity/forms.py +++ b/opportunity/forms.py @@ -1,10 +1,13 @@ from django import forms from opportunity.models import Opportunity from common.models import Comment, Attachments +from teams.models import Teams class OpportunityForm(forms.ModelForm): probability = forms.IntegerField(max_value=100, required=False) + teams_queryset = [] + teams = forms.MultipleChoiceField(choices=teams_queryset) def __init__(self, *args, **kwargs): assigned_users = kwargs.pop('assigned_to', []) @@ -29,6 +32,8 @@ class OpportunityForm(forms.ModelForm): self.fields['probability'].widget.attrs.update({ 'placeholder': 'Probability'}) + self.fields["teams"].choices = [(team.get('id'), team.get('name')) for team in Teams.objects.all().values('id', 'name')] + self.fields["teams"].required = False class Meta: model = Opportunity diff --git a/opportunity/templates/create_opportunity.html b/opportunity/templates/create_opportunity.html index 301a2480..bbf10631 100644 --- a/opportunity/templates/create_opportunity.html +++ b/opportunity/templates/create_opportunity.html @@ -91,6 +91,16 @@ {{opportunity_form.probability.errors}}
+ {% if request.user.is_superuser or request.user.role == 'ADMIN' %} +
+
+ + {{ opportunity_form.teams }} + {{ opportunity_form.teams.errors }} +
+
+ {% endif %}
@@ -261,6 +271,7 @@ $('.assigned_users').select2(); $("#id_contacts").select2(); $('#id_currency').select2(); + $('#id_teams').select2(); }); $('#id_account').change(function(){ diff --git a/opportunity/templates/opportunity.html b/opportunity/templates/opportunity.html index 0253cbe3..33f17f1c 100644 --- a/opportunity/templates/opportunity.html +++ b/opportunity/templates/opportunity.html @@ -1,6 +1,7 @@ {% extends "sales/base.html" %} {% load staticfiles %} {% load paginate %} +{% load thumbnail %} {% block extralinks %} {% endblock %} @@ -137,13 +138,21 @@ {{ opportunity.name }} {% with opportunity_users=opportunity.assigned_to.all %} - {% if opportunity_users %} {% for user in opportunity_users %} - {{ user.email }}
- {% endfor %} + {% if user.profile_pic %} + {% thumbnail user.profile_pic "40x40" crop="center" as im %} + + + + {% endthumbnail %} {% else %} - None + + + {% endif %} + {% empty %} + None + {% endfor %} {% endwith %} diff --git a/opportunity/views.py b/opportunity/views.py index 9e0419e2..4c908211 100644 --- a/opportunity/views.py +++ b/opportunity/views.py @@ -22,7 +22,7 @@ from common.tasks import send_email_user_mentions from opportunity.tasks import send_email_to_assigned_user from common.access_decorators_mixins import ( sales_access_required, marketing_access_required, SalesAccessRequiredMixin, MarketingAccessRequiredMixin) - +from teams.models import Teams class OpportunityListView(SalesAccessRequiredMixin, LoginRequiredMixin, TemplateView): model = Opportunity @@ -125,11 +125,11 @@ def create_opportunity(request): if request.POST.getlist('assigned_to', []): opportunity_obj.assigned_to.add( *request.POST.getlist('assigned_to')) - assigned_to_list = request.POST.getlist('assigned_to') - current_site = get_current_site(request) - recipients = assigned_to_list - send_email_to_assigned_user.delay(recipients, opportunity_obj.id, domain=current_site.domain, - protocol=request.scheme) + # assigned_to_list = request.POST.getlist('assigned_to') + # current_site = get_current_site(request) + # recipients = assigned_to_list + # send_email_to_assigned_user.delay(recipients, opportunity_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 opportunity.' @@ -144,6 +144,18 @@ def create_opportunity(request): # mail_subject, message, to=[user.email]) # email.content_subtype = "html" # email.send() + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = opportunity_obj.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + opportunity_obj.assigned_to.add(user_id) + + current_site = get_current_site(request) + recipients = list(opportunity_obj.assigned_to.all().values_list('id', flat=True)) + send_email_to_assigned_user.delay(recipients, opportunity_obj.id, domain=current_site.domain, + protocol=request.scheme) + if request.POST.getlist('contacts', []): opportunity_obj.contacts.add( *request.POST.getlist('contacts')) @@ -286,10 +298,10 @@ def update_opportunity(request, pk): all_members_list = list( set(list(assigned_form_users)) - set(list(assigned_to_ids))) - current_site = get_current_site(request) - recipients = all_members_list - send_email_to_assigned_user.delay(recipients, opportunity_obj.id, domain=current_site.domain, - protocol=request.scheme) + # current_site = get_current_site(request) + # recipients = all_members_list + # send_email_to_assigned_user.delay(recipients, opportunity_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) @@ -312,6 +324,18 @@ def update_opportunity(request, pk): else: opportunity_obj.assigned_to.clear() + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = opportunity_obj.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + opportunity_obj.assigned_to.add(user_id) + + current_site = get_current_site(request) + recipients = list(opportunity_obj.assigned_to.all().values_list('id', flat=True)) + send_email_to_assigned_user.delay(recipients, opportunity_obj.id, domain=current_site.domain, + protocol=request.scheme) + if request.POST.getlist('contacts', []): opportunity_obj.contacts.add( *request.POST.getlist('contacts')) diff --git a/tasks/forms.py b/tasks/forms.py index 2c07a0aa..c9a5df81 100644 --- a/tasks/forms.py +++ b/tasks/forms.py @@ -4,9 +4,12 @@ from accounts.models import Account from contacts.models import Contact from common.models import User, Attachments, Comment from django.db.models import Q +from teams.models import Teams class TaskForm(forms.ModelForm): + teams_queryset = [] + teams = forms.MultipleChoiceField(choices=teams_queryset) def __init__(self, *args, **kwargs): # assigned_users = kwargs.pop('assigned_to', []) @@ -27,7 +30,9 @@ class TaskForm(forms.ModelForm): self.fields["account"].queryset = Account.objects.filter(status="open") self.fields["contacts"].queryset = Contact.objects.filter() + self.fields["teams"].choices = [(team.get('id'), team.get('name')) for team in Teams.objects.all().values('id', 'name')] + self.fields["teams"].required = False self.fields['assigned_to'].required = False # if assigned_users: # self.fields['assigned_to'].queryset = assigned_users diff --git a/tasks/templates/task_create.html b/tasks/templates/task_create.html index ff328968..a2de1c3c 100644 --- a/tasks/templates/task_create.html +++ b/tasks/templates/task_create.html @@ -17,7 +17,7 @@ {% block content %}
-
+
@@ -35,8 +35,8 @@
- + {{ form.status }} {{ form.status.errors }}
@@ -80,10 +80,6 @@ {{ form.account.errors }}
-
-
-
-
+
+
+
+
+ {% if request.user.is_superuser or request.user.role == 'ADMIN' %} +
+
+ + {{ form.teams }} + {{ form.teams.errors }} +
+
+ {% endif %}
@@ -136,6 +146,7 @@ $(document).ready(function () { $("#id_contacts").select2(); $("#id_assigned_to").select2(); + $("#id_teams").select2(); var date = new Date(); var today = new Date(date.getFullYear(), date.getMonth(), date.getDate()); $('#id_due_date').datetimepicker({ diff --git a/tasks/templates/task_detail.html b/tasks/templates/task_detail.html index b2d22a10..cef0e3d5 100644 --- a/tasks/templates/task_detail.html +++ b/tasks/templates/task_detail.html @@ -101,7 +101,11 @@
{% for user in task.assigned_to.all %} - + {% endfor %}
diff --git a/tasks/templates/tasks_tasks_list.html b/tasks/templates/tasks_tasks_list.html index e865e7d6..fab37e74 100644 --- a/tasks/templates/tasks_tasks_list.html +++ b/tasks/templates/tasks_tasks_list.html @@ -145,14 +145,22 @@ {% endif %} - {% with users=task.assigned_to.all %} - {% if users %} - {% for user in users %} - {{ user.email }}
- {% endfor %} + {% with task_users=task.assigned_to.all %} + {% for user in task_users %} + {% if user.profile_pic %} + {% thumbnail user.profile_pic "40x40" crop="center" as im %} + + + + {% endthumbnail %} {% else %} - None + + + {% endif %} + {% empty %} + None + {% endfor %} {% endwith %} @@ -196,14 +204,23 @@ {% endif %} - {% with users=task.assigned_to.all %} - {% if users %} - {% for user in users %} - {{ user.email }}
- {% endfor %} + + {% with task_users=task.assigned_to.all %} + {% for user in task_users %} + {% if user.profile_pic %} + {% thumbnail user.profile_pic "40x40" crop="center" as im %} + + + + {% endthumbnail %} {% else %} - None + + + {% endif %} + {% empty %} + None + {% endfor %} {% endwith %} @@ -247,14 +264,22 @@ {% endif %} - {% with users=task.assigned_to.all %} - {% if users %} - {% for user in users %} - {{ user.email }}
- {% endfor %} + {% with task_users=task.assigned_to.all %} + {% for user in task_users %} + {% if user.profile_pic %} + {% thumbnail user.profile_pic "40x40" crop="center" as im %} + + + + {% endthumbnail %} {% else %} - None + + + {% endif %} + {% empty %} + None + {% endfor %} {% endwith %} diff --git a/tasks/views.py b/tasks/views.py index 9f430b9d..e11745b9 100644 --- a/tasks/views.py +++ b/tasks/views.py @@ -20,6 +20,7 @@ from tasks.models import Task from tasks.utils import * from common.access_decorators_mixins import ( sales_access_required, marketing_access_required, SalesAccessRequiredMixin, MarketingAccessRequiredMixin) +from teams.models import Teams @login_required @@ -79,6 +80,14 @@ def task_create(request): task.save() task.assigned_to.add(*request.POST.getlist('assigned_to')) task.contacts.add(*request.POST.getlist('contacts')) + + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = task.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + task.assigned_to.add(user_id) + kwargs = {'domain': request.get_host(), 'protocol': request.scheme} send_email.delay(task.id, **kwargs) return JsonResponse({'error': False, 'success_url': reverse('tasks:tasks_list')}) @@ -141,6 +150,12 @@ def task_edit(request, task_id): # task.contacts.clear() # task.assigned_to.add(*request.POST.getlist('assigned_to')) # task.contacts.add(*request.POST.getlist('contacts')) + if request.POST.getlist('teams', []): + user_ids = Teams.objects.filter(id__in=request.POST.getlist('teams')).values_list('users', flat=True) + assinged_to_users_ids = task.assigned_to.all().values_list('id', flat=True) + for user_id in user_ids: + if user_id not in assinged_to_users_ids: + task.assigned_to.add(user_id) kwargs = {'domain': request.get_host(), 'protocol': request.scheme} send_email.delay(task.id, **kwargs) return JsonResponse({'error': False, 'success_url': reverse('tasks:tasks_list')}) diff --git a/teams/templates/team_detail.html b/teams/templates/team_detail.html index 7dd82974..6fa7b4b0 100644 --- a/teams/templates/team_detail.html +++ b/teams/templates/team_detail.html @@ -49,12 +49,16 @@ {% if user.profile_pic %} {% thumbnail user.profile_pic "40x40" crop="center" as im %} - + + + {% endthumbnail %} {% else %} - + + + {% endif %} {% empty %} diff --git a/teams/templates/teams.html b/teams/templates/teams.html index d2c4eed1..ed51eb0b 100644 --- a/teams/templates/teams.html +++ b/teams/templates/teams.html @@ -28,8 +28,8 @@
- +
@@ -128,11 +128,15 @@ {% for user in users %} {% if user.profile_pic %} {% thumbnail user.profile_pic "40x40" crop="center" as im %} - + + + {% endthumbnail %} {% else %} - + + + {% endif %} {% empty %} None @@ -144,11 +148,11 @@ {% if team.created_by.profile_pic %} {% thumbnail team.created_by.profile_pic "40x40" crop="center" as im %} + title="{{ team.created_by }}" style="border-radius: 50%" /> {% endthumbnail %} {% else %} - + {% endif %} {% else %} None diff --git a/templates/marketing/campaign/details.html b/templates/marketing/campaign/details.html index 9c13e243..b640cb65 100644 --- a/templates/marketing/campaign/details.html +++ b/templates/marketing/campaign/details.html @@ -95,7 +95,7 @@
Last Activity - +
@@ -104,7 +104,7 @@ - + @@ -119,9 +119,9 @@ - {% endfor %} @@ -171,10 +171,12 @@ {% with contacts=contact.contact_list.all %} {% endwith %} @@ -229,10 +231,12 @@ {% with contacts=contact.contact_list.all %} {% endwith %} @@ -288,10 +292,12 @@ {% with contacts=contact.contact_list.all %} {% endwith %} diff --git a/templates/marketing/campaign/new.html b/templates/marketing/campaign/new.html index f202f560..8d40fd24 100644 --- a/templates/marketing/campaign/new.html +++ b/templates/marketing/campaign/new.html @@ -197,14 +197,14 @@
- Use the following tags in your subject, plain text or HTML code and +
-
-
+ + +
+
Personalization tags
+

The following tags can be used on both Plain text or HTML. Add these tags to customize the email template, these tags will be replaced with the associated contact details while the mails are sent.

+
    +
  • + First Name: + {% templatetag openvariable %} first_name {% templatetag closevariable %} +
  • +
  • + Last Name: + {% templatetag openvariable %} last_name {% templatetag closevariable %} +
  • +
  • + Full Name: + {% templatetag openvariable %} name {% templatetag closevariable %} +
  • +
  • + Email: + {% templatetag openvariable %} email {% templatetag closevariable %} +
  • +
  • + Company: + {% templatetag openvariable %} company_name {% templatetag closevariable %} +
  • +
  • + City: + {% templatetag openvariable %} city {% templatetag closevariable %} +
  • +
  • + State: + {% templatetag openvariable %} state {% templatetag closevariable %} +
  • + +
+
@@ -292,7 +296,8 @@ {% block js_block %} - + + @@ -350,7 +355,7 @@ [{ header: [1, 2, false] }], ['bold', 'italic', 'underline'], // ['image', 'code-block'] - ['code-block'] + ['code-block', 'link'] ] }, placeholder: 'Compose an epic...', @@ -358,6 +363,7 @@ }); quill.on('text-change', function () { + $('.ql-syntax').css({ "background-color": "#23241f", "color": "#f8f8f2", "overflow": "visible", "border-radius": "3px", "white-space": "pre-wrap", "margin-bottom": "5px", "margin-top": "5px", "padding": "5px 10px" }) var justHtml = quill.root.innerHTML; $('#html_content').val(justHtml); }); diff --git a/templates/marketing/email_template/new.html b/templates/marketing/email_template/new.html index 6b6753a7..db3af5d7 100644 --- a/templates/marketing/email_template/new.html +++ b/templates/marketing/email_template/new.html @@ -81,7 +81,7 @@ toolbar: [ [{ header: [1, 2, false] }], ['bold', 'italic', 'underline'], - ['code-block'] + ['code-block', 'link'] ] }, placeholder: 'Compose an epic...', @@ -98,6 +98,7 @@ var justHtmlContent = document.getElementById('justHtml'); quill.on('text-change', function () { + $('.ql-syntax').css({"background-color":"#23241f","color":"#f8f8f2","overflow":"visible","border-radius":"3px","white-space":"pre-wrap","margin-bottom":"5px","margin-top":"5px","padding":"5px 10px"}) var delta = quill.getContents(); var text = quill.getText(); var justHtml = quill.root.innerHTML; @@ -118,6 +119,9 @@ dataType: 'json', url: $(this).attr('action'), data: $(this).serialize(), + beforeSend: function (e) { + $('.ql-syntax').css({"background-color":"#23241f","color":"#f8f8f2","overflow":"visible","border-radius":"3px","white-space":"pre-wrap","margin-bottom":"5px","margin-top":"5px","padding":"5px 10px"}) + }, success: function (data) { console.log("data", data) if (data.error == false) { diff --git a/templates/marketing/lists/all.html b/templates/marketing/lists/all.html index 4339374c..f722dcc2 100644 --- a/templates/marketing/lists/all.html +++ b/templates/marketing/lists/all.html @@ -44,7 +44,7 @@
+ value="{{request.POST.contact_email}}" minlength="2">
@@ -111,8 +111,8 @@ {% endfor %}
Link(url) Unique TotalExport
{{ link.unique }} {{ link.clicks }} +
{% for contact in contacts %} + {% if contact in campaign.contact_lists.all %}
{{contact.name}} + class="anchor">{{contact.name}}
+ {% endif %} {% endfor %}
{% for contact in contacts %} + {% if contact in campaign.contact_lists.all %} + {% endif %} {% endfor %} {% for contact in contacts %} + {% if contact in campaign.contact_lists.all %} + {% endif %} {% endfor %} - + 1) { - if (!confirm('Are you sure you want to delete? this contact exists in other contact lists, deleting this will delete this contact from other contact lists too!')) + if (!confirm('This contact exists in other contact lists too. Are you sure you want to delete this contact?')) return; window.location = $(this).attr('href'); } else { diff --git a/templates/marketing/lists/detail.html b/templates/marketing/lists/detail.html index 404c75e3..ab9710d9 100644 --- a/templates/marketing/lists/detail.html +++ b/templates/marketing/lists/detail.html @@ -149,7 +149,7 @@ url = $(this).attr('href') var contact_lists = parseInt($(this).attr('data-other-contacts-lists')); if (contact_lists > 1) { - if (!confirm('Are you sure you want to delete? this contact exists in other contact lists, deleting this will delete this contact from other contact lists too!')) + if (!confirm('This contact exists in other contact lists too. Are you sure you want to delete this contact?')) return; window.location = $(this).attr('href'); } else { diff --git a/templates/root.html b/templates/root.html index 615247ef..93cf2ec7 100644 --- a/templates/root.html +++ b/templates/root.html @@ -33,16 +33,44 @@ {% with users=opportunity.assigned_to.all %} {% for user in users|slice:'3' %} - {{user}} + {{user}} {% empty %} None {% endfor %}