new api (#413)
* changing to SaaS * saas beta * restructuring * restruturing * restruturing * restruturing * restruturing * restruturing * added new api
This commit is contained in:
parent
0957ee8cda
commit
87c79bcd2b
67 changed files with 3070 additions and 183 deletions
|
|
@ -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
12
accounts/api_urls.py
Normal 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
500
accounts/api_views.py
Normal 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)
|
||||
|
|
@ -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
85
accounts/serializer.py
Normal 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"
|
||||
)
|
||||
66
accounts/swagger_params.py
Normal file
66
accounts/swagger_params.py
Normal 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,
|
||||
]
|
||||
|
|
@ -358,6 +358,7 @@
|
|||
$('#submit_btn').removeAttr('disabled')
|
||||
}
|
||||
else {
|
||||
alert(data.message)
|
||||
window.location = data.success_url;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
43
cases/serializer.py
Normal 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
24
common/api_urls.py
Normal 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
564
common/api_views.py
Normal 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
119
common/custom_auth.py
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
206
common/serializer.py
Normal 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
84
common/swagger_params.py
Normal 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),
|
||||
]
|
||||
|
|
@ -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()
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
21
common/templates/password_reset_email.html
Normal file
21
common/templates/password_reset_email.html
Normal 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 %}
|
||||
|
|
@ -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 ""
|
||||
|
|
|
|||
|
|
@ -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
12
contacts/api_urls.py
Normal 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
390
contacts/api_views.py
Normal 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})
|
||||
|
|
@ -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
51
contacts/serializer.py
Normal 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",
|
||||
)
|
||||
31
contacts/swagger_params.py
Normal file
31
contacts/swagger_params.py
Normal 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),
|
||||
]
|
||||
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
129
crm/settings.py
129
crm/settings.py
|
|
@ -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
|
||||
|
|
|
|||
26
crm/urls.py
26
crm/urls.py
|
|
@ -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")),
|
||||
|
|
|
|||
|
|
@ -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
8
emails/serializer.py
Normal 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
31
env.md
|
|
@ -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
0
events/serializer.py
Normal 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"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
18
invoices/migrations/0010_invoice_tax.py
Normal file
18
invoices/migrations/0010_invoice_tax.py
Normal 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),
|
||||
),
|
||||
]
|
||||
|
|
@ -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
8
invoices/serializer.py
Normal 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__"
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
61
leads/serializer.py
Normal 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",
|
||||
)
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
20
marketing/migrations/0014_emailtemplate_company.py
Normal file
20
marketing/migrations/0014_emailtemplate_company.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
55
marketing/migrations/0015_auto_20200912_0301.py
Normal file
55
marketing/migrations/0015_auto_20200912_0301.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
|
|
@ -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
0
marketing/serializer.py
Normal 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
39
opportunity/serializer.py
Normal 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",
|
||||
)
|
||||
|
|
@ -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
0
planner/serializer.py
Normal 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
|
||||
|
|
|
|||
5
setup.py
5
setup.py
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
10
tasks/serializer.py
Normal 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
22
teams/serializer.py
Normal 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"
|
||||
)
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}}">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue