* changing to SaaS

* saas beta

* restructuring

* restruturing

* restruturing

* restruturing

* restruturing

* restruturing

* added new api
This commit is contained in:
Ashwin Kumar 2020-11-09 14:11:56 +05:30 committed by GitHub
parent 0957ee8cda
commit 87c79bcd2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 3070 additions and 183 deletions

View file

@ -31,8 +31,13 @@ sudo apt install postgresql xvfb libfontconfig wkhtmltopdf git libpq-dev python3
sudo gem install sass
sudo apt-get install postfix
```
#### postfix package installation process
* After running ```sudo apt-get install postfix``` in terminal, select ```Internet``` as a configuration from the pop-up and click ok. Next it will ask to enter the ```System mail name``` which will be the used as a ```From Email Address``` during sending the mail.
#### Install dependencies
---

12
accounts/api_urls.py Normal file
View file

@ -0,0 +1,12 @@
from django.urls import path
from accounts import api_views
app_name = 'api_accounts'
urlpatterns = [
path("accounts-list/", api_views.AccountsListView.as_view()),
path("accounts-create/", api_views.CreateAccountView.as_view()),
path("<int:pk>/view/", api_views.AccountDetailView.as_view()),
path("accounts/<int:pk>/update/", api_views.AccountUpdateView.as_view()),
path("accounts/<int:pk>/delete/", api_views.AccountDeleteView.as_view()),
]

500
accounts/api_views.py Normal file
View file

@ -0,0 +1,500 @@
import pytz
from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from accounts.forms import (
AccountForm,
)
from accounts.models import Account, Tags
from accounts.tasks import send_email_to_assigned_user
from accounts import swagger_params
from accounts.serializer import AccountSerializer, TagsSerailizer
from common.models import User, Attachments
from common.utils import (
COUNTRIES,
INDCHOICES,
)
from common.custom_auth import JSONWebTokenAuthentication
from common.serializer import UserSerializer, CommentSerializer, AttachmentsSerializer
from common.utils import (
CASE_TYPE,
COUNTRIES,
CURRENCY_CODES,
INDCHOICES,
PRIORITY_CHOICE,
STATUS_CHOICE,
)
from contacts.models import Contact
from leads.models import Lead
from opportunity.models import SOURCES, STAGES, Opportunity
from cases.models import Case
from cases.serializer import CaseSerializer
from contacts.serializer import ContactSerializer
from leads.serializer import LeadSerializer
from teams.serializer import TeamsSerializer
from tasks.serializer import TaskSerializer
from opportunity.serializer import OpportunitySerializer
from invoices.serializer import InvoiceSerailizer
from emails.serializer import EmailSerailizer
from teams.models import Teams
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from drf_yasg.utils import swagger_auto_schema
class AccountsListView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
model = Account
def get_queryset(self):
queryset = self.model.objects.all()
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
queryset = queryset.filter(
Q(created_by=self.request.user) | Q(
assigned_to=self.request.user)
).distinct()
if self.request.GET.get("tag", None):
queryset = queryset.filter(
tags__in=self.request.GET.getlist("tag"))
request_post = self.request.POST
if request_post:
if request_post.get("name"):
queryset = queryset.filter(
name__icontains=request_post.get("name"))
if request_post.get("city"):
queryset = queryset.filter(
billing_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.getlist("tag"))
return queryset.filter(company=self.request.company).distinct()
def get_context_data(self, **kwargs):
context = {}
open_accounts = AccountSerializer(
self.get_queryset().filter(status="open"), many=True).data
close_accounts = AccountSerializer(
self.get_queryset().filter(status="close"), many=True).data
context["accounts_list"] = AccountSerializer(
self.get_queryset(), many=True).data
context["users"] = UserSerializer(User.objects.filter(
is_active=True).order_by("email"), many=True).data
context["open_accounts"] = open_accounts
context["close_accounts"] = close_accounts
context["industries"] = INDCHOICES
context["per_page"] = self.request.POST.get("per_page")
tag_ids = list(set(Account.objects.values_list("tags", flat=True)))
context["tags"] = TagsSerailizer(
Tags.objects.filter(id__in=tag_ids), many=True).data
if self.request.POST.get("tag", None):
context["request_tags"] = self.request.POST.getlist("tag")
elif self.request.GET.get("tag", None):
context["request_tags"] = self.request.GET.getlist("tag")
else:
context["request_tags"] = None
search = False
if (
self.request.POST.get("name")
or self.request.POST.get("city")
or self.request.POST.get("industry")
or self.request.POST.get("tag")
):
search = True
context["search"] = search
tab_status = "Open"
if self.request.POST.get("tab_status"):
tab_status = self.request.POST.get("tab_status")
context["tab_status"] = tab_status
TIMEZONE_CHOICES = [(tz, tz) for tz in pytz.common_timezones]
context["timezones"] = TIMEZONE_CHOICES
context["settings_timezone"] = settings.TIME_ZONE
return context
@swagger_auto_schema(tags=["accounts"], manual_parameters=swagger_params.account_list_get_params)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return Response(context)
@swagger_auto_schema(tags=["accounts"], manual_parameters=swagger_params.account_list_post_params)
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return Response(context)
class CreateAccountView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
model = Account
@swagger_auto_schema(tags=["accounts"], manual_parameters=swagger_params.account_create_post_params)
def post(self, request):
params = request.query_params if len(
request.data) == 0 else request.data
context = {}
form = AccountForm(
params, request.FILES, request_obj=request, account=True, request_user=self.request.user
)
# Save Account
if form.is_valid():
account_object = form.save(commit=False)
account_object.created_by = self.request.user
account_object.company = self.request.company
account_object.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)
account_object.tags.add(tag)
if self.request.POST.getlist("contacts", []):
account_object.contacts.add(
*self.request.POST.getlist("contacts"))
if self.request.POST.getlist("assigned_to", []):
account_object.assigned_to.add(
*self.request.POST.getlist("assigned_to"))
if self.request.FILES.get("account_attachment"):
attachment = Attachments()
attachment.created_by = self.request.user
attachment.file_name = self.request.FILES.get(
"account_attachment").name
attachment.account = account_object
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.getlist("teams", []):
account_object.teams.add(*self.request.POST.getlist("teams"))
assigned_to_list = list(
account_object.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,
account_object.id,
domain=current_site.domain,
protocol=self.request.scheme,
)
return Response({'error': False})
context["errors"] = form.errors
return Response(context, status=status.HTTP_400_BAD_REQUEST)
def get_context_data(self, **kwargs):
context = {}
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
self.users = User.objects.filter(
is_active=True, company=self.request.company
).order_by("email")
elif self.request.user.google.all():
self.users = []
else:
self.users = User.objects.filter(
role="ADMIN", company=self.request.company
).order_by("email")
context["users"] = UserSerializer(self.users, many=True).data
context["industries"] = INDCHOICES
context["countries"] = COUNTRIES
contacts = Contact.objects.filter(company=self.request.company)
leads = Lead.objects.exclude(
status__in=["converted", "closed"], company=self.request.company)
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
context["leads"] = LeadSerializer(leads, many=True).data
context["contacts"] = ContactSerializer(contacts, many=True).data
else:
leads = filter(
Q(assigned_to__in=[self.request.user])
| Q(created_by=self.request.user))
context["leads"] = LeadSerializer(leads, many=True).data
context["lead_count"] = leads.count()
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
context["lead_count"] = (
Lead.objects.filter(
Q(assigned_to__in=[self.request.user])
| Q(created_by=self.request.user)
)
.filter(company=self.request.company,)
.exclude(status="closed")
.count()
)
contacts = contacts.filter(
Q(assigned_to__in=[self.request.user]) | Q(
created_by=self.request.user)
)
context["contacts"] = ContactSerializer(contacts, many=True).data
context["contact_count"] = contacts.count()
context["teams"] = TeamsSerializer(Teams.objects.filter(
company=self.request.company,), many=True).data
return context
@swagger_auto_schema(tags=["accounts"], manual_parameters=swagger_params.account_create_get_params)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return Response(context)
class AccountUpdateView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
model = Account
def dispatch(self, request, *args, **kwargs):
self.users = User.objects.filter(is_active=True).order_by("email")
self.account_obj = Account.objects.filter(id=kwargs.get('pk')).first()
if self.account_obj.company != request.company:
raise PermissionDenied
return super(AccountUpdateView, self).dispatch(request, *args, **kwargs)
@swagger_auto_schema(tags=["accounts"], manual_parameters=swagger_params.account_update_post_params)
def post(self, request, *args, **kwargs):
params = request.query_params if len(
request.data) == 0 else request.data
context = {}
form = AccountForm(params, request.FILES, instance=self.account_obj, request_obj=request, account=True, request_user=self.request.user
)
if form.is_valid():
account_object = form.save(commit=False)
account_object.save()
previous_assigned_to_users = list(
account_object.assigned_to.all().values_list("id", flat=True)
)
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)
if self.request.POST.getlist("contacts", []):
account_object.contacts.clear()
account_object.contacts.add(
*self.request.POST.getlist("contacts"))
if self.request.POST.getlist("assigned_to", []):
account_object.assigned_to.clear()
account_object.assigned_to.add(
*self.request.POST.getlist("assigned_to"))
else:
account_object.assigned_to.clear()
if self.request.FILES.get("account_attachment"):
attachment = Attachments()
attachment.created_by = self.request.user
attachment.file_name = self.request.FILES.get(
"account_attachment").name
attachment.account = account_object
attachment.attachment = self.request.FILES.get(
"account_attachment")
attachment.save()
if self.request.POST.getlist("teams", []):
account_object.teams.clear()
account_object.teams.add(*self.request.POST.getlist("teams"))
else:
account_object.teams.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 = 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)
assigned_to_list = list(
account_object.assigned_to.all().values_list("id", flat=True)
)
current_site = get_current_site(self.request)
recipients = list(set(assigned_to_list) -
set(previous_assigned_to_users))
send_email_to_assigned_user.delay(
recipients,
account_object.id,
domain=current_site.domain,
protocol=self.request.scheme,
)
return Response({'error': False})
context['errors'] = form.errors
return Response(context, status=status.HTTP_400_BAD_REQUEST)
def get_context_data(self, **kwargs):
context = {}
self.users = User.objects.filter(is_active=True).order_by("email")
context["account_obj"] = AccountSerializer(self.account_obj).data
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if (self.request.user != context["account_obj"].created_by) and (
self.request.user not in context[
"account_obj"].assigned_to.all()
):
raise PermissionDenied
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
self.users = self.users.filter(
Q(role="ADMIN") | Q(id__in=[self.request.user.id, ])
)
context["users"] = UserSerializer(self.users, many=True).data
context["industries"] = INDCHOICES
context["countries"] = COUNTRIES
context["contact_count"] = Contact.objects.count()
context["edit_view"] = True
leads = Lead.objects.exclude(
status__in=["converted", "closed"])
if self.request.user.role == "ADMIN":
context["leads"] = LeadSerializer(leads, many=True).data
else:
leads = leads.filter(Q(assigned_to__in=[self.request.user]) | Q(
created_by=self.request.user))
context["leads"] = LeadSerializer(leads, many=True).data
context["lead_count"] = leads.count()
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
context["lead_count"] = (
Lead.objects.filter(
Q(assigned_to__in=[self.request.user])
| Q(created_by=self.request.user)
)
.exclude(status="closed")
.count()
)
context["teams"] = TeamsSerializer(Teams.objects.all(), many=True).data
return context
@swagger_auto_schema(tags=["accounts"], manual_parameters=swagger_params.account_update_get_params)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return Response(context)
class AccountDeleteView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
model = Account
def get_object(self):
return Account.objects.filter(id=self.kwargs.get('pk')).first()
@swagger_auto_schema(tags=["accounts"], manual_parameters=swagger_params.account_delete_params)
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.company != request.company:
raise PermissionDenied
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if self.request.user != self.object.created_by:
raise PermissionDenied
self.object.delete()
return Response({'status': 'success'}, status=status.HTTP_204_NO_CONTENT)
class AccountDetailView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
model = Account
def get_object(self):
return Account.objects.filter(id=self.kwargs.get('pk')).first()
def dispatch(self, request, *args, **kwargs):
self.account = self.get_object()
if self.account.company != request.company:
raise PermissionDenied
return super(AccountDetailView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {}
context["account_obj"] = AccountSerializer(self.account).data
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if not (
(self.request.user == self.account.created_by)
or (self.request.user in self.account.assigned_to.all())
):
raise PermissionDenied
comment_permission = (
True
if (
self.request.user == self.account.created_by
or self.request.user.is_superuser
or self.request.user.role == "ADMIN"
)
else False
)
if self.request.user.is_superuser or self.request.user.role == "ADMIN":
users_mention = list(
User.objects.filter(
is_active=True, company=self.request.company,
).values("username")
)
elif self.request.user != self.account.created_by:
if self.account.created_by:
users_mention = [{"username": self.account.created_by.username}]
else:
users_mention = []
else:
users_mention = []
context.update(
{
"comments": CommentSerializer(self.account.accounts_comments.all(), many=True).data,
"attachments": AttachmentsSerializer(self.account.account_attachment.all(), many=True).data,
"opportunity_list": OpportunitySerializer(Opportunity.objects.filter(account=self.account), many=True).data,
"contacts": ContactSerializer(self.account.contacts.all(), many=True).data,
"users": UserSerializer(User.objects.filter(
is_active=True, company=self.request.company,
).order_by("email"), many=True).data,
"cases": CaseSerializer(Case.objects.filter(account=self.account), many=True).data,
"stages": STAGES,
"sources": SOURCES,
"countries": COUNTRIES,
"currencies": CURRENCY_CODES,
"case_types": CASE_TYPE,
"case_priority": PRIORITY_CHOICE,
"case_status": STATUS_CHOICE,
"comment_permission": comment_permission,
"tasks": TaskSerializer(self.account.accounts_tasks.all(), many=True).data,
"invoices": InvoiceSerailizer(self.account.accounts_invoices.all(), many=True).data,
"emails": EmailSerailizer(self.account.sent_email.all(), many=True).data,
"users_mention": users_mention,
}
)
return context
@swagger_auto_schema(tags=["accounts"], manual_parameters=swagger_params.account_list_get_params)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return Response(context)

View file

@ -84,6 +84,24 @@ class AccountForm(forms.ModelForm):
if self.instance.id:
self.fields["lead"].required = False
self.fields["lead"].required = False
self.company = request_obj.company
def clean_name(self):
name = self.cleaned_data.get("name")
if self.instance.id:
if self.instance.name != name:
if not Account.objects.filter(
name__iexact=self.cleaned_data.get("name"),
company=self.company
).exists():
return self.cleaned_data.get("name")
raise forms.ValidationError(
"Account already exists with this name")
return self.cleaned_data.get("name")
if not Account.objects.filter(name__iexact=self.cleaned_data.get("name"), company=self.company).exists():
return self.cleaned_data.get("name")
raise forms.ValidationError("Account already exists with this name")
class Meta:
model = Account

85
accounts/serializer.py Normal file
View file

@ -0,0 +1,85 @@
from rest_framework import serializers
from accounts.models import Account, Email, Tags
from common.serializer import UserSerializer, CompanySerializer
from leads.serializer import LeadSerializer
from teams.serializer import TeamsSerializer
from contacts.serializer import ContactSerializer
class TagsSerailizer(serializers.ModelSerializer):
class Meta:
model = Tags
fields = (
"name",
"slug"
)
class AccountSerializer(serializers.ModelSerializer):
created_by = UserSerializer()
lead = LeadSerializer()
company = CompanySerializer()
tags = TagsSerailizer(read_only=True, many=True)
assigned_to = UserSerializer(read_only=True, many=True)
contacts = ContactSerializer(read_only=True, many=True)
teams = TeamsSerializer(read_only=True, many=True)
class Meta:
model = Account
# fields = ‘__all__’
fields = (
'id',
"name",
"email",
"phone",
"industry",
"billing_address_line",
"billing_street",
"billing_city",
"billing_state",
"billing_postcode",
"billing_country",
"website",
"description",
"created_by",
"created_on",
"is_active",
"tags",
"status",
"lead",
"contact_name",
"contacts",
"assigned_to",
"teams",
"company"
)
class EmailSerializer(serializers.ModelSerializer):
class Meta:
model = Email
fields = (
"from_account"
"recipients"
"message_subject"
"message_body"
"timezone"
"scheduled_date_time"
"scheduled_later"
"created_on"
"from_email"
"rendered_message_body"
)
class EmailLogSerializer(serializers.ModelSerializer):
email = EmailSerializer()
class Meta:
model = Email
fields = (
"email"
"contact"
"is_sent"
)

View file

@ -0,0 +1,66 @@
from drf_yasg import openapi
company_params_in_header = openapi.Parameter(
'company', openapi.IN_HEADER, required=True, type=openapi.TYPE_STRING)
account_list_get_params = [company_params_in_header]
account_list_post_params = [company_params_in_header]
account_create_get_params = [company_params_in_header]
account_create_post_params = [
company_params_in_header,
openapi.Parameter('name', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('phone', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('email', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_address_line', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_street', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_city', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_state', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_postcode', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_country', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('contacts', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
]
account_update_get_params = [company_params_in_header]
account_update_post_params = [
company_params_in_header,
openapi.Parameter('name', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('phone', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('email', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_address_line', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_street', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_city', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_state', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_postcode', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('billing_country', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('contacts', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
]
account_delete_params = [
company_params_in_header,
]

View file

@ -358,6 +358,7 @@
$('#submit_btn').removeAttr('disabled')
}
else {
alert(data.message)
window.location = data.success_url;
}
}

View file

@ -236,7 +236,8 @@ class CreateAccountView(SalesAccessRequiredMixin, LoginRequiredMixin, CreateView
return redirect("accounts:new_account")
if self.request.is_ajax():
data = {"success_url": reverse_lazy("accounts:list"), "error": False}
data = {"success_url": reverse_lazy("accounts:list"), "error": False,
"message": "Account created successfully"}
return JsonResponse(data)
return redirect("accounts:list")
@ -366,6 +367,7 @@ class AccountUpdateView(SalesAccessRequiredMixin, LoginRequiredMixin, UpdateView
def dispatch(self, request, *args, **kwargs):
self.users = User.objects.filter(is_active=True).order_by("email")
lead_obj = self.get_object()
self.account_status = lead_obj.status
if lead_obj.company != request.company:
raise PermissionDenied
# if self.request.user.role == 'ADMIN' or self.request.user.is_superuser:
@ -453,9 +455,15 @@ class AccountUpdateView(SalesAccessRequiredMixin, LoginRequiredMixin, UpdateView
domain=current_site.domain,
protocol=self.request.scheme,
)
message = "Account updated successfully"
if self.account_status != account_object.status:
if account_object.status == "open":
message = "Account opened successfully"
else:
message = "Account closed successfully"
if self.request.is_ajax():
data = {"success_url": reverse_lazy("accounts:list"), "error": False}
data = {"success_url": reverse_lazy("accounts:list"), "error": False,
"message": message}
return JsonResponse(data)
return redirect("accounts:list")

43
cases/serializer.py Normal file
View file

@ -0,0 +1,43 @@
from rest_framework import serializers
from cases.models import Case
from common.serializer import UserSerializer, CompanySerializer
from teams.serializer import TeamsSerializer
from accounts.serializer import AccountSerializer
from contacts.serializer import ContactSerializer
class CaseSerializer(serializers.ModelSerializer):
account = AccountSerializer(read_only=True, many=True)
contacts = ContactSerializer(read_only=True, many=True)
assigned_to = UserSerializer(read_only=True, many=True)
created_by = UserSerializer(read_only=True)
teams = TeamsSerializer(read_only=True, many=True)
company = CompanySerializer()
get_team_users = UserSerializer(read_only=True, many=True)
get_team_and_assigned_users = UserSerializer(read_only=True, many=True)
get_assigned_users_not_in_teams = UserSerializer(read_only=True, many=True)
class Meta:
model = Case
fields = (
'id',
'name',
'status',
'priority',
'case_type',
'account',
'contacts',
'closed_on',
'description',
'assigned_to',
'created_by',
'created_on',
'is_active',
'teams',
'company',
'get_team_users',
'get_team_and_assigned_users',
"get_assigned_users_not_in_teams",
"created_on_arrow",
)

24
common/api_urls.py Normal file
View file

@ -0,0 +1,24 @@
from django.urls import path
from common import api_views
app_name = "api_common"
urlpatterns = [
path("dashboard/", api_views.ApiHomeView.as_view()),
path("registration/", api_views.RegistrationView.as_view()),
path("login/", api_views.LoginView.as_view()),
path("validate-subdomain/", api_views.check_sub_domain),
path("profile/", api_views.ProfileView.as_view()),
path("get_teams_and_users/", api_views.GetTeamsAndUsersView.as_view()),
path("change-password/", api_views.ChangePasswordView.as_view(), name="change_password"),
path("forgot-password/", api_views.ForgotPasswordView.as_view()),
path("reset-password/", api_views.ResetPasswordView.as_view(), name='reset_password'),
path("users/list/", api_views.UsersListView.as_view(), name="users_list"),
path("users/<int:pk>/view/", api_views.UserDetailView.as_view(), name="view_user"),
# path("users/create/", api_views.CreateUserView.as_view(), name="create_user"),
path("documents/create/", api_views.DocumentCreate.as_view(), name="create_doc"),
#To be checked
path("users/<int:pk>/delete/", api_views.UserDeleteView.as_view(), name="remove_user"),
]

564
common/api_views.py Normal file
View file

@ -0,0 +1,564 @@
from django.conf import settings
from drf_yasg.utils import swagger_auto_schema
from django.shortcuts import get_object_or_404
from rest_framework import status
from django.http import Http404
from accounts.serializer import AccountSerializer
from contacts.serializer import ContactSerializer
from opportunity.serializer import OpportunitySerializer
from leads.serializer import LeadSerializer
from teams.serializer import TeamsSerializer
from common.serializer import *
from cases.serializer import CaseSerializer
from accounts.models import (Account, Contact)
from opportunity.models import Opportunity
from cases.models import Case
from leads.models import Lead
from teams.models import Teams
from common.forms import DocumentForm
from common.utils import ROLES
from common.models import User, Company
from common.access_decorators_mixins import (
MarketingAccessRequiredMixin,
SalesAccessRequiredMixin,
admin_login_required,
marketing_access_required,
sales_access_required,
)
from common.tasks import (
resend_activation_link_to_user,
send_email_to_new_user,
send_email_user_delete,
send_email_user_status,
send_email_to_reset_password
)
from django.utils.translation import gettext as _
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.serializers import jwt_encode_handler
from common.utils import jwt_payload_handler
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAuthenticated
from common.custom_auth import JSONWebTokenAuthentication
from common import swagger_params
from django.db.models import Q
from rest_framework.decorators import api_view
class UserDetailView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
@swagger_auto_schema(tags=["users"], manual_parameters=swagger_params.dashboard_params)
def get(self, request, *args, **kwargs):
company = request.headers['company']
user_id = kwargs['pk']
user_obj = get_object_or_404(
User, pk=user_id, company__sub_domain=company)
users_data = []
for each in User.objects.all():
assigned_dict = {}
assigned_dict["id"] = each.id
assigned_dict["name"] = each.username
users_data.append(assigned_dict)
context = {}
context["user_obj"] = UserSerializer(user_obj).data
opportunity_list = Opportunity.objects.filter(assigned_to=user_obj.id)
context["opportunity_list"] = OpportunitySerializer(
opportunity_list, many=True).data
contacts = Contact.objects.filter(assigned_to=user_obj.id)
context["contacts"] = ContactSerializer(contacts, many=True).data
cases = Case.objects.filter(assigned_to=user_obj.id)
context["cases"] = CaseSerializer(cases, many=True).data
# "accounts": Account.objects.filter(assigned_to=user_obj.id)
# context["assigned_data"] = json.dumps(users_data)
context["assigned_data"] = users_data
comments = user_obj.user_comments.all()
context["comments"] = CommentSerializer(comments, many=True).data
return Response(context)
class GetTeamsAndUsersView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
@swagger_auto_schema(tags=["users"], manual_parameters=swagger_params.dashboard_params)
def get(self, request, *args, **kwargs):
data = {}
teams = Teams.objects.filter(company=request.company)
# teams_data = [
# {"team": team.id, "users": [user.id for user in team.users.all()]}
# for team in teams
# ]
teams_data = TeamsSerializer(teams, many=True).data
users = User.objects.filter(company=request.company)
users_data = UserSerializer(users, many=True).data
users = User.objects.all().values_list("id", flat=True)
data["teams"] = teams_data
data["users_data"] = users_data
data["users"] = list(users)
return Response(data)
# to be checked
class UserDeleteView(APIView):
model = User
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
@swagger_auto_schema(tags=["users"], manual_parameters=swagger_params.dashboard_params)
def get(self, request, *args, **kwargs):
self.object = self.get_object()
current_site = request.get_host()
deleted_by = self.request.user.email
send_email_user_delete.delay(
self.object.email,
deleted_by=deleted_by,
domain=current_site,
protocol=request.scheme,
)
self.object.delete()
return Response({'status': 'success'}, status=status.HTTP_204_NO_CONTENT)
class ChangePasswordView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
@swagger_auto_schema(tags=["profile"], operation_description="This is change password api",
manual_parameters=swagger_params.change_password_params)
def post(self, request, format=None):
params = request.query_params if len(
request.data) == 0 else request.data
company = request.headers['company']
old_password = params.get("old_password", None)
new_password = params.get("new_password", None)
retype_password = params.get("retype_password", None)
company_obj = Company.objects.filter(sub_domain=company).first()
errors = {}
if request.company.id is not company_obj.id:
errors["company"] = "Company header doesnot match."
if old_password:
if not request.user.check_password(old_password):
errors["old_password"] = "old password entered is incorrect."
if new_password:
if len(new_password) < 8:
errors['new_password'] = "Password must be at least 8 characters long!"
if new_password == old_password:
errors[
'new_password'] = "New password and old password should not be same"
if retype_password:
if new_password != retype_password:
errors[
'retype_password'] = "new_password and retype_password did not match."
if errors:
return Response({'status': 'failure', 'errors': errors}, status=400)
user = request.user
user.set_password(new_password)
user.save()
return Response({'status': 'success'}, status=200)
# check_header not working
class ApiHomeView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
@swagger_auto_schema(tags=["dashboard"], manual_parameters=swagger_params.dashboard_params)
def get(self, request, format=None):
accounts = Account.objects.filter(status="open")
contacts = Contact.objects.all()
leads = Lead.objects.exclude(
status="converted").exclude(status="closed")
opportunities = Opportunity.objects.all()
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
pass
else:
accounts = accounts.filter(Q(assigned_to__id__in=[self.request.user.id]) | Q(
created_by=self.request.user.id))
contacts = contacts.filter(Q(assigned_to__id__in=[self.request.user.id]) | Q(
created_by=self.request.user.id))
leads = leads.filter(Q(assigned_to__id__in=[self.request.user.id]) | Q(
created_by=self.request.user.id)).exclude(status="closed")
opportunities = opportunities.filter(
Q(assigned_to__id__in=[self.request.user.id]) | Q(created_by=self.request.user.id))
context = {}
context["leads_count"] = leads.count()
context["accounts_count"] = accounts.count()
context["contacts_count"] = contacts.count()
context["opportunities_count"] = opportunities.count()
accounts = AccountSerializer(accounts, many=True).data
opportunities = OpportunitySerializer(opportunities, many=True).data
context["accounts"] = accounts
context["opportunities"] = opportunities
return Response(context)
class LoginView(APIView):
@swagger_auto_schema(tags=["common"], operation_description="This is login api",
manual_parameters=swagger_params.login_page_params)
def post(self, request, format=None):
params = request.query_params if len(
request.data) == 0 else request.data
company = request.headers['company']
username = params.get("email", None)
password = params.get("password", None)
company_obj = Company.objects.filter(sub_domain=company).first()
if not company_obj:
company_field = "doesnot exit"
msg = _('company with this subdomin {company_field}')
msg = msg.format(company_field=company_field)
return Response({'status': 'failure', 'message': msg}, status=400)
# raise APIException(msg)
if not username:
username_field = "User Name/Email"
msg = _('Must include "{username_field}"')
msg = msg.format(username_field=username_field)
return Response({'status': 'failure', 'message': msg}, status=400)
# raise APIException(msg)
user = User.objects.filter(email=username, company=company_obj).first()
if not user:
return Response({'status': 'failure', 'message': 'user not avaliable in our records'}, status=400)
if user.check_password(password):
payload = jwt_payload_handler(user)
response_data = {
'token': jwt_encode_handler(payload),
'status': 'success',
'company': user.company.id,
'subdomin': company_obj.sub_domain
}
return Response(response_data)
else:
password_field = "doesnot match"
msg = _('Email and password {password_field}')
msg = msg.format(password_field=password_field)
return Response({'status': 'failure', 'message': msg}, status=400)
class RegistrationView(APIView):
@swagger_auto_schema(tags=["common"], operation_description="This is registration api",
manual_parameters=swagger_params.registration_page_params)
def post(self, request, format=None):
params = request.query_params if len(
request.data) == 0 else request.data
sub_domain = params.get("sub_domain", None)
username = params.get("username", None)
email = params.get("email", None)
password = params.get("password", None)
errors = {}
if sub_domain:
if Company.objects.filter(sub_domain__iexact=sub_domain):
errors["sub_domain"] = "company with this subdomin already exit"
if username:
if User.objects.filter(username__iexact=username):
errors['username'] = "User name already exit."
if email:
if User.objects.filter(email__iexact=email):
errors['email'] = "Email already exit."
if password:
if len(password) < 8:
errors['password'] = "Password must be at least 8 characters long!"
if errors:
return Response({'status': 'failure', 'errors': errors}, status=400)
else:
user = User.objects.create(username=username, email=email)
company = Company.objects.create(sub_domain=sub_domain)
company.save()
user.company = company
user.role = "ADMIN"
user.is_superuser = False
user.has_marketing_access = True
user.has_sales_access = True
user.is_admin = True
user.set_password(password)
user.save()
return Response({'status': 'success'}, status=201)
@swagger_auto_schema(method='post', tags=["common"], manual_parameters=swagger_params.check_sub_domain_params)
@api_view(['POST'])
def check_sub_domain(request):
if request.method == "POST":
params = request.query_params if len(
request.data) == 0 else request.data
sub_domain = params.get("sub_domain", None)
msg = _('Given sub_domain {domain_name} is {validity_status}')
company = Company.objects.filter(sub_domain=sub_domain).first()
if company:
request.session["company"] = company.id
msg = msg.format(domain_name=sub_domain, validity_status="Valid")
status_code = 200
status_msg = "success"
else:
msg = msg.format(domain_name=sub_domain, validity_status="Invalid")
status_code = 400
status_msg = "failure"
return Response({'status': status_msg, 'message': msg}, status=status_code)
class ProfileView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
@swagger_auto_schema(tags=["profile"], manual_parameters=swagger_params.dashboard_params)
def get(self, request, format=None):
context = {}
context["user_obj"] = UserSerializer(request.user).data
return Response(context)
class UsersListView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
@swagger_auto_schema(tags=["users"], manual_parameters=swagger_params.user_list_params)
def post(self, request, format=None):
context = {}
queryset = User.objects.filter(company=self.request.company)
params = request.query_params if len(
request.data) == 0 else request.data
if params:
if params.get("username"):
queryset = queryset.filter(
username__icontains=params.get("username")
)
if params.get("email"):
queryset = queryset.filter(
email__icontains=params.get("email"))
if params.get("role"):
queryset = queryset.filter(role=params.get("role"))
if params.get("status"):
queryset = queryset.filter(is_active=params.get("status"))
active_users = queryset.filter(is_active=True)
inactive_users = queryset.filter(is_active=False)
context["active_users"] = UserSerializer(active_users, many=True).data
context["inactive_users"] = UserSerializer(
inactive_users, many=True).data
context["per_page"] = self.request.POST.get("per_page")
context["admin_email"] = settings.ADMIN_EMAIL
context["roles"] = ROLES
context["status"] = [("True", "Active"), ("False", "In Active")]
return Response(context)
@swagger_auto_schema(tags=["users"], manual_parameters=swagger_params.dashboard_params)
def get(self, request, format=None):
queryset = User.objects.filter(company=self.request.company)
context = {}
active_users = queryset.filter(is_active=True)
inactive_users = queryset.filter(is_active=False)
context["active_users"] = UserSerializer(active_users, many=True).data
context["inactive_users"] = UserSerializer(
inactive_users, many=True).data
context["per_page"] = self.request.POST.get("per_page")
context["admin_email"] = settings.ADMIN_EMAIL
context["roles"] = ROLES
context["status"] = [("True", "Active"), ("False", "In Active")]
return Response(context)
class DocumentCreate(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
@swagger_auto_schema(tags=["documents"], manual_parameters=swagger_params.dashboard_params)
def get(self, request, format=None):
users = []
if request.user.role == "ADMIN" or request.user.is_superuser:
users = User.objects.filter(is_active=True, company=request.company).order_by(
"email"
)
else:
users = User.objects.filter(role="ADMIN", company=request.company).order_by(
"email"
)
context = {}
context["users"] = UserSerializer(users, many=True).data
teams = Teams.objects.filter(company=request.company)
context["teams"] = TeamsSerializer(teams, many=True).data
context['status'] = (("active", "active"), ("inactive", "inactive"))
return Response(context)
# to be checked file upload
@swagger_auto_schema(tags=["documents"], manual_parameters=swagger_params.document_create_params)
def post(self, request, format=None):
params = request.query_params if len(
request.data) == 0 else request.data
context = {}
users = []
if request.user.role == "ADMIN" or request.user.is_superuser:
users = User.objects.filter(is_active=True, company=request.company).order_by(
"email"
)
else:
users = User.objects.filter(role="ADMIN", company=request.company).order_by(
"email"
)
form = DocumentForm(
params, request.FILES, users=users, request_obj=request
)
if form.is_valid():
doc = form.save(commit=False)
doc.created_by = request.user
doc.company = request.company
doc.save()
if params.getlist("shared_to"):
doc.shared_to.add(*params.getlist("shared_to"))
if params.getlist("teams", []):
user_ids = Teams.objects.filter(
id__in=params.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)
if params.getlist("teams", []):
doc.teams.add(*params.getlist("teams"))
data = {"error": False}
return Response(data)
return Response({"error": True, "errors": form.errors})
context["errors"] = form.errors
return Response(context)
# from common.forms import UserForm
# from common.utils import ROLES
# class CreateUserView(APIView):
# # model = User
# # form_class = UserForm
# # template_name = "create.html"
# # success_url = '/users/list/'
# authentication_classes = (JSONWebTokenAuthentication,)
# permission_classes = (IsAuthenticated,)
# # def form_valid(self, form):
# # user = form.save(commit=False)
# # if form.cleaned_data.get("password"):
# # user.set_password(form.cleaned_data.get("password"))
# # user.company = self.request.company
# # user.save()
# # if self.request.POST.getlist("teams"):
# # for team in self.request.POST.getlist("teams"):
# # Teams.objects.filter(id=team).first().users.add(user)
# # current_site = self.request.get_host()
# # protocol = self.request.scheme
# # send_email_to_new_user.delay(
# # user.email, self.request.user.email, domain=current_site, protocol=protocol
# # )
# # if self.request.is_ajax():
# # data = {"success_url": reverse_lazy("common:users_list"), "error": False}
# # return Response(data)
# # return super(CreateUserView, self).form_valid(form)
# # def form_invalid(self, form):
# # response = super(CreateUserView, self).form_invalid(form)
# # if self.request.is_ajax():
# # return Response({"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"]
# # context["teams"] = Teams.objects.all()
# # if "errors" in kwargs:
# # context["errors"] = kwargs["errors"]
# # return context
# @swagger_auto_schema(tags=["user create"], manual_parameters=swagger_params.dashboard_params)
# def get(self, request, format=None):
# context = {}
# context['role'] = ROLES
# teams = Teams.objects.filter(company=request.company)
# context["teams"] = TeamsSerializer(teams, many=True).data
# return Response(context)
# @swagger_auto_schema(tags=["user create"], operation_description="Create user",
# manual_parameters=swagger_params.user_create_params)
# def post(self, request, format=None):
# params = request.query_params if len(request.data) == 0 else request.data
# form = UserForm(params, request_user=self.request)
# if form.is_valid():
# user = form.save(commit=False)
# if form.cleaned_data.get("password"):
# user.set_password(form.cleaned_data.get("password"))
# user.company = self.request.company
# user.save()
# if self.request.POST.getlist("teams"):
# for team in self.request.POST.getlist("teams"):
# Teams.objects.filter(id=team).first().users.add(user)
# data = { "error": False}
# return Response(data)
# # if self.request.is_ajax():
# return Response({"error": True, "errors": form.errors})
class ForgotPasswordView(APIView):
@swagger_auto_schema(tags=["common"], manual_parameters=swagger_params.forgot_password_params)
def post(self, request, format=None):
params = request.query_params if len(
request.data) == 0 else request.data
serializer = ForgotPasswordSerializer(data=params)
if serializer.is_valid():
user = get_object_or_404(User, email=params.get('email'))
current_site = self.request.get_host()
protocol = self.request.scheme
send_email_to_reset_password.delay(
user.email, protocol=protocol, domain=current_site)
data = {"error": False,
"message": "We have sent you an email. please reset password"}
return Response(data)
else:
data = {"error": True, "errors": serializer.errors}
response_status = status.HTTP_400_BAD_REQUEST
return Response(data, status=response_status)
class ResetPasswordView(APIView):
@swagger_auto_schema(tags=["common"], manual_parameters=swagger_params.reset_password_params)
def post(self, request, format=None):
params = request.query_params if len(
request.data) == 0 else request.data
serializer = ResetPasswordSerailizer(data=params)
if serializer.is_valid():
password = params.get('new_password1')
user = serializer.user
user.set_password(password)
user.save()
data = {"error": False,
"message": "Password Updated Successfully. Please login"}
else:
data = {"error": True, "errors": serializer.errors}
return Response(data, status=status.HTTP_400_BAD_REQUEST)

119
common/custom_auth.py Normal file
View file

@ -0,0 +1,119 @@
import jwt
import base64
# from .models import User
# from django.contrib.auth import get_user_model
from django.utils.encoding import smart_text
from django.utils.translation import ugettext as _
from rest_framework import exceptions
from rest_framework.authentication import (
BaseAuthentication
)
from rest_framework.authentication import get_authorization_header
from django.conf import settings
from rest_framework_jwt.settings import api_settings
try:
from threading import local
except ImportError:
from django.utils._threading_local import local
_thread_locals = local()
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
def set_current_instance_field(name, value):
setattr(_thread_locals, name, value)
class BaseJSONWebTokenAuthentication(BaseAuthentication):
"""
Token based authentication using the JSON Web Token standard.
"""
def authenticate(self, request):
"""
Returns a two-tuple of User and token if a valid signature has been
supplied using JWT-based authentication. Otherwise returns None.
"""
authorization = request.META.get('HTTP_AUTHORIZATION', None)
self.company = request.META.get('HTTP_COMPANY', None)
jwt_value = self.get_jwt_value(request)
if jwt_value is None:
return None
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
account = self.authenticate_credentials(payload)
set_current_instance_field('authorization', True)
return account, payload
def authenticate_credentials(self, payload):
"""
Returns an active user that matches the payload's user id and email.
"""
account_id = payload['id']
from common.models import User
if not account_id:
msg = _('Invalid payload.')
raise exceptions.AuthenticationFailed(msg)
try:
account = User.objects.get(pk=account_id)
if account.company.sub_domain != self.company:
raise exceptions.AuthenticationFailed("user company doesnot match with header....")
except User.DoesNotExist:
msg = _('Invalid signature.')
raise exceptions.AuthenticationFailed(msg)
return account
# return True
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
"""
Clients should authenticate by passing the token key in the "Authorization"
HTTP header, prepended with the string specified in the setting
`JWT_AUTH_HEADER_PREFIX`. For example:
Authorization: JWT eyJhbGciOiAiSFMyNTYiLCAidHlwIj
"""
www_authenticate_realm = 'api'
def get_jwt_value(self, request):
auth = get_authorization_header(request).split()
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
if not auth:
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None
if smart_text(auth[0].lower()) != auth_header_prefix:
return None
if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return auth[1]
def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
return '{0} realm="{1}"'.format(api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm)

View file

@ -10,15 +10,24 @@ class GetCompany(object):
return response
def process_view(self, request, view_func, view_args, view_kwargs):
company_id = request.session.get("company", None)
if company_id:
company = Company.objects.get(id=company_id)
request.company = company
request.session["company"] = company.id
else:
host_name = request.META.get("HTTP_HOST")
subdomain = host_name.split(".")[0]
if request.headers.get('company'):
subdomain = request.headers.get('company')
company = Company.objects.filter(sub_domain=subdomain).first()
if company:
request.company = company
else:
request.company = None
# request.session["company"] = company.id
else:
company_id = request.session.get("company", None)
if company_id:
company = Company.objects.get(id=company_id)
request.company = company
request.session["company"] = company.id
else:
host_name = request.META.get("HTTP_HOST")
subdomain = host_name.split(".")[0]
company = Company.objects.filter(sub_domain=subdomain).first()
if company:
request.company = company
request.session["company"] = company.id

View file

@ -85,6 +85,10 @@ class User(AbstractBaseUser, PermissionsMixin):
full_name = self.email
return full_name
@property
def created_on_arrow(self):
return arrow.get(self.date_joined).humanize()
def __str__(self):
return self.email

206
common/serializer.py Normal file
View file

@ -0,0 +1,206 @@
from rest_framework import serializers
from common.models import User, Company, Comment, Address, Attachments
from django.utils.http import urlsafe_base64_decode
from django.contrib.auth.tokens import default_token_generator
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = (
'id',
"name",
"address",
"sub_domain",
"user_limit",
"country"
)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
'id',
"file_prepend",
"username",
"first_name",
"last_name",
"email",
"is_active",
"is_admin",
"is_staff",
"date_joined",
"role",
"profile_pic",
"has_sales_access",
"has_marketing_access",
"company",
"get_app_name"
)
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = "__all__"
class CreateUserSerializer(serializers.ModelSerializer):
password = serializers.CharField()
class Meta:
model = User
fields = (
"email",
"first_name",
"last_name",
"username",
"role",
"profile_pic",
"has_sales_access",
"has_marketing_access",
)
def __init__(self, *args, **kwargs):
self.request_user = kwargs.pop("request_user", None)
super(CreateUserSerializer, self).__init__(*args, **kwargs)
self.fields["first_name"].required = True
if not self.instance.pk:
self.fields["password"].required = True
# self.fields['password'].required = True
def validate_password(self):
password = self.cleaned_data.get("password")
if password:
if len(password) < 4:
raise serializers.ValidationError(
"Password must be at least 4 characters long!"
)
return password
def validate_has_sales_access(self):
sales = self.cleaned_data.get("has_sales_access", False)
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 serializers.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 validate_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 validate_email(self):
email = self.cleaned_data.get("email")
if self.instance.id:
if self.instance.email != email:
if not User.objects.filter(
email=self.cleaned_data.get("email")
).exists():
return self.cleaned_data.get("email")
raise serializers.ValidationError("Email already exists")
else:
return self.cleaned_data.get("email")
else:
if not User.objects.filter(email=self.cleaned_data.get("email")).exists():
return self.cleaned_data.get("email")
raise serializers.ValidationError(
"User already exists with this email")
class ForgotPasswordSerializer(serializers.Serializer):
email = serializers.CharField(max_length=200)
def validate(self, data):
email = data.get('email')
user = User.objects.filter(email__iexact=email).last()
if not user:
raise serializers.ValidationError(
"You don't have an account. Please create one."
)
return data
class CheckTokenSerializer(serializers.Serializer):
uidb64_regex = r'[0-9A-Za-z_\-]+'
token_regex = r'[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20}'
uidb64 = serializers.RegexField(uidb64_regex)
token = serializers.RegexField(token_regex)
error_message = {'__all__': ('Invalid password reset token')}
def get_user(self, uidb64):
try:
uid = urlsafe_base64_decode(uidb64).decode()
user = User._default_manager.get(pk=uid)
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
return user
class ResetPasswordSerailizer(CheckTokenSerializer):
new_password1 = serializers.CharField()
new_password2 = serializers.CharField()
def validate(self, data):
self.user = self.get_user(data.get('uidb64'))
if not self.user:
raise serializers.ValidationError(self.error_message)
is_valid_token = default_token_generator.check_token(
self.user, data.get('token'))
if not is_valid_token:
raise serializers.ValidationError(self.error_message)
new_password2 = data.get('new_password2')
new_password1 = data.get('new_password1')
if new_password1 != new_password2:
raise serializers.ValidationError(
"The two password fields didn't match.")
return new_password2
class AttachmentsSerializer(serializers.ModelSerializer):
class Meta:
model = Attachments
fields = ['created_by', 'file_name', 'created_on']
class BillingAddressSerializer(serializers.ModelSerializer):
class Meta:
model = Address
fields = ("address_line", "street", "city",
"state", "postcode", "country")
def __init__(self, *args, **kwargs):
account_view = kwargs.pop("account", False)
super(BillingAddressSerializer, self).__init__(*args, **kwargs)
if account_view:
self.fields["address_line"].required = True
self.fields["street"].required = True
self.fields["city"].required = True
self.fields["state"].required = True
self.fields["postcode"].required = True
self.fields["country"].required = True

84
common/swagger_params.py Normal file
View file

@ -0,0 +1,84 @@
from drf_yasg import openapi
company_params_in_header = openapi.Parameter(
'company', openapi.IN_HEADER, required=True, type=openapi.TYPE_STRING)
login_page_params = [
company_params_in_header,
openapi.Parameter('email', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
openapi.Parameter('password', openapi.IN_QUERY,
format='password', required=True, type=openapi.TYPE_STRING)
]
change_password_params = [
company_params_in_header,
openapi.Parameter('old_password', openapi.IN_QUERY,
format='password', required=True, type=openapi.TYPE_STRING),
openapi.Parameter('new_password', openapi.IN_QUERY,
format='password', required=True, type=openapi.TYPE_STRING),
openapi.Parameter('retype_password', openapi.IN_QUERY,
format='password', required=True, type=openapi.TYPE_STRING),
]
dashboard_params = [
company_params_in_header
]
user_delete_params = [
company_params_in_header,
openapi.Parameter('user_id', openapi.IN_QUERY, required=True, type=openapi.TYPE_NUMBER),
]
check_sub_domain_params = [
openapi.Parameter('sub_domain', openapi.IN_QUERY,
required=True, type=openapi.TYPE_STRING),
]
registration_page_params = [
openapi.Parameter('sub_domain', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
openapi.Parameter('username', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
openapi.Parameter('email', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
openapi.Parameter('password', openapi.IN_QUERY, format='password', required=True, type=openapi.TYPE_STRING)
]
forgot_password_params = [
openapi.Parameter('email', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
]
reset_password_params = [
openapi.Parameter('uidb64', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
openapi.Parameter('token', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
openapi.Parameter('new_password1', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
openapi.Parameter('new_password2', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
]
user_list_params = [
company_params_in_header,
openapi.Parameter('username', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('email', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('role', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('status', openapi.IN_QUERY, type=openapi.TYPE_STRING)
]
user_create_params = [
company_params_in_header,
openapi.Parameter('username', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('email', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('role', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('password', openapi.IN_QUERY, format='password', required=True, type=openapi.TYPE_STRING),
openapi.Parameter('first_name', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('last_name', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('profile_pic', openapi.IN_QUERY, type=openapi.TYPE_FILE),
openapi.Parameter('has_sales_access', openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN),
openapi.Parameter('has_marketing_access', openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN),
openapi.Parameter('status', openapi.IN_QUERY, type=openapi.TYPE_STRING)
]
document_create_params = [
company_params_in_header,
openapi.Parameter('title', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('status', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('shared_to', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('document_file', openapi.IN_QUERY, type=openapi.TYPE_FILE),
]

View file

@ -1,5 +1,5 @@
import datetime
from django.conf import settings
from celery.task import task
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.core.mail import EmailMessage
@ -12,6 +12,7 @@ from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from common.models import Comment, Profile, User
from common.token_generator import account_activation_token
from django.contrib.auth.tokens import default_token_generator
from marketing.models import BlockedDomain, BlockedEmail
@ -108,7 +109,8 @@ def send_email_user_mentions(
protocol
+ "://"
+ domain
+ reverse("opportunity:opp_view", args=(comment.opportunity.id,))
+ reverse("opportunity:opp_view",
args=(comment.opportunity.id,))
)
subject = "New comment on Opportunity. "
elif called_from == "cases":
@ -132,7 +134,8 @@ def send_email_user_mentions(
protocol
+ "://"
+ domain
+ reverse("invoices:invoice_details", args=(comment.invoice.id,))
+ reverse("invoices:invoice_details",
args=(comment.invoice.id,))
)
subject = "New comment on Invoice. "
elif called_from == "events":
@ -146,7 +149,8 @@ def send_email_user_mentions(
else:
context["url"] = ""
# subject = 'Django CRM : comment '
blocked_domains = BlockedDomain.objects.values_list("domain", flat=True)
blocked_domains = BlockedDomain.objects.values_list(
"domain", flat=True)
blocked_emails = BlockedEmail.objects.values_list("email", flat=True)
if recipients:
for recipient in recipients:
@ -220,7 +224,8 @@ def send_email_user_delete(
recipients = []
recipients.append(user_email)
subject = "CRM : Your account is Deleted. "
html_content = render_to_string("user_delete_email.html", context=context)
html_content = render_to_string(
"user_delete_email.html", context=context)
if recipients:
msg = EmailMessage(subject, html_content, to=recipients)
msg.content_subtype = "html"
@ -263,3 +268,27 @@ def resend_activation_link_to_user(
msg = EmailMessage(subject, html_content, to=recipients)
msg.content_subtype = "html"
msg.send()
@task
def send_email_to_reset_password(
user_email, domain="demo.django-crm.io", protocol="http"
):
""" Send Mail To Users When their account is created """
user = User.objects.filter(email=user_email).first()
context = {}
context["user_email"] = user_email
context["url"] = protocol + "://" + domain
context["uid"] = (urlsafe_base64_encode(force_bytes(user.pk)),)
context["token"] = default_token_generator.make_token(user)
context["token"] = context["token"]
context["complete_url"] = context["url"] + '/api-common/reset-password/{uidb64}/{token}/'.format(
uidb64=context["uid"], token=context["token"])
recipients = []
recipients.append(user_email)
subject = "Password Reset"
html_content = render_to_string("password_reset_email.html", context=context)
if recipients:
msg = EmailMessage(subject, html_content, to=recipients)
msg.content_subtype = "html"
msg.send()

View file

@ -152,7 +152,7 @@
{% for user in active_users %}
<tr class="text-center">
<td><a href="{% url 'common:view_user' user.id %}">{{ user.username }}</a></td>
<td data-toggle="tooltip" title="{{user.date_joined}}">{{user.date_joined.date|timesince}}
<td data-toggle="tooltip" title="{{user.date_joined}}">{{user.created_on_arrow}}
<td>{{ user.email }}</td>
<td class="user_role">{% if user.is_superuser %} ADMIN {% else %} {{user.role}} {% endif %}
</td>

View file

@ -0,0 +1,21 @@
{% extends 'root_email_template_new.html' %}
{% block heading %}
Hi {{email}}
{% endblock heading %}
{% ifequal 'activated' message %}
{% block content_body %}
You account has been activated by {{status_changed_user}}. You can login to your account and access all the features of
Django CRM.
{% endblock content_body %}
{% block button_link %}
{% if url %}
<div style="margin-bottom:20px">
<a href="{{password_reset_url}}"
style="display:inline-block;width:170px;background:#38abdd;padding:10px;text-align:center;color:#fff;font-size:1rem;font-weight:600;margin:0px auto;margin-bottom:20px;border-radius:5px;text-decoration:none;display:block">{{password_reset_url}}</a>
</div>
{% endif %}
{% endblock button_link %}
{% endifequal %}

View file

@ -1,5 +1,36 @@
import pytz
from datetime import datetime
from datetime import datetime, timedelta
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.forms.models import model_to_dict
from .custom_auth import BaseJSONWebTokenAuthentication
from rest_framework.authentication import get_authorization_header
def jwt_payload_handler(user):
""" Custom payload handler
Token encrypts the dictionary returned by this function, and can be
decoded by rest_framework_jwt.utils.jwt_decode_handler
"""
return {
'id': user.pk,
# 'name': user.name,
'email': user.email,
'company': user.company.id,
"role": user.role,
"has_sales_access": user.has_sales_access,
"has_marketing_access": user.has_marketing_access,
"file_prepend": user.file_prepend,
"username": user.username,
"first_name": user.first_name,
"last_name": user.last_name,
"is_active": user.is_active,
"is_admin": user.is_admin,
"is_staff": user.is_staff,
# "date_joined"
}
INDCHOICES = (
@ -593,4 +624,9 @@ def append_str_to(append_to: str, *args, sep=", ", **kwargs):
"""
append_to = append_to or ""
result_list = [append_to] + list(args) + list(kwargs.values())
return f"{sep}".join(filter(len, result_list))
data = False
for item in result_list:
if item:
data = True
break
return f"{sep}".join(filter(len, result_list)) if data else ""

View file

@ -95,13 +95,17 @@ def landing_page(request):
def check_sub_domain(request):
if request.method == "GET":
try:
if request.company:
return redirect("common:login")
except:
return render(request, "check_subdomain.html", {})
# try:
# if request.company:
# return redirect("common:login")
# except:
return render(request, "check_subdomain.html", {})
if request.method == "POST":
sub_domain = request.POST.get("sub_domain", "")
if sub_domain == "":
return render(
request, "check_subdomain.html", {"error": "Please mention a sub_domain"}
)
company = Company.objects.filter(sub_domain=sub_domain).first()
if company:
abs_url = (
@ -112,7 +116,7 @@ def check_sub_domain(request):
+ settings.DOMAIN_NAME
+ reverse("common:login")
)
request.session["company"] = company.id
# request.session["company"] = company.id
return HttpResponseRedirect(abs_url)
else:
return render(
@ -150,10 +154,11 @@ class HomeView(SalesAccessRequiredMixin, LoginRequiredMixin, TemplateView):
def get_context_data(self, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)
accounts = Account.objects.filter(status="open")
contacts = Contact.objects.all()
leads = Lead.objects.exclude(status="converted").exclude(status="closed")
opportunities = Opportunity.objects.all()
accounts = Account.objects.filter(status="open", company=self.request.company)
contacts = Contact.objects.filter(company=self.request.company)
leads = Lead.objects.filter(company=self.request.company).exclude(Q(status="converted") |
Q(status="closed"))
opportunities = Opportunity.objects.filter(company=self.request.company)
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
pass
else:
@ -313,6 +318,7 @@ class LogoutView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
logout(request)
request.session.flush()
request.company = None
url = request.scheme + "://" + settings.DOMAIN_NAME + "/"
return HttpResponseRedirect(url)
# return redirect("common:login")
@ -389,6 +395,15 @@ class CompanyLoginView(CreateView):
template_name = "company_login.html"
def post(self, request, *args, **kwargs):
# add company to request and header
host_name = request.META.get("HTTP_HOST")
subdomain = host_name.split(".")[0]
company = Company.objects.filter(sub_domain=subdomain).first()
if company:
request.company = company
request.session["company"] = company.id
else:
return render(request, "check_subdomain.html", {"error": "Please mention a sub_domain"})
form = CompanyLoginForm(request.POST, request=request)
if form.is_valid():
email = form.cleaned_data.get("email", "")
@ -507,7 +522,7 @@ class CreateUserView(AdminRequiredMixin, CreateView):
def get_context_data(self, **kwargs):
context = super(CreateUserView, self).get_context_data(**kwargs)
context["user_form"] = context["form"]
context["teams"] = Teams.objects.all()
context["teams"] = Teams.objects.filter(company=self.request.company)
if "errors" in kwargs:
context["errors"] = kwargs["errors"]
return context
@ -615,7 +630,7 @@ class UpdateUserView(LoginRequiredMixin, UpdateView):
user_profile_name = user_profile_name[-1]
context["user_profile_name"] = user_profile_name
context["user_form"] = context["form"]
context["teams"] = Teams.objects.all()
context["teams"] = Teams.objects.filter(company=self.request.company)
if "errors" in kwargs:
context["errors"] = kwargs["errors"]
return context

12
contacts/api_urls.py Normal file
View file

@ -0,0 +1,12 @@
from django.urls import path
from contacts import api_views
app_name = 'api_contacts'
urlpatterns = [
path("contacts-list/", api_views.ContactsListView.as_view()),
path("create/", api_views.CreateContactView.as_view(), name="add_contact"),
path("<int:pk>/view/", api_views.ContactDetailView.as_view(), name="view_contact"),
path("<int:pk>/edit/", api_views.UpdateContactView.as_view(), name="edit_contact"),
path("<int:pk>/delete/", api_views.RemoveContactView.as_view(), name="remove_contact"),
]

390
contacts/api_views.py Normal file
View file

@ -0,0 +1,390 @@
from common.models import User
from contacts.models import Contact
from teams.models import Teams
from django.db.models import Q
from django.shortcuts import get_object_or_404
from drf_yasg.utils import swagger_auto_schema
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from common.custom_auth import JSONWebTokenAuthentication
from contacts import swagger_params
from contacts.serializer import *
from rest_framework.views import APIView
from common.utils import COUNTRIES
from common.serializer import (UserSerializer, CommentSerializer,
AttachmentsSerializer, BillingAddressSerializer)
from teams.serializer import TeamsSerializer
from tasks.serializer import TaskSerializer
from django.contrib.sites.shortcuts import get_current_site
from contacts.tasks import send_email_to_assigned_user
class ContactsListView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
model = Contact
def get_queryset(self):
queryset = self.model.objects.filter(
company=self.request.company).order_by("-created_on")
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
queryset = queryset.filter(
Q(assigned_to__in=[self.request.user]) | Q(
created_by=self.request.user)
)
request_post = params
if request_post:
if request_post.get("first_name"):
queryset = queryset.filter(
first_name__icontains=request_post.get("first_name")
)
if request_post.get("city"):
queryset = queryset.filter(
address__city__icontains=request_post.get("city")
)
if request_post.get("phone"):
queryset = queryset.filter(
phone__icontains=request_post.get("phone"))
if request_post.get("email"):
queryset = queryset.filter(
email__icontains=request_post.get("email"))
if request_post.getlist("assigned_to"):
queryset = queryset.filter(
assigned_to__id__in=request_post.getlist("assigned_to")
)
return queryset.distinct()
def get_context_data(self, **kwargs):
context = {}
context["contact_obj_list"] = ContactSerializer(
self.get_queryset(), many=True).data
context["per_page"] = params.get("per_page")
context["users"] = UserSerializer(
User.objects.filter(is_active=True).order_by("username"), many=True).data
context["assignedto_list"] = [
int(i) for i in params.getlist("assigned_to", []) if i
]
search = False
if (
params.get("first_name")
or params.get("city")
or params.get("phone")
or params.get("email")
or params.get("assigned_to")
):
search = True
context["search"] = search
return context
@swagger_auto_schema(tags=["contacts"], manual_parameters=swagger_params.contact_list_get_params)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return Response(context)
@swagger_auto_schema(tags=["contacts"], manual_parameters=swagger_params.contact_list_post_params)
def post(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return Response(context)
class CreateContactView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
model = Contact
@swagger_auto_schema(tags=["contacts"], manual_parameters=swagger_params.contact_create_post_params)
def post(self, request, *args, **kwargs):
self.object = None
params = request.query_params if len(
request.data) == 0 else request.data
contact_serializer = CreateContctForm(data=params)
address_serializer = BillingAddressSerializer(data=params)
data = {}
if not contact_serializer.is_valid():
data['contact_errors'] = contact_serializer.errors
if not address_serializer.is_valid():
data["address_errors"] = address_serializer.errors,
if data:
data['error'] = True
return Response(data)
address_obj = address_serializer.save()
contact_obj = contact_serializer.save()
contact_obj.address = address_obj
contact_obj.created_by = self.request.user
contact_obj.company = self.request.company
contact_obj.save()
if self.request.GET.get("view_account", None):
if Account.objects.filter(
id=int(self.request.GET.get("view_account"))
).exists():
Account.objects.get(
id=int(self.request.GET.get("view_account"))
).contacts.add(contact_obj)
if params.getlist("assigned_to", []):
contact_obj.assigned_to.add(
*params.getlist("assigned_to"))
if params.getlist("teams", []):
user_ids = Teams.objects.filter(
id__in=params.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)
if params.getlist("teams", []):
contact_obj.teams.add(*params.getlist("teams"))
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 request.FILES.get("contact_attachment"):
attachment = Attachments()
attachment.created_by = request.user
attachment.file_name = request.FILES.get(
"contact_attachment").name
attachment.contact = contact_obj
attachment.attachment = request.FILES.get("contact_attachment")
attachment.save()
return Response({'error': False})
def get_context_data(self, **kwargs):
params = self.request.query_params if len(
self.request.data) == 0 else request.data
context = {}
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
self.users = User.objects.filter(
is_active=True, company=self.request.company).order_by("email")
else:
self.users = User.objects.filter(
role="ADMIN", company=self.request.company).order_by("email")
context["users"] = UserSerializer(self.users, many=True).data
context["countries"] = COUNTRIES
context["assignedto_list"] = [
int(i) for i in params.getlist("assigned_to", []) if i
]
context["teams"] = TeamsSerializer(Teams.objects.filter(
company=self.request.company), many=True).data
return context
@swagger_auto_schema(tags=["contacts"], manual_parameters=swagger_params.contact_list_get_params)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return Response(context)
class ContactDetailView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
model = Contact
def get_context_data(self, **kwargs):
context = {}
contact_obj = get_object_or_404(Contact, pk=kwargs['pk'])
context["contact_obj"] = ContactSerializer(contact_obj).data
user_assgn_list = [
assigned_to.id for assigned_to in contact_obj.assigned_to.all()
]
user_assigned_accounts = set(
self.request.user.account_assigned_users.values_list(
"id", flat=True)
)
contact_accounts = set(
contact_obj.account_contacts.values_list("id", flat=True)
)
if user_assigned_accounts.intersection(contact_accounts):
user_assgn_list.append(self.request.user.id)
if self.request.user == contact_obj.created_by:
user_assgn_list.append(self.request.user.id)
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if self.request.user.id not in user_assgn_list:
raise PermissionDenied
assigned_data = []
for each in contact_obj.assigned_to.all():
assigned_dict = {}
assigned_dict["id"] = each.id
assigned_dict["name"] = each.email
assigned_data.append(assigned_dict)
if self.request.user.is_superuser or self.request.user.role == "ADMIN":
users_mention = list(
User.objects.filter(
is_active=True, company=self.request.company
).values("username")
)
elif self.request.user != contact_obj.created_by:
users_mention = [
{"username": contact_obj.created_by.username}]
else:
users_mention = list(
contact_obj.assigned_to.all().values("username"))
context.update(
{
"comments": CommentSerializer(contact_obj.contact_comments.all(), many=True).data,
"attachments": AttachmentsSerializer(contact_obj.contact_attachment.all(), many=True).data,
"assigned_data": assigned_data,
"tasks": TaskSerializer(contact_obj.contacts_tasks.all(), many=True).data,
"users_mention": users_mention,
}
)
return context
@swagger_auto_schema(tags=["contacts"], manual_parameters=swagger_params.contact_detail_get_params)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return Response(context)
class UpdateContactView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
model = Contact
def dispatch(self, request, *args, **kwargs):
self.contact = get_object_or_404(Contact, pk=kwargs.get('pk'))
return super(UpdateContactView, self).dispatch(request, *args, **kwargs)
@swagger_auto_schema(tags=["contacts"], manual_parameters=swagger_params.contact_create_post_params)
def post(self, request, *args, **kwargs):
params = request.query_params if len(
request.data) == 0 else request.data
self.object = get_object_or_404(Contact, pk=kwargs.get('pk'))
address_obj = self.contact.address
contact_serializer = CreateContctForm(
data=params, instance=self.contact)
address_serializer = BillingAddressSerializer(
data=params, instance=address_obj)
data = {}
if not contact_serializer.is_valid():
data['contact_errors'] = contact_serializer.errors
if not address_serializer.is_valid():
data["address_errors"] = address_serializer.errors,
if data:
data['error'] = True
return Response(data)
addres_obj = address_serializer.save()
contact_obj = contact_serializer.save()
contact_obj.address = addres_obj
contact_obj.save()
previous_assigned_to_users = list(
contact_obj.assigned_to.all().values_list("id", flat=True)
)
if params.getlist("assigned_to", []):
current_site = get_current_site(request)
contact_obj.assigned_to.clear()
contact_obj.assigned_to.add(
*params.getlist("assigned_to"))
else:
contact_obj.assigned_to.clear()
if params.getlist("teams", []):
user_ids = Teams.objects.filter(
id__in=params.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)
if params.getlist("teams", []):
contact_obj.teams.clear()
contact_obj.teams.add(*params.getlist("teams"))
else:
contact_obj.teams.clear()
current_site = get_current_site(request)
assigned_to_list = list(
contact_obj.assigned_to.all().values_list("id", flat=True)
)
recipients = list(set(assigned_to_list) -
set(previous_assigned_to_users))
send_email_to_assigned_user.delay(
recipients,
contact_obj.id,
domain=current_site.domain,
protocol=request.scheme,
)
if request.FILES.get("contact_attachment"):
attachment = Attachments()
attachment.created_by = request.user
attachment.file_name = request.FILES.get(
"contact_attachment").name
attachment.contact = contact_obj
attachment.attachment = request.FILES.get(
"contact_attachment")
attachment.save()
return Response({'error': False})
def get_context_data(self, **kwargs):
params = self.request.query_params if len(
self.request.data) == 0 else request.data
context = {}
context["contact_obj"] = ContactSerializer(self.contact).data
user_assgn_list = [
assigned_to.id for assigned_to in self.contact.assigned_to.all()
]
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
self.users = User.objects.filter(
is_active=True, company=self.request.company).order_by("email")
else:
self.users = User.objects.filter(
role="ADMIN", company=self.request.company).order_by("email")
if self.request.user == self.contact.created_by:
user_assgn_list.append(self.request.user.id)
if self.request.user.role != "ADMIN" and not self.request.user.is_superuser:
if self.request.user.id not in user_assgn_list:
raise PermissionDenied
context["address_obj"] = BillingAddressSerializer(
self.contact.address).data
context["users"] = UserSerializer(self.users, many=True).data
context["countries"] = COUNTRIES
context["teams"] = TeamsSerializer(
Teams.objects.filter(company=self.request.company), many=True).data
context["assignedto_list"] = [
int(i) for i in params.getlist("assigned_to", []) if i
]
return context
@swagger_auto_schema(tags=["contacts"], manual_parameters=swagger_params.contact_list_get_params)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return Response(context)
class RemoveContactView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
permission_classes = (IsAuthenticated,)
@swagger_auto_schema(tags=["contacts"], manual_parameters=swagger_params.contact_delete_get_params)
def get(self, request, *args, **kwargs):
contact_id = kwargs.get("pk")
self.object = get_object_or_404(Contact, id=contact_id)
if (
self.request.user.role != "ADMIN"
and not self.request.user.is_superuser
and self.request.user != self.object.created_by
) or self.object.company != self.request.company:
raise PermissionDenied
else:
if self.object.address_id:
self.object.address.delete()
self.object.delete()
return Response({"error": False})

View file

@ -20,7 +20,7 @@ class ContactForm(forms.ModelForm):
for key, value in self.fields.items():
if key == "phone":
value.widget.attrs["placeholder"] = "+911234567890"
pass
else:
value.widget.attrs["placeholder"] = value.label
self.fields["teams"].choices = [

51
contacts/serializer.py Normal file
View file

@ -0,0 +1,51 @@
from rest_framework import serializers
from contacts.models import Contact
from common.serializer import UserSerializer, CompanySerializer, BillingAddressSerializer
from teams.serializer import TeamsSerializer
class ContactSerializer(serializers.ModelSerializer):
created_by = UserSerializer()
company = CompanySerializer()
teams = TeamsSerializer(read_only=True, many=True)
assigned_to = UserSerializer(read_only=True, many=True)
address = BillingAddressSerializer(read_only=True)
get_team_users = UserSerializer(read_only=True, many=True)
get_team_and_assigned_users = UserSerializer(read_only=True, many=True)
get_assigned_users_not_in_teams = UserSerializer(read_only=True, many=True)
class Meta:
model = Contact
fields = (
'id',
"first_name",
"last_name",
"email",
"phone",
"address",
"description",
"assigned_to",
"created_by",
"created_on",
"is_active",
"teams",
"company",
"created_on_arrow",
"get_team_users",
"get_team_and_assigned_users",
"get_assigned_users_not_in_teams",
)
class CreateContctForm(serializers.ModelSerializer):
class Meta:
model = Contact
fields = (
"first_name",
"last_name",
"email",
"phone",
"address",
"description",
)

View file

@ -0,0 +1,31 @@
from drf_yasg import openapi
company_params_in_header = openapi.Parameter(
'company', openapi.IN_HEADER, required=True, type=openapi.TYPE_STRING)
contact_list_get_params = [company_params_in_header]
contact_list_post_params = [company_params_in_header]
contact_detail_get_params = [company_params_in_header]
contact_delete_get_params = [company_params_in_header]
contact_create_post_params = [
company_params_in_header,
openapi.Parameter('first_name', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
openapi.Parameter('last_name', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
openapi.Parameter('phone', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
openapi.Parameter('email', openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING),
openapi.Parameter('teams', openapi.IN_QUERY, type=openapi.TYPE_INTEGER),
openapi.Parameter('contact_attachment', openapi.IN_QUERY, type=openapi.TYPE_FILE),
openapi.Parameter('description', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('address_line', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('street', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('city', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('state', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('postcode', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('country', openapi.IN_QUERY, type=openapi.TYPE_STRING),
openapi.Parameter('assigned_to', openapi.IN_QUERY, type=openapi.TYPE_INTEGER),
]

View file

@ -1,6 +1,7 @@
{% extends 'sales/base.html' %}
{% load static %}
{% block breadcrumb %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.3/css/intlTelInput.min.css" />
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'contacts:list' %}">Contacts</a></li>
@ -169,7 +170,7 @@
<input type="hidden" id="save_new_form" name="savenewform">
<div class="col-md-12">
<div class="row marl buttons_row text-center form_btn_row">
<button class="btn btn-default save" type="submit">Save</button>
<button class="btn btn-default save update_data" type="submit">Save</button>
{% if not contact_obj %}
<button class="btn btn-success save savenew" type="submit">Save & New</button>
{% endif %}
@ -186,10 +187,68 @@
</form>
{% endblock %}
{% block js_block %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/11.0.9/css/intlTelInput.css" rel="stylesheet" media="screen">
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/11.0.9/js/intlTelInput.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/11.0.9/js/utils.js"></script>
<script type="text/javascript">
var telInput = $("#id_phone"),
errorMsg = $("#error-msg"),
validMsg = $("#valid-msg");
// initialise plugin
telInput.intlTelInput({
allowExtensions: true,
formatOnDisplay: true,
autoFormat: true,
autoHideDialCode: true,
autoPlaceholder: true,
defaultCountry: "auto",
ipinfoToken: "yolo",
nationalMode: false,
numberType: "MOBILE",
//onlyCountries: ['us', 'gb', 'ch', 'ca', 'do'],
preferredCountries: ['sa', 'ae', 'qa','om','bh','kw','ma'],
preventInvalidNumbers: true,
separateDialCode: true,
initialCountry: "us",
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/11.0.9/js/utils.js"
});
var reset = function() {
telInput.removeClass("error");
errorMsg.addClass("hide");
validMsg.addClass("hide");
};
// on blur: validate
telInput.blur(function() {
reset();
if ($.trim(telInput.val())) {
if (telInput.intlTelInput("isValidNumber")) {
validMsg.removeClass("hide");
} else {
telInput.addClass("error");
errorMsg.removeClass("hide");
}
}
});
// on keyup / change flag: reset
telInput.on("keyup change", reset);
$('.update_data').click(function(){
country_code = $('.selected-dial-code').text()
phone_number = $('#id_phone').val()
$('#id_phone').val(country_code + ' ' + phone_number)
})
$(".savenew").on('click', function (e) {
e.preventDefault()
$("#save_new_form").val('true')
country_code = $('.selected-dial-code').text()
phone_number = $('#id_phone').val()
$('#id_phone').val(country_code + ' ' + phone_number)
$('#add_form').submit()
});
$(document).ready(function () {

View file

@ -30,12 +30,12 @@ from teams.models import Teams
@login_required
def get_teams_and_users(request):
data = {}
teams = Teams.objects.all()
teams = Teams.objects.filter(company=request.company)
teams_data = [
{"team": team.id, "users": [user.id for user in team.users.all()]}
for team in teams
]
users = User.objects.all().values_list("id", flat=True)
users = User.objects.filter(company=request.company).values_list("id", flat=True)
data["teams"] = teams_data
data["users"] = list(users)
return JsonResponse(data)
@ -107,15 +107,15 @@ class CreateContactView(SalesAccessRequiredMixin, LoginRequiredMixin, CreateView
def dispatch(self, request, *args, **kwargs):
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
self.users = User.objects.filter(is_active=True).order_by("email")
self.users = User.objects.filter(is_active=True, company=self.request.company).order_by("email")
else:
self.users = User.objects.filter(role="ADMIN").order_by("email")
self.users = User.objects.filter(role="ADMIN", company=self.request.company).order_by("email")
return super(CreateContactView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(CreateContactView, self).get_form_kwargs()
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
self.users = User.objects.filter(is_active=True).order_by("email")
self.users = User.objects.filter(is_active=True, company=self.request.company).order_by("email")
kwargs.update({"assigned_to": self.users})
return kwargs
@ -228,7 +228,7 @@ class CreateContactView(SalesAccessRequiredMixin, LoginRequiredMixin, CreateView
context["address_form"] = BillingAddressForm(self.request.POST)
else:
context["address_form"] = BillingAddressForm()
context["teams"] = Teams.objects.all()
context["teams"] = Teams.objects.filter(company=self.request.company)
return context
@ -306,15 +306,15 @@ class UpdateContactView(SalesAccessRequiredMixin, LoginRequiredMixin, UpdateView
raise PermissionDenied
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
self.users = User.objects.filter(is_active=True).order_by("email")
self.users = User.objects.filter(is_active=True, company=self.request.company).order_by("email")
else:
self.users = User.objects.filter(role="ADMIN").order_by("email")
self.users = User.objects.filter(role="ADMIN", company=self.request.company).order_by("email")
return super(UpdateContactView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(UpdateContactView, self).get_form_kwargs()
if self.request.user.role == "ADMIN" or self.request.user.is_superuser:
self.users = User.objects.filter(is_active=True).order_by("email")
self.users = User.objects.filter(is_active=True, company=self.request.company).order_by("email")
kwargs.update({"assigned_to": self.users})
return kwargs
@ -443,7 +443,7 @@ class UpdateContactView(SalesAccessRequiredMixin, LoginRequiredMixin, UpdateView
context["contact_form"] = context["form"]
context["users"] = self.users
context["countries"] = COUNTRIES
context["teams"] = Teams.objects.all()
context["teams"] = Teams.objects.filter(company=self.request.company)
context["assignedto_list"] = [
int(i) for i in self.request.POST.getlist("assigned_to", []) if i
]

View file

@ -16,11 +16,11 @@ def send_mail(mto, mfrom, msubject, mbody, user_active):
else:
mail_sender = settings.INACTIVE_MAIL_SENDER
if mail_sender == "AMAZON":
# conn=SESConnection(settings.AM_ACCESS_KEY, settings.AM_PASS_KEY)
# conn=SESConnection(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
conn = boto.ses.connect_to_region(
settings.AWS_REGION,
aws_access_key_id=settings.AM_ACCESS_KEY,
aws_secret_access_key=settings.AM_PASS_KEY,
settings.AWS_SES_REGION_NAME,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
)
response = conn.send_email(mfrom, msubject, mbody, mto, format="html")
elif mail_sender == "MAILGUN":

View file

@ -1,12 +1,17 @@
import os
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
DEBUG = False
DOMAIN_NAME = "bottlecrm.com"
AWS_STORAGE_BUCKET_NAME = AWS_BUCKET_NAME = os.getenv("AWSBUCKETNAME", "")
AM_ACCESS_KEY = AWS_ACCESS_KEY_ID = os.getenv("AWSACCESSKEYID", "")
AM_PASS_KEY = AWS_SECRET_ACCESS_KEY = os.getenv("AWSSECRETACCESSKEY", "")
AWS_STORAGE_BUCKET_NAME = AWS_BUCKET_NAME = os.getenv("AWS_BUCKET_NAME", "")
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID", "")
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY", "")
S3_DOMAIN = AWS_S3_CUSTOM_DOMAIN = str(AWS_BUCKET_NAME) + ".s3.amazonaws.com"
AWS_SES_REGION_NAME = os.getenv("AWS_SES_REGION_NAME", "")
AWS_SES_REGION_ENDPOINT = os.getenv("AWS_SES_REGION_ENDPOINT", "")
AWS_S3_OBJECT_PARAMETERS = {
"CacheControl": "max-age=86400",
@ -34,3 +39,22 @@ AWS_IS_GZIPPED = True
AWS_ENABLED = True
AWS_S3_SECURE_URLS = True
COMPRESS_URL = STATIC_URL
EMAIL_BACKEND = "django_ses.SESBackend"
SESSION_COOKIE_DOMAIN = ".bottlecrm.com"
ELASTIC_APM = {
'SERVICE_NAME': os.getenv("ELASTIC_APM_SERVICE_NAME"),
'SECRET_TOKEN': os.getenv("ELASTIC_APM_SECRET_TOKEN"),
'SERVER_URL': os.getenv("ELASTIC_APM_SERVER_URL"),
}
sentry_sdk.init(
dsn=os.getenv("SENTRY_DSN"),
integrations=[DjangoIntegration()],
# If you wish to associate users to errors (assuming you are using
# django.contrib.auth) you may enable sending PII data.
send_default_pii=True
)

View file

@ -2,18 +2,19 @@ import os
from dotenv import load_dotenv
from pathlib import Path
from celery.schedules import crontab
from corsheaders.defaults import default_headers
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
env_path = Path(".") / ".env"
env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path)
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv("SECRETKEY")
SECRET_KEY = os.getenv("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = os.getenv("DEBUG")
ALLOWED_HOSTS = [".bottlecrm.com", ".localhost"]
@ -49,6 +50,10 @@ INSTALLED_APPS = [
"invoices",
"events",
"teams",
"rest_framework",
"drf_yasg",
"corsheaders",
"django_ses",
]
MIDDLEWARE = [
@ -59,6 +64,8 @@ MIDDLEWARE = [
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"common.middleware.get_company.GetCompany",
"corsheaders.middleware.CorsMiddleware",
]
ROOT_URLCONF = "crm.urls"
@ -66,7 +73,7 @@ ROOT_URLCONF = "crm.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(BASE_DIR, "templates"),],
"DIRS": [os.path.join(BASE_DIR, "templates"), ],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
@ -100,7 +107,7 @@ DATABASES = {
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
os.path.join(BASE_DIR, "blog_app/static"),
# os.path.join(BASE_DIR, "blog_app/static"),
]
# Password validation
@ -110,9 +117,9 @@ AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",},
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", },
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", },
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", },
]
# Internationalization
@ -133,7 +140,7 @@ USE_TZ = True
STATIC_URL = "/static/"
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
# EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
# EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
# EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
@ -142,11 +149,11 @@ EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
# AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend', )
EMAIL_HOST = "smtp.sendgrid.net"
EMAIL_HOST_USER = os.getenv("SG_USER", "")
EMAIL_HOST_PASSWORD = os.getenv("SG_PWD", "")
EMAIL_PORT = 587
EMAIL_USE_TLS = True
# EMAIL_HOST = "smtp.sendgrid.net"
# EMAIL_HOST_USER = os.getenv("SG_USER", "")
# EMAIL_HOST_PASSWORD = os.getenv("SG_PWD", "")
# EMAIL_PORT = 587
# EMAIL_USE_TLS = True
AUTH_USER_MODEL = "common.User"
@ -162,21 +169,25 @@ if ENV_TYPE == "dev":
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"
STATIC_URL = "/static/"
STATICFILES_DIRS = (BASE_DIR + "/static",)
COMPRESS_ROOT = BASE_DIR + "/static/"
ADMIN_MEDIA_PREFIX = STATIC_URL + "admin/"
elif ENV_TYPE == "live":
INSTALLED_APPS = INSTALLED_APPS + [
'elasticapm.contrib.django',
]
MIDDLEWARE = MIDDLEWARE + [
'elasticapm.contrib.django.middleware.TracingMiddleware',
]
from .server_settings import *
SESSION_COOKIE_DOMAIN = ".bottlecrm.com"
# CORS_ORIGIN_ALLOW_ALL = True
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
# COMPRESS_ROOT = BASE_DIR + "/static/"
COMPRESS_JS_FILTERS = ["compressor.filters.jsmin.JSMinFilter"]
@ -215,6 +226,10 @@ COMPRESS_OFFLINE_CONTEXT = {
}
DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL")
ADMIN_EMAIL = os.getenv("ADMIN_EMAIL")
MARKETING_REPLY_EMAIL = os.getenv("MARKETING_REPLY_EMAIL", "")
PASSWORD_RESET_MAIL_FROM_USER = os.getenv("PASSWORD_RESET_MAIL_FROM_USER", "")
# celery Tasks
CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL")
@ -242,10 +257,6 @@ CELERY_BEAT_SCHEDULE = {
MAIL_SENDER = "AMAZON"
# INACTIVE_MAIL_SENDER = "MANDRILL"
AM_ACCESS_KEY = os.getenv("AWSACCESSKEYID", "")
AM_PASS_KEY = os.getenv("AWSSECRETACCESSKEY", "")
AWS_REGION = os.getenv("AWS_REGION", "")
# MGUN_API_URL = os.getenv("MGUN_API_URL", "")
# MGUN_API_KEY = os.getenv("MGUN_API_KEY", "")
@ -263,16 +274,15 @@ AWS_REGION = os.getenv("AWS_REGION", "")
# GP_CLIENT_SECRET = os.getenv("GP_CLIENT_SECRET", False)
# ENABLE_GOOGLE_LOGIN = os.getenv("ENABLE_GOOGLE_LOGIN", False)
MARKETING_REPLY_EMAIL = os.getenv("MARKETINGREPLYEMAIL")
PASSWORD_RESET_TIMEOUT_DAYS = 3
SENTRY_ENABLED = os.getenv("SENTRY_ENABLED", False)
if SENTRY_ENABLED and not DEBUG:
if os.getenv("SENTRYDSN") is not None:
if os.getenv("SENTRY_DSN") is not None:
RAVEN_CONFIG = {
"dsn": os.getenv("SENTRYDSN", ""),
"dsn": os.getenv("SENTRY_DSN", ""),
}
INSTALLED_APPS = INSTALLED_APPS + [
"raven.contrib.django.raven_compat",
@ -287,8 +297,8 @@ LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"filters": {
"require_debug_false": {"()": "django.utils.log.RequireDebugFalse",},
"require_debug_true": {"()": "django.utils.log.RequireDebugTrue",},
"require_debug_false": {"()": "django.utils.log.RequireDebugFalse", },
"require_debug_true": {"()": "django.utils.log.RequireDebugTrue", },
},
"formatters": {
"django.server": {
@ -317,11 +327,11 @@ LOGGING = {
"filters": ["require_debug_false"],
"class": "django.utils.log.AdminEmailHandler",
},
"logfile": {"class": "logging.FileHandler", "filename": "server.log",},
"logfile": {"class": "logging.FileHandler", "filename": "server.log", },
},
"loggers": {
"django": {
"handlers": ["console", "console_debug_false", "logfile",],
"handlers": ["console", "console_debug_false", "logfile", ],
"level": "INFO",
},
"django.server": {
@ -389,7 +399,64 @@ CACHES = {
}
}
PASSWORD_RESET_MAIL_FROM_USER = os.getenv("PASSWORD_RESET_MAIL_FROM_USER")
SETTINGS_EXPORT = ["APPLICATION_NAME"]
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
SWAGGER_SETTINGS = {
'DEFAULT_INFO': "crm.urls.info",
'SECURITY_DEFINITIONS': {
"api_key": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
},
}
TOKEN_SECRET_KEY = 'mk%l3jsghg-u*!luyer@tew$xn!ksb(k$&2rj4h4@%tmy76z'
JWT_AUTH = {
'JWT_ENCODE_HANDLER':
'rest_framework_jwt.utils.jwt_encode_handler',
'JWT_DECODE_HANDLER':
'rest_framework_jwt.utils.jwt_decode_handler',
'JWT_PAYLOAD_HANDLER':
'common.utils.jwt_payload_handler',
'JWT_RESPONSE_PAYLOAD_HANDLER':
'rest_framework_jwt.utils.jwt_response_payload_handler',
'JWT_SECRET_KEY': TOKEN_SECRET_KEY,
'JWT_GET_USER_SECRET_KEY': None,
'JWT_PUBLIC_KEY': None,
'JWT_PRIVATE_KEY': None,
'JWT_ALGORITHM': 'HS256',
'JWT_VERIFY': True,
'JWT_VERIFY_EXPIRATION': True,
'JWT_LEEWAY': 0,
'JWT_AUDIENCE': None,
'JWT_ISSUER': None,
'JWT_ALLOW_REFRESH': True,
'JWT_AUTH_HEADER_PREFIX': 'JWT',
'JWT_AUTH_COOKIE': None,
}
CORS_ALLOW_HEADERS = default_headers + (
'company',
)
CORS_ORIGIN_ALLOW_ALL = True

View file

@ -3,11 +3,37 @@ from django.conf.urls.static import static
from django.contrib.auth import views
from django.urls import include, path
from common.views import handler404, handler500
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from django.conf.urls import url
from rest_framework import permissions
openapi_info = openapi.Info(
title="Crm API",
default_version='v1',
)
schema_view = get_schema_view(
openapi_info,
public=True,
permission_classes=(permissions.AllowAny,),
)
app_name = "crm"
urlpatterns = [
url(r'^swagger(?P<format>\.json|\.yaml)$',
schema_view.without_ui(cache_timeout=0), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger',
cache_timeout=0), name='schema-swagger-ui'),
url(r'^redoc/$', schema_view.with_ui('redoc',
cache_timeout=0), name='schema-redoc'),
path("", include("common.urls", namespace="common")),
path("api-common/", include("common.api_urls", namespace="api_common")),
path("api-accounts/", include("accounts.api_urls", namespace="api_accounts")),
path("api-contacts/", include("contacts.api_urls", namespace="api_contacts")),
path("", include("django.contrib.auth.urls")),
path("marketing/", include("marketing.urls", namespace="marketing")),
path("accounts/", include("accounts.urls", namespace="accounts")),

View file

@ -58,9 +58,9 @@ copyright = u"2017, MicroPyramid"
# built documents.
#
# The short X.Y version.
version = "0.8.0"
version = "0.7.0"
# The full version, including alpha/beta/rc tags.
release = "0.8.0"
release = "0.7.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

8
emails/serializer.py Normal file
View file

@ -0,0 +1,8 @@
from rest_framework import serializers
from emails.models import Email
class EmailSerailizer(serializers.ModelSerializer):
class Meta:
model = Email
fields = "__all__"

31
env.md
View file

@ -1,14 +1,17 @@
# Environment variables
SECRETKEY
SECRET_KEY
ENV_TYPE="dev/live"
PASSWORD_RESET_MAIL_FROM_USER
DEBUG=True
# AWS
AWSBUCKETNAME
AWSACCESSKEYID
AWSSECRETACCESSKEY
AWS_REGION
AWS_BUCKET_NAME
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_SES_REGION_NAME
AWS_SES_REGION_ENDPOINT
# DB
DBNAME
@ -18,8 +21,7 @@ DBHOST
DBPORT
# Sentry
SENTRY_ENABLED
SENTRYDSN
SENTRY_DSN
# Celery
CELERY_BROKER_URL
@ -28,8 +30,13 @@ CELERY_RESULT_BACKEND
#CACHES
MEMCACHELOCATION
# Sendgrid
SG_USER
SG_PWD
# Email
DEFAULT_FROM_EMAIL
MARKETING_REPLY_EMAIL
MARKETING_REPLY_EMAIL
PASSWORD_RESET_MAIL_FROM_USER
ADMIN_EMAIL
# Elastic APM
ELASTIC_APM_SERVICE_NAME
ELASTIC_APM_SECRET_TOKEN
ELASTIC_APM_SERVER_URL

0
events/serializer.py Normal file
View file

View file

@ -49,7 +49,6 @@ class InvoiceForm(forms.ModelForm):
self.fields["teams"].required = False
self.fields["phone"].widget.attrs.update({"placeholder": "+911234567890"})
self.fields["invoice_title"].required = True
self.fields["invoice_number"].required = True
self.fields["currency"].required = True
self.fields["email"].required = True
self.fields["total_amount"].required = True
@ -64,24 +63,10 @@ class InvoiceForm(forms.ModelForm):
return quantity
def clean_invoice_number(self):
invoice_number = self.cleaned_data.get("invoice_number")
if (
Invoice.objects.filter(invoice_number=invoice_number)
.exclude(id=self.instance.id)
.exists()
):
raise forms.ValidationError(
"Invoice with this Invoice Number already exists."
)
return invoice_number
class Meta:
model = Invoice
fields = (
"invoice_title",
"invoice_number",
"from_address",
"to_address",
"name",
@ -96,6 +81,7 @@ class InvoiceForm(forms.ModelForm):
"details",
"due_date",
"accounts",
"tax"
)

View file

@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-09-25 08:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('invoices', '0009_invoice_company'),
]
operations = [
migrations.AddField(
model_name='invoice',
name='tax',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True),
),
]

View file

@ -1,3 +1,4 @@
import datetime
import arrow
from django.db import models
from django.utils.translation import ugettext_lazy as _
@ -41,6 +42,9 @@ class Invoice(models.Model):
total_amount = models.DecimalField(
blank=True, null=True, max_digits=12, decimal_places=2
)
tax = models.DecimalField(
blank=True, null=True, max_digits=12, decimal_places=2
)
currency = models.CharField(
max_length=3, choices=CURRENCY_CODES, blank=True, null=True
)
@ -66,6 +70,9 @@ class Invoice(models.Model):
company = models.ForeignKey(
Company, on_delete=models.SET_NULL, null=True, blank=True
)
tax = models.DecimalField(
blank=True, null=True, max_digits=12, decimal_places=2
)
class Meta:
"""Meta definition for Invoice."""
@ -77,6 +84,20 @@ class Invoice(models.Model):
"""Unicode representation of Invoice."""
return self.invoice_number
def invoice_id_generator(self, prev_invoice_number=None):
if prev_invoice_number:
prev_invoice_number += 1
return prev_invoice_number
date = datetime.datetime.now().strftime('%d%m%Y')
return int(date + "0001")
def save(self, *args, **kwargs):
if not self.invoice_number:
self.invoice_number = self.invoice_id_generator()
while Invoice.objects.filter(invoice_number=self.invoice_number).exists():
self.invoice_number = self.invoice_id_generator(prev_invoice_number=self.invoice_number)
super(Invoice, self).save(*args, **kwargs)
def formatted_total_amount(self):
return self.currency + " " + str(self.total_amount)

8
invoices/serializer.py Normal file
View file

@ -0,0 +1,8 @@
from rest_framework import serializers
from invoices.models import Invoice
class InvoiceSerailizer(serializers.ModelSerializer):
class Meta:
model = Invoice
fields = "__all__"

View file

@ -52,25 +52,19 @@
</div>
</div>
</div>
<div class="col-md-12" style="margin: 0; padding: 0;">
<div class="form-group row" style="padding:0; margin:0;">
<label for="inputPassword" class="col-sm-3 col-form-label" style="">Invoice
Number{% if form.invoice_number.field.required %}<span class="error_marker" style="color:red">
*</span>{% endif %}</label>
<div class="col-sm-9 pl-0">
{{ form.invoice_number }}
{% if invoice_obj %}
<div class="col-md-6">
<div class="form-group row">
<label for="inputPassword" class="col-sm-4 col-form-label" style="">Invoice
Number<span class="error_marker" style="color:red">
*</span></label>
<div class="col-sm-8 pl-0">
<input type="text" name="invoice_number" value="{{invoice_obj.invoice_number}}" class="form-control" readonly="">
<span class="error" id="id__invoice_number">{{ form.invoice_number.errors }}</span>
</div>
</div>
</div>
{% endif %}
<div class="col-md-12">
<div class="row">
<div class="form-group mt-5 col-md-6">
@ -232,17 +226,15 @@
{% endif %}
<div class="form-group row">
<label for="staticEmail" class="col-sm-4 col-form-label">Assign To</label>
<select class="assigned_users form-control" name="assigned_to" multiple="multiple"
id="id_assigned_to">
{% for user in users %}
<option value="{{user.id}}" {% if user in invoice_obj.get_assigned_users_not_in_teams or user.id in assignedto_list %}
selected="" {% endif %}>{{user.email}}</option>
{% endfor %}
</select>
<span class="error error_message" id="error_id_assigned_to">{{ form.assigned_to.errors }}</span>
<div class="col-sm-8 pl-0">
<select class="assigned_users form-control" name="assigned_to" multiple="multiple" id="id_assigned_to">
{% for user in users %}
<option value="{{user.id}}" {% if user in invoice_obj.get_assigned_users_not_in_teams or user.id in assignedto_list %}
selected="" {% endif %}>{{user.email}}</option>
{% endfor %}
</select>
</div>
<span class="error error_message" id="error_id_assigned_to">{{ form.assigned_to.errors }}</span>
</div>
<div class="form-group row">
<label for="staticEmail" class="col-sm-4 col-form-label">Accounts</label>
@ -256,20 +248,26 @@
</div>
<div class="col-md-12 mt-5">
<div class="row pt-0 pb-4">
<div class="form-group col-md-4">
<div class="form-group col-md-3">
<label for="inputCity">Quality Hours{% if form.quantity.field.required %}<span
class="error_marker" style="color:red"> * </span>{% endif %}</label>
{{ form.quantity }}
<span class="error" id="id__quantity">{{ form.quantity.errors }}</span>
</div>
<div class="form-group col-md-4">
<div class="form-group col-md-3">
<label for="inputState">Rate {% if form.rate.field.required %}<span class="error_marker"
style="color:red">*</span>{% endif %}</label>
{{ form.rate }}
<span class="error" id="id__rate">{{ form.rate.errors }}</span>
</div>
<div class="form-group col-md-4">
<div class="form-group col-md-3">
<label for="inputCity">Tax{% if form.tax.field.required %}<span
class="error_marker" style="color:red"> * </span>{% endif %}</label>
{{ form.tax }}
<span class="error" id="id__tax">{{ form.tax.errors }}</span>
</div>
<div class="form-group col-md-3">
<label for="inputZip">Total Amount{% if form.total_amount.field.required %}<span
class="error_marker" style="color:red"> * </span>{% endif %}</label>
{{ form.total_amount }}
@ -337,8 +335,19 @@
$('#id_quantity').keyup(calculate);
$('#id_rate').keyup(calculate);
$('#id_tax').keyup(calculate)
function calculate(e) {
$('#id_total_amount').val($('#id_quantity').val() * $('#id_rate').val());
tax = $('#id_tax').val()
data = $('#id_quantity').val() * $('#id_rate').val()
$('#id_total_amount').val(data);
if(tax){
if(data){
$('#id_total_amount').val(parseInt(tax)+parseInt(data));
}
else{
$('#id_total_amount').val(parseInt(tax));
}
}
}
});

View file

@ -141,6 +141,15 @@
{{ invoice.rate }}</td>
{% endif %}
</tr>
<tr class="sub_total">
{% if invoice.tax %}
<td colspan="2"
style="padding:8px 10px;vertical-align: middle;background: #dedede;font-weight: 800;border: none;">
<b>Tax</b></td>
<td style="padding:8px 10px;vertical-align: middle;background: #dedede;font-weight: 800;border: none;">
{{ invoice.tax }}</td>
{% endif %}
</tr>
<tr class="sub_total">
<td colspan="2"
style="padding:8px 10px;vertical-align: middle;background: #dedede;font-weight: 800;border: none;">

View file

@ -182,19 +182,25 @@
<div class="col-md-12 ">
<div class="row pt-0 pb-4">
{% if invoice.quantity %}
<div class="form-group col-md-4">
<div class="form-group col-md-3">
<label for="inputCity">Quality Hours</label>
{{ invoice.quantity }}
</div>
{% endif %}
{% if invoice.rate %}
<div class="form-group col-md-4">
<div class="form-group col-md-3">
<label for="inputCity">Rate</label>
{{ invoice.rate }}
</div>
{% endif %}
{% if invoice.tax %}
<div class="form-group col-md-3">
<label for="inputCity">Tax</label>
{{ invoice.tax }}
</div>
{% endif %}
{% if invoice.total_amount %}
<div class="form-group col-md-4">
<div class="form-group col-md-3">
<label for="inputCity">Total Amount</label>
{{ invoice.total_amount }}
</div>

View file

@ -141,9 +141,9 @@
{% endif %}
{% for invoice in invoices %}
{% if invoice.due_date < today %}
<tr class="text-center due_date">
<tr class="text-center">
<td scope="row" >{{ forloop.counter }}</td>
<td><a style="color: red;" href="#" data-toggle="modal" data-target="#exampleModalCenter_invoice{{invoice.id}}">
<td><a href="#" data-toggle="modal" data-target="#exampleModalCenter_invoice{{invoice.id}}">
{{ invoice.invoice_title }} </a></td>
<td>{{ invoice.invoice_number }} </td>
<td>{{ invoice.email }} </td>

View file

@ -7,6 +7,7 @@ from django import forms
from common.models import Attachments, Comment
from leads.models import Lead
from accounts.models import Account
from phonenumber_field.formfields import PhoneNumberField
from teams.models import Teams
@ -74,6 +75,24 @@ class LeadForm(forms.ModelForm):
prev_choices = self.fields["source"]._get_choices()
prev_choices = prev_choices + [("micropyramid", "Micropyramid")]
self.fields["source"]._set_choices(prev_choices)
self.company = request_obj.company
def clean_account_name(self):
account_name = self.cleaned_data.get("account_name")
if self.instance.id:
if self.instance.account_name != account_name:
if not Account.objects.filter(
name__iexact=self.cleaned_data.get("account_name"),
company=self.company
).exists():
return self.cleaned_data.get("account_name")
raise forms.ValidationError(
"Account already exists with this name")
return self.cleaned_data.get("account_name")
if not Account.objects.filter(name__iexact=self.cleaned_data.get("account_name"), company=self.company).exists():
return self.cleaned_data.get("account_name")
raise forms.ValidationError("Account already exists with this name")
class Meta:
model = Lead

61
leads/serializer.py Normal file
View file

@ -0,0 +1,61 @@
from rest_framework import serializers
from leads.models import Lead
from accounts.models import Tags
from common.serializer import UserSerializer
from contacts.serializer import ContactSerializer
class TagsSerailizer(serializers.ModelSerializer):
class Meta:
model = Tags
fields = (
"name",
"slug"
)
class LeadSerializer(serializers.ModelSerializer):
# user = UserSerializer()
assigned_to = UserSerializer(read_only=True, many=True)
created_by = UserSerializer()
tags = TagsSerailizer(read_only=True, many=True)
contacts = ContactSerializer(read_only=True, many=True)
class Meta:
model = Lead
# fields = ‘__all__’
fields = (
'id',
"title",
"first_name",
"last_name",
"email",
"phone",
"status",
"source",
"address_line",
"street",
"city",
"state",
"postcode",
"country",
"website",
"description",
"assigned_to",
"account_name",
"opportunity_amount",
"created_by",
"created_on",
"is_active",
"enquery_type",
"tags",
"contacts",
"created_from_site",
"teams",
"company",
# "phone_raw_input",
# "created_on_arrow",
# "get_team_users",
# "get_team_and_assigned_users",
# "get_assigned_users_not_in_teams",
)

View file

@ -8,7 +8,7 @@ from django.db.models import Q
from django.shortcuts import reverse
from django.template.loader import render_to_string
from accounts.models import User
from accounts.models import User, Company
from leads.models import Lead
from marketing.models import BlockedDomain, BlockedEmail
@ -112,12 +112,13 @@ def send_email_to_assigned_user(
@task
def create_lead_from_file(validated_rows, invalid_rows, user_id, source):
def create_lead_from_file(validated_rows, invalid_rows, user_id, source, company_id):
"""Parameters : validated_rows, invalid_rows, user_id.
This function is used to create leads from a given file.
"""
email_regex = "^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,4})$"
user = User.objects.get(id=user_id)
company = Company.objects.filter(id=company_id).first()
for row in validated_rows:
if not Lead.objects.filter(title=row.get("title")).exists():
if re.match(email_regex, row.get("email")) is not None:
@ -139,8 +140,9 @@ def create_lead_from_file(validated_rows, invalid_rows, user_id, source):
lead.account_name = row.get("account_name", "")[:255]
lead.created_from_site = False
lead.created_by = user
lead.company = company
lead.save()
except e:
except Exception as e:
print(e)

View file

@ -665,6 +665,7 @@ def update_lead(request, pk):
description=request.POST.get("description"),
website=request.POST.get("website"),
lead=lead_obj,
company=request.company
)
account_object.billing_address_line = lead_obj.address_line
account_object.billing_street = lead_obj.street
@ -1092,6 +1093,7 @@ def upload_lead_csv_file(request):
lead_form.invalid_rows,
request.user.id,
request.get_host(),
request.company.id,
)
return JsonResponse(
{"error": False, "data": lead_form.data}, status=status.HTTP_201_CREATED

View file

@ -580,7 +580,6 @@ class BlockedEmailForm(forms.ModelForm):
# # Check to see if a start_date was chosen.
# if self.cleaned_data['email_domain']:
# # import pdb; pdb.set_trace()
# sqs = sqs.filter(email__icontains=self.cleaned_data.get('email_domain'))
# return sqs

View file

@ -0,0 +1,20 @@
# Generated by Django 3.1 on 2020-09-11 19:10
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('common', '0022_auto_20200609_1203'),
('marketing', '0013_blockeddomain_blockedemail'),
]
operations = [
migrations.AddField(
model_name='emailtemplate',
name='company',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='marketing_emailtemplates_company', to='common.company'),
),
]

View file

@ -0,0 +1,55 @@
# Generated by Django 3.1 on 2020-09-11 21:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('common', '0022_auto_20200609_1203'),
('marketing', '0014_emailtemplate_company'),
]
operations = [
migrations.AddField(
model_name='blockeddomain',
name='company',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='common.company'),
),
migrations.AddField(
model_name='blockedemail',
name='company',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='common.company'),
),
migrations.AddField(
model_name='campaign',
name='company',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='marketing_campaigns_company', to='common.company'),
),
migrations.AddField(
model_name='contact',
name='company',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='marketing_contacts_company', to='common.company'),
),
migrations.AddField(
model_name='contactemailcampaign',
name='company',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='marketing_contacts_emails_campaign_company', to='common.company'),
),
migrations.AddField(
model_name='contactlist',
name='company',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='marketing_contactlist_company', to='common.company'),
),
migrations.AddField(
model_name='failedcontact',
name='company',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='marketing_failed_contacts_company', to='common.company'),
),
migrations.AddField(
model_name='tag',
name='company',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='common.company'),
),
]

View file

@ -8,7 +8,7 @@ from django.core.validators import RegexValidator
from django.dispatch import receiver
from django.db.models import Sum
from django.template.defaultfilters import slugify
from common.models import User
from common.models import User, Company
from common.utils import convert_to_custom_timezone
@ -19,6 +19,12 @@ class Tag(models.Model):
User, related_name="marketing_tags", null=True, on_delete=models.SET_NULL
)
created_on = models.DateTimeField(auto_now_add=True)
company = models.ForeignKey(
Company,
on_delete=models.CASCADE,
null=True,
)
@property
def created_by_user(self):
@ -37,6 +43,12 @@ class EmailTemplate(models.Model):
title = models.CharField(max_length=5000)
subject = models.CharField(max_length=5000)
html = models.TextField()
company = models.ForeignKey(
Company,
related_name="marketing_emailtemplates_company",
on_delete=models.CASCADE,
null=True,
)
class Meta:
ordering = [
@ -62,6 +74,12 @@ class ContactList(models.Model):
tags = models.ManyToManyField(Tag)
# is_public = models.BooleanField(default=False)
visible_to = models.ManyToManyField(User, related_name="contact_lists_visible_to")
company = models.ForeignKey(
Company,
related_name="marketing_contactlist_company",
on_delete=models.CASCADE,
null=True,
)
class Meta:
ordering = ("-created_on",)
@ -148,6 +166,12 @@ class Contact(models.Model):
city = models.CharField(max_length=500, null=True, blank=True)
state = models.CharField(max_length=500, null=True, blank=True)
contry = models.CharField(max_length=500, null=True, blank=True)
company = models.ForeignKey(
Company,
related_name="marketing_contacts_company",
on_delete=models.CASCADE,
null=True,
)
def __str__(self):
return self.email
@ -186,6 +210,12 @@ class FailedContact(models.Model):
city = models.CharField(max_length=500, null=True, blank=True)
state = models.CharField(max_length=500, null=True, blank=True)
contry = models.CharField(max_length=500, null=True, blank=True)
company = models.ForeignKey(
Company,
related_name="marketing_failed_contacts_company",
on_delete=models.CASCADE,
null=True,
)
def __str__(self):
return self.email
@ -243,6 +273,12 @@ class Campaign(models.Model):
attachment = models.FileField(
max_length=1000, upload_to=get_campaign_attachment_path, blank=True, null=True
)
company = models.ForeignKey(
Company,
related_name="marketing_campaigns_company",
on_delete=models.CASCADE,
null=True,
)
class Meta:
ordering = ("-created_on",)
@ -446,6 +482,12 @@ class ContactEmailCampaign(models.Model):
on_delete=models.SET_NULL,
)
created_on = models.DateTimeField(auto_now_add=True)
company = models.ForeignKey(
Company,
related_name="marketing_contacts_emails_campaign_company",
on_delete=models.CASCADE,
null=True,
)
def created_on_arrow(self):
return arrow.get(self.created_on).humanize()
@ -483,6 +525,11 @@ class BlockedDomain(models.Model):
created_by = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, blank=True
)
company = models.ForeignKey(
Company,
on_delete=models.CASCADE,
null=True,
)
def __str__(self):
return self.domain
@ -504,6 +551,12 @@ class BlockedEmail(models.Model):
created_by = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, blank=True
)
company = models.ForeignKey(
Company,
on_delete=models.CASCADE,
null=True,
)
def __str__(self):
return self.email

0
marketing/serializer.py Normal file
View file

View file

@ -75,10 +75,10 @@ TIMEZONE_CHOICES = [(tz, tz) for tz in pytz.common_timezones]
@marketing_access_required
def dashboard(request):
if request.user.role == "ADMIN" or request.user.is_superuser:
email_templates = EmailTemplate.objects.all()
contacts = Contact.objects.all()
campaign = Campaign.objects.all()
contacts_list = ContactList.objects.all()
email_templates = EmailTemplate.objects.filter(company=request.company)
contacts = Contact.objects.filter(company=request.company)
campaign = Campaign.objects.filter(company=request.company)
contacts_list = ContactList.objects.filter(company=request.company)
else:
email_templates = EmailTemplate.objects.filter(created_by=request.user)
contacts = Contact.objects.filter(created_by=request.user)
@ -125,7 +125,7 @@ def dashboard(request):
def contact_lists(request):
tags = Tag.objects.all()
if request.user.role == "ADMIN":
queryset = ContactList.objects.all().order_by("-created_on")
queryset = ContactList.objects.filter(company=request.company).order_by("-created_on")
else:
queryset = ContactList.objects.filter(
Q(created_by=request.user) | Q(visible_to=request.user)
@ -170,8 +170,8 @@ def contact_lists(request):
@marketing_access_required
def contacts_list(request):
if request.user.role == "ADMIN":
contacts = Contact.objects.all()
contact_lists = ContactList.objects.all()
contacts = Contact.objects.filter(company=request.company)
contact_lists = ContactList.objects.filter(company=request.company)
else:
contact_ids = request.user.marketing_contactlist.all().values_list(
"contacts", flat=True
@ -216,6 +216,7 @@ def contact_list_new(request):
if form.is_valid():
instance = form.save(commit=False)
instance.created_by = request.user
instance.company = request.company
instance.save()
tags = request.POST["tags"].split(",") if request.POST["tags"] else []
for each in tags:
@ -250,7 +251,7 @@ def edit_contact_list(request, pk):
if request.method == "GET":
# contact_lists = ContactList.objects.filter(company=request.company)
if user.is_superuser or request.user.role == "ADMIN":
contact_lists = ContactList.objects.all()
contact_lists = ContactList.objects.filter(company=request.company)
else:
contact_lists = ContactList.objects.filter(created_by=request.user)
@ -377,6 +378,7 @@ def edit_contact(request, pk):
if updated_contact.is_valid():
updated_contact_obj = updated_contact.save()
updated_contact_obj.created_by = request.user
updated_contact_obj.company = request.company
updated_contact_obj.save()
updated_contact_obj.contact_list.add(contact_list_obj)
else:
@ -561,7 +563,7 @@ def failed_contact_list_download_delete(request, pk):
def email_template_list(request):
# users = User.objects.all()
if request.user.role == "ADMIN" or request.user.is_superuser:
queryset = EmailTemplate.objects.all().order_by("-created_on")
queryset = EmailTemplate.objects.filter(company=request.company).order_by("-created_on")
else:
queryset = EmailTemplate.objects.filter(created_by=request.user).order_by(
"-created_on"
@ -590,6 +592,7 @@ def email_template_new(request):
if form.is_valid():
instance = form.save(commit=False)
instance.created_by = request.user
instance.company = request.user.company
instance.save()
return JsonResponse(
{"error": False, "data": form.data}, status=status.HTTP_201_CREATED
@ -665,7 +668,7 @@ def email_template_delete(request, pk):
def campaign_list(request):
# users = User.objects.all()
if request.user.role == "ADMIN":
queryset = Campaign.objects.all()
queryset = Campaign.objects.filter(company=request.company)
else:
queryset = Campaign.objects.all().filter(created_by=request.user)
users = User.objects.filter(id__in=queryset.values_list("created_by_id", flat=True))
@ -692,11 +695,11 @@ def campaign_list(request):
def campaign_new(request):
if request.method == "GET":
if request.user.role == "ADMIN" or request.user.is_superuser:
email_templates = EmailTemplate.objects.all()
email_templates = EmailTemplate.objects.all(company=request.company)
else:
email_templates = EmailTemplate.objects.filter(created_by=request.user)
if request.user.role == "ADMIN" or request.user.is_superuser:
contact_lists = ContactList.objects.all()
contact_lists = ContactList.objects.filter(company=request.company)
else:
contact_lists = ContactList.objects.filter(created_by=request.user)
# if request.GET.get('contact_list'):
@ -720,6 +723,7 @@ def campaign_new(request):
if form.is_valid():
instance = form.save(commit=False)
instance.created_by = request.user
instance.company = request.company
if request.POST.get("from_email"):
instance.from_email = request.POST["from_email"]
if request.POST.get("from_name"):
@ -831,7 +835,7 @@ def campaign_details(request, pk):
raise PermissionDenied
contact_lists = campaign.contact_lists.all()
if request.user.role == "ADMIN" or request.user.is_superuser:
contact_lists = contact_lists
contact_lists = contact_lists.filter(company=request.company)
else:
# contact_lists = contact_lists.filter(
# is_public=True, created_by=request.user)
@ -1385,7 +1389,7 @@ def download_failed_contacts(request, contact_list_id):
def list_all_emails_for_campaigns(request):
context = {}
if request.method == "GET":
queryset = ContactEmailCampaign.objects.all()
queryset = ContactEmailCampaign.objects.filter(company=request.company)
context["contacts"] = queryset
return render(request, "email_for_campaigns_list.html", context)
@ -1403,6 +1407,7 @@ def add_email_for_campaigns(request):
if form.is_valid():
obj = form.save(commit=False)
obj.created_by = request.user
obj.company = request.company
obj.save()
return JsonResponse(
{"error": False, "success_url": reverse("common:api_settings")}
@ -1457,6 +1462,7 @@ def add_blocked_domain(request):
if form.is_valid():
domain = form.save(commit=False)
domain.created_by = request.user
domain.company = request.company
domain.save()
return JsonResponse(
{"error": False, "success_url": reverse("common:api_settings")}
@ -1529,6 +1535,7 @@ def add_blocked_email(request):
if form.is_valid():
email = form.save(commit=False)
email.created_by = request.user
email.company = request.company
email.save()
return JsonResponse(
{"error": False, "success_url": reverse("common:api_settings")}
@ -1595,13 +1602,13 @@ def delete_blocked_email(request, blocked_email_id):
def contacts_list_elastic_search(request):
search = False
if request.user.role == "ADMIN":
contacts = Contact.objects.filter(is_bounced=False)
contacts = Contact.objects.filter(is_bounced=False, company=request.company)
# contacts = SearchQuerySet().filter(is_bounced='false').models(Contact)
# bounced_contacts = SearchQuerySet().filter(is_bounced='true').models(Contact)
# failed_contacts = SearchQuerySet().models(FailedContact).filter()
bounced_contacts = Contact.objects.filter(is_bounced=True)
failed_contacts = FailedContact.objects.all()
contact_lists = ContactList.objects.all()
bounced_contacts = Contact.objects.filter(is_bounced=True, company=request.company)
failed_contacts = FailedContact.objects.filter(company=request.company)
contact_lists = ContactList.objects.filter(company=request.company)
else:
contact_ids = request.user.marketing_contactlist.all().values_list(
"contacts", flat=True

39
opportunity/serializer.py Normal file
View file

@ -0,0 +1,39 @@
from rest_framework import serializers
from opportunity.models import Opportunity
from accounts.serializer import AccountSerializer
from common.serializer import UserSerializer
class OpportunitySerializer(serializers.ModelSerializer):
account = AccountSerializer()
closed_by = UserSerializer()
created_by = UserSerializer()
class Meta:
model = Opportunity
# fields = ‘__all__’
fields = (
'id',
"name",
"account",
"stage",
"currency",
"amount",
"lead_source",
"probability",
"contacts",
"closed_by",
"closed_on",
"description",
"assigned_to",
"created_by",
"created_on",
"is_active",
"tags",
"teams",
"company",
"created_on_arrow",
# "get_team_users",
# "get_team_and_assigned_users",
# "get_assigned_users_not_in_teams",
)

View file

@ -34,12 +34,12 @@ from teams.models import Teams
@login_required
def get_teams_and_users(request):
data = {}
teams = Teams.objects.all()
teams = Teams.objects.filter(company=request.company)
teams_data = [
{"team": team.id, "users": [user.id for user in team.users.all()]}
for team in teams
]
users = User.objects.all().values_list("id", flat=True)
users = User.objects.filter(company=request.company).values_list("id", flat=True)
data["teams"] = teams_data
data["users"] = list(users)
return JsonResponse(data)

0
planner/serializer.py Normal file
View file

View file

@ -1,6 +1,5 @@
amqp==2.6.0
arrow==0.14.5
asgiref==3.2.7
atomicwrites==1.3.0
attrs==19.3.0
Babel==2.8.0
@ -14,7 +13,7 @@ chardet==3.0.4
codacy-coverage==1.3.11
coverage==5.1
cssselect==1.1.0
Django==3.0.8
Django==3.1.0
django-appconf==1.0.4
django-compressor==2.4
django-haystack==2.8.1
@ -23,8 +22,10 @@ django-phonenumber-field==4.0.0
django-settings-export==1.2.1
django-simple-pagination==1.3
django-storages==1.9.1
django-ses==1.0.2
docutils==0.15.2
elasticsearch==7.7.1
elastic-apm==5.8.1
et-xmlfile==1.0.1
idna==2.9
importlib-metadata==1.6.0
@ -66,3 +67,8 @@ whitenoise==5.1.0
xlrd==1.2.0
xlwt==1.3.0
zipp==3.1.0
drf-yasg==1.17.1
djangorestframework==3.11.0
django-cors-headers==3.4.0
djangorestframework-jwt==1.11.0
sentry-sdk==0.14.4

View file

@ -1,7 +1,7 @@
import os
from setuptools import setup, find_packages
with open(os.path.join(os.path.dirname(__file__), "README.md")) as readme:
with open(os.path.join(os.path.dirname(__file__), "README.rst")) as readme:
README = readme.read()
# allow setup.py to be run from any path
@ -21,12 +21,11 @@ for dirpath, dirnames, filenames in os.walk(PROJECT_NAME):
setup(
name="django-crm",
version="0.8.0",
version="0.7.0",
packages=find_packages(exclude=["tests", "tests.*"]),
include_package_data=True,
description="An opensourse CRM developed on django framework",
long_description=README,
long_description_content_type="text/markdown",
url="https://github.com/MicroPyramid/Django-CRM.git",
author="Micropyramid",
author_email="hello@micropyramid.com",

View file

@ -1591,3 +1591,17 @@ pre {
background: #28a745;
border-color: #35a745;
}
#dashboard{
font-weight: 500;
font-size: 0.9rem;
padding: 10px 15px;
display: inline-block;
padding-top: 16px;
color: #4f616a!important;
border-bottom: 3px solid #099CBE;
float: right;
}
.intl-tel-input{
display: block!important;
}

View file

@ -4,4 +4,6 @@ sudo apt-get install -y curl wget libpq-dev python3-dev gem ruby ruby-dev build-
sudo gem install sass
sudo apt-get install wkhtmltopdf
sudo apt-get install wkhtmltopdf
sudo apt-get install postfix

10
tasks/serializer.py Normal file
View file

@ -0,0 +1,10 @@
from rest_framework import serializers
from tasks.models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = "__all__"

22
teams/serializer.py Normal file
View file

@ -0,0 +1,22 @@
from teams.models import Teams
from rest_framework import serializers
from common.serializer import UserSerializer, CompanySerializer
class TeamsSerializer(serializers.ModelSerializer):
users = UserSerializer(read_only=True, many=True)
created_by = UserSerializer()
company = CompanySerializer()
class Meta:
model = Teams
fields = (
"id",
"name",
"description",
"users",
"created_on",
"created_by",
"company",
"created_on_arrow"
)

View file

@ -69,11 +69,7 @@
</ul>
</div>
{% else %}
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto my-2 my-lg-0">
<li class="nav-item"><a class="nav-link" id="dashboard" href="{% url 'common:dashboard' %}">Login</a></li>
</ul>
</div>
<a class="nav-link active ml-auto" id="dashboard" href="/dashboard/">Login</a>
{% endif %}
</nav>
</header>

View file

@ -7,7 +7,7 @@
<div class="login_block">
<div class="login_form_block">
<div class="welcome">{{ APPLICATION_NAME }}</div>
<form method="POST" action="">
<form method="POST" action="{% url 'common:check_domain' %}">
<div class="form-group">
<input type="text" class="form-control" id="id_sub_domain" placeholder="subdomain.bottle.crm"
name="sub_domain" value="{{request.POST.sub_domain}}">

View file

@ -9,7 +9,7 @@
<div class="welcome">{{ settings.APPLICATION_NAME }}</div>
<form method="POST" action="">
<div class="form-group">
<input type="text" class="form-control" id="id_sub_domain" placeholder="Sub-domain.bottle.crm" name="sub_domain" value="{{request.POST.sub_domain}}">
<input type="text" class="form-control" id="id_sub_domain" placeholder="Sub-domain" name="sub_domain" value="{{request.POST.sub_domain}}">
<span class="error">{{form.sub_domain.errors}}</span>
</div>
<div class="form-group">

View file

@ -168,7 +168,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-tagsinput/1.3.6/jquery.tagsinput.min.js" integrity="sha256-IkytFcY/Z/rLvRE9rYyZvybaitAWr1y2jS/+eyxXky8=" crossorigin="anonymous"></script>
<script src="https://malsup.github.io/jquery.form.js"></script>
<script src="http://cdn.ckeditor.com/4.5.4/basic/ckeditor.js"></script>
<script src="https://cdn.ckeditor.com/4.5.4/basic/ckeditor.js"></script>
<script src="{% static 'js/bootstrap-suggest.js' %}"></script>