From 67834d65812470a0d54c3e49b24d85d1d34b1617 Mon Sep 17 00:00:00 2001 From: KingCSharp Date: Wed, 6 Oct 2021 15:52:56 +0530 Subject: [PATCH] new release (#450) --- .gitignore | 4 +- .../migrations/0013_auto_20210913_1918.py | 30 + .../0014_rename_company_account_org.py | 18 + accounts/migrations/0015_alter_account_org.py | 20 + accounts/models.py | 15 +- accounts/serializer.py | 13 +- accounts/swagger_params.py | 14 + accounts/tasks.py | 16 +- accounts/urls.py | 2 +- accounts/views.py | 199 +++--- cases/migrations/0008_auto_20210913_1918.py | 30 + .../0009_rename_company_case_org.py | 18 + cases/migrations/0010_alter_case_org.py | 20 + cases/models.py | 18 +- cases/serializer.py | 14 +- cases/swagger_params.py | 11 + cases/tasks.py | 12 +- cases/views.py | 204 +++--- common/custom_auth.py | 22 +- common/middleware/get_company.py | 36 ++ common/migrations/0028_user_phone.py | 19 + common/migrations/0029_auto_20210730_1328.py | 58 ++ common/migrations/0030_alter_user_role.py | 18 + common/migrations/0031_auto_20210805_1214.py | 113 ++++ .../migrations/0032_remove_user_user_type.py | 17 + .../0033_alter_user_alternate_email.py | 18 + common/migrations/0034_auto_20210913_1918.py | 181 ++++++ .../0035_remove_company_sub_domain.py | 17 + common/migrations/0036_auto_20210922_1801.py | 45 ++ common/migrations/0037_alter_profile_org.py | 19 + common/models.py | 221 ++++--- common/serializer.py | 279 ++++---- common/swagger_params.py | 86 ++- common/tasks.py | 50 +- common/urls.py | 2 + common/utils.py | 8 +- common/views.py | 603 +++++++++++------- .../migrations/0007_auto_20210716_1554.py | 84 +++ .../migrations/0008_auto_20210913_1918.py | 30 + .../0009_rename_company_contact_org.py | 18 + .../migrations/0010_auto_20211006_1251.py | 33 + contacts/models.py | 32 +- contacts/serializer.py | 52 +- contacts/swagger_params.py | 56 +- contacts/tasks.py | 12 +- contacts/views.py | 153 +++-- crm/server_settings.py | 4 +- crm/settings.py | 25 +- crm/urls.py | 8 +- emails/tests.py | 8 +- env.md | 3 + events/migrations/0006_auto_20210913_1918.py | 30 + .../0007_rename_company_event_org.py | 18 + events/models.py | 15 +- events/serializer.py | 15 +- events/swagger_params.py | 12 + events/tasks.py | 12 +- events/views.py | 215 ++++--- installation.md | 56 ++ invoices/api_views.py | 5 +- .../0011_rename_company_invoice_org.py | 18 + invoices/models.py | 6 +- invoices/serializer.py | 14 +- invoices/swagger_params.py | 2 +- leads/migrations/0015_auto_20210913_1918.py | 30 + .../0016_rename_company_lead_org.py | 18 + leads/migrations/0017_alter_lead_org.py | 20 + leads/models.py | 16 +- leads/serializer.py | 17 +- leads/swagger_params.py | 13 + leads/tasks.py | 14 +- leads/views.py | 223 ++++--- .../migrations/0007_auto_20210913_1918.py | 35 + .../0008_rename_company_opportunity_org.py | 18 + .../migrations/0009_auto_20211006_1251.py | 25 + opportunity/models.py | 17 +- opportunity/serializer.py | 14 +- opportunity/swagger_params.py | 12 + opportunity/tasks.py | 10 +- opportunity/views.py | 156 +++-- requirements.txt | 45 +- tasks/migrations/0007_auto_20210913_1918.py | 30 + .../0008_rename_company_task_org.py | 18 + tasks/migrations/0009_alter_task_org.py | 20 + tasks/models.py | 13 +- tasks/serializer.py | 6 +- tasks/swagger_params.py | 11 + tasks/views.py | 154 ++--- teams/migrations/0006_auto_20210913_1918.py | 30 + .../0007_rename_company_teams_org.py | 18 + teams/models.py | 9 +- teams/serializer.py | 10 +- teams/swagger_params.py | 9 + teams/views.py | 38 +- .../registration/password_reset_email.html | 8 +- 95 files changed, 3201 insertions(+), 1262 deletions(-) create mode 100644 accounts/migrations/0013_auto_20210913_1918.py create mode 100644 accounts/migrations/0014_rename_company_account_org.py create mode 100644 accounts/migrations/0015_alter_account_org.py create mode 100644 cases/migrations/0008_auto_20210913_1918.py create mode 100644 cases/migrations/0009_rename_company_case_org.py create mode 100644 cases/migrations/0010_alter_case_org.py create mode 100644 common/middleware/get_company.py create mode 100644 common/migrations/0028_user_phone.py create mode 100644 common/migrations/0029_auto_20210730_1328.py create mode 100644 common/migrations/0030_alter_user_role.py create mode 100644 common/migrations/0031_auto_20210805_1214.py create mode 100644 common/migrations/0032_remove_user_user_type.py create mode 100644 common/migrations/0033_alter_user_alternate_email.py create mode 100644 common/migrations/0034_auto_20210913_1918.py create mode 100644 common/migrations/0035_remove_company_sub_domain.py create mode 100644 common/migrations/0036_auto_20210922_1801.py create mode 100644 common/migrations/0037_alter_profile_org.py create mode 100644 contacts/migrations/0007_auto_20210716_1554.py create mode 100644 contacts/migrations/0008_auto_20210913_1918.py create mode 100644 contacts/migrations/0009_rename_company_contact_org.py create mode 100644 contacts/migrations/0010_auto_20211006_1251.py create mode 100644 events/migrations/0006_auto_20210913_1918.py create mode 100644 events/migrations/0007_rename_company_event_org.py create mode 100644 installation.md create mode 100644 invoices/migrations/0011_rename_company_invoice_org.py create mode 100644 leads/migrations/0015_auto_20210913_1918.py create mode 100644 leads/migrations/0016_rename_company_lead_org.py create mode 100644 leads/migrations/0017_alter_lead_org.py create mode 100644 opportunity/migrations/0007_auto_20210913_1918.py create mode 100644 opportunity/migrations/0008_rename_company_opportunity_org.py create mode 100644 opportunity/migrations/0009_auto_20211006_1251.py create mode 100644 tasks/migrations/0007_auto_20210913_1918.py create mode 100644 tasks/migrations/0008_rename_company_task_org.py create mode 100644 tasks/migrations/0009_alter_task_org.py create mode 100644 teams/migrations/0006_auto_20210913_1918.py create mode 100644 teams/migrations/0007_rename_company_teams_org.py diff --git a/.gitignore b/.gitignore index abae020..3a3cfe2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -*~ *.pyc +*~ *.swo *.swp db.sqlite3 @@ -23,3 +23,5 @@ local_settings.py media .sass-cache server.log +.vscode +static \ No newline at end of file diff --git a/accounts/migrations/0013_auto_20210913_1918.py b/accounts/migrations/0013_auto_20210913_1918.py new file mode 100644 index 0000000..f880fa9 --- /dev/null +++ b/accounts/migrations/0013_auto_20210913_1918.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2 on 2021-09-13 13:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0034_auto_20210913_1918'), + ('accounts', '0012_remove_account_company'), + ] + + operations = [ + migrations.AddField( + model_name='account', + name='company', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.company'), + ), + migrations.AlterField( + model_name='account', + name='assigned_to', + field=models.ManyToManyField(related_name='account_assigned_users', to='common.Profile'), + ), + migrations.AlterField( + model_name='account', + name='created_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='account_created_by', to='common.profile'), + ), + ] diff --git a/accounts/migrations/0014_rename_company_account_org.py b/accounts/migrations/0014_rename_company_account_org.py new file mode 100644 index 0000000..d1a2746 --- /dev/null +++ b/accounts/migrations/0014_rename_company_account_org.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-09-22 12:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0013_auto_20210913_1918'), + ] + + operations = [ + migrations.RenameField( + model_name='account', + old_name='company', + new_name='org', + ), + ] diff --git a/accounts/migrations/0015_alter_account_org.py b/accounts/migrations/0015_alter_account_org.py new file mode 100644 index 0000000..f0aa166 --- /dev/null +++ b/accounts/migrations/0015_alter_account_org.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.7 on 2021-10-06 07:21 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0037_alter_profile_org'), + ('accounts', '0014_rename_company_account_org'), + ] + + operations = [ + migrations.AlterField( + model_name='account', + name='org', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='account_org', to='common.org'), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 1f30a93..ab02598 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -3,7 +3,7 @@ from django.utils.translation import pgettext_lazy from django.utils.translation import ugettext_lazy as _ -from common.models import User +from common.models import Org, Profile from common.utils import INDCHOICES, COUNTRIES from phonenumber_field.modelfields import PhoneNumberField from django.utils.text import slugify @@ -50,7 +50,7 @@ class Account(models.Model): website = models.URLField(_("Website"), blank=True, null=True) description = models.TextField(blank=True, null=True) created_by = models.ForeignKey( - User, related_name="account_created_by", on_delete=models.SET_NULL, null=True + Profile, related_name="account_created_by", on_delete=models.SET_NULL, null=True ) created_on = models.DateTimeField(_("Created on"), auto_now_add=True) is_active = models.BooleanField(default=False) @@ -67,8 +67,11 @@ class Account(models.Model): contacts = models.ManyToManyField( "contacts.Contact", related_name="account_contacts" ) - assigned_to = models.ManyToManyField(User, related_name="account_assigned_users") + assigned_to = models.ManyToManyField(Profile, related_name="account_assigned_users") teams = models.ManyToManyField(Teams, related_name="account_teams") + org = models.ForeignKey( + Org, on_delete=models.SET_NULL, null=True, blank=True, related_name="account_org" + ) class Meta: ordering = ["-created_on"] @@ -102,21 +105,21 @@ def contact_values(self): @property def get_team_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) - return User.objects.filter(id__in=team_user_ids) + return Profile.objects.filter(id__in=team_user_ids) @property def get_team_and_assigned_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = team_user_ids + assigned_user_ids - return User.objects.filter(id__in=user_ids) + return Profile.objects.filter(id__in=user_ids) @property def get_assigned_users_not_in_teams(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = set(assigned_user_ids) - set(team_user_ids) - return User.objects.filter(id__in=list(user_ids)) + return Profile.objects.filter(id__in=list(user_ids)) class Email(models.Model): diff --git a/accounts/serializer.py b/accounts/serializer.py index 483bee4..a7fa74c 100644 --- a/accounts/serializer.py +++ b/accounts/serializer.py @@ -1,6 +1,6 @@ from rest_framework import serializers from accounts.models import Account, Email, Tags -from common.serializer import UserSerializer, AttachmentsSerializer +from common.serializer import ProfileSerializer, AttachmentsSerializer, OrganizationSerializer from leads.serializer import LeadSerializer from teams.serializer import TeamsSerializer from contacts.serializer import ContactSerializer @@ -13,10 +13,11 @@ class Meta: class AccountSerializer(serializers.ModelSerializer): - created_by = UserSerializer() + created_by = ProfileSerializer() lead = LeadSerializer() + org = OrganizationSerializer() tags = TagsSerailizer(read_only=True, many=True) - assigned_to = UserSerializer(read_only=True, many=True) + assigned_to = ProfileSerializer(read_only=True, many=True) contacts = ContactSerializer(read_only=True, many=True) teams = TeamsSerializer(read_only=True, many=True) account_attachment = AttachmentsSerializer(read_only=True, many=True) @@ -49,6 +50,7 @@ class Meta: "contacts", "assigned_to", "teams", + "org" ) @@ -113,17 +115,18 @@ def __init__(self, *args, **kwargs): if self.instance: self.fields["lead"].required = False self.fields["lead"].required = False + self.org = request_obj.org def validate_name(self, name): if self.instance: if self.instance.name != name: - if not Account.objects.filter(name__iexact=name).exists(): + if not Account.objects.filter(name__iexact=name, org=self.org).exists(): return name raise serializers.ValidationError( "Account already exists with this name" ) return name - if not Account.objects.filter(name__iexact=name).exists(): + if not Account.objects.filter(name__iexact=name, org=self.org).exists(): return name raise serializers.ValidationError("Account already exists with this name") diff --git a/accounts/swagger_params.py b/accounts/swagger_params.py index 32b8dbb..afed867 100644 --- a/accounts/swagger_params.py +++ b/accounts/swagger_params.py @@ -1,12 +1,21 @@ from drf_yasg import openapi +organization_params_in_header = openapi.Parameter( + 'org', openapi.IN_HEADER, required=True, type=openapi.TYPE_INTEGER) + +organization_params = [ + organization_params_in_header, +] + account_get_params = [ + organization_params_in_header, openapi.Parameter("name", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("city", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("tags", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] account_post_params = [ + organization_params_in_header, openapi.Parameter( "name", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING ), @@ -57,6 +66,7 @@ ] account_detail_get_params = [ + organization_params_in_header, openapi.Parameter( "account_attachment", openapi.IN_QUERY, @@ -66,10 +76,12 @@ ] account_comment_edit_params = [ + organization_params_in_header, openapi.Parameter("comment", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] account_detail_get_params = [ + organization_params_in_header, openapi.Parameter( "account_attachment", openapi.IN_QUERY, @@ -79,10 +91,12 @@ ] account_comment_edit_params = [ + organization_params_in_header, openapi.Parameter("comment", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] account_mail_params = [ + organization_params_in_header, openapi.Parameter("from_email", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("recipients", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("message_subject", openapi.IN_QUERY, type=openapi.TYPE_STRING), diff --git a/accounts/tasks.py b/accounts/tasks.py index aebcd75..ead525d 100644 --- a/accounts/tasks.py +++ b/accounts/tasks.py @@ -9,7 +9,7 @@ from django.template.loader import render_to_string from accounts.models import Account, Email, EmailLog -from common.models import User +from common.models import User, Profile from common.utils import convert_to_custom_timezone app = Celery("redis://") @@ -27,7 +27,7 @@ def send_email(email_obj_id): ).exists(): html = email_obj.message_body context_data = { - "email": contact_obj.email if contact_obj.email else "", + "email": contact_obj.primary_email if contact_obj.primary_email else "", "name": contact_obj.first_name if contact_obj.first_name else "" + " " + contact_obj.last_name @@ -42,7 +42,7 @@ def send_email(email_obj_id): html_content, from_email=from_email, to=[ - contact_obj.email, + contact_obj.primary_email, ], ) msg.content_subtype = "html" @@ -65,14 +65,14 @@ def send_email_to_assigned_user( account = Account.objects.filter(id=from_email).first() created_by = account.created_by - for user in recipients: + for profile_id in recipients: recipients_list = [] - user = User.objects.filter(id=user, is_active=True).first() - if user: - recipients_list.append(user.email) + profile = Profile.objects.filter(id=profile_id, is_active=True).first() + if profile: + recipients_list.append(profile.user.email) context = {} context["url"] = protocol + "://" + domain - context["user"] = user + context["user"] = profile.user context["account"] = account context["created_by"] = created_by subject = "Assigned a account for you." diff --git a/accounts/urls.py b/accounts/urls.py index d463402..d7e1bb5 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -6,7 +6,7 @@ urlpatterns = [ path("", views.AccountsListView.as_view()), path("/", views.AccountDetailView.as_view()), - path("/create_mail", views.AccountCreateMailView.as_view()), + path("/create_mail/", views.AccountCreateMailView.as_view()), path("comment//", views.AccountCommentView.as_view()), path("attachment//", views.AccountAttachmentView.as_view()), ] diff --git a/accounts/views.py b/accounts/views.py index 2bcc461..3f736ae 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -11,13 +11,13 @@ TagsSerailizer, AccountCreateSerializer, ) -from common.models import User, Attachments, Comment +from common.models import Profile, Attachments, Comment from common.utils import ( COUNTRIES, INDCHOICES, ) from common.custom_auth import JSONWebTokenAuthentication -from common.serializer import UserSerializer, CommentSerializer, AttachmentsSerializer +from common.serializer import ProfileSerializer, CommentSerializer, AttachmentsSerializer from common.utils import ( CASE_TYPE, COUNTRIES, @@ -48,7 +48,6 @@ from rest_framework.pagination import LimitOffsetPagination from drf_yasg.utils import swagger_auto_schema import json -from django.conf import settings class AccountsListView(APIView, LimitOffsetPagination): @@ -63,17 +62,19 @@ def get_context_data(self, **kwargs): if len(self.request.data) == 0 else self.request.data ) - queryset = self.model.objects.all() - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + queryset = self.model.objects.filter(org=self.request.org) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: queryset = queryset.filter( - Q(created_by=self.request.user) | Q(assigned_to=self.request.user) + Q(created_by=self.request.profile) | Q( + assigned_to=self.request.profile) ).distinct() if params: request_post = params if request_post: if request_post.get("name"): - queryset = queryset.filter(name__icontains=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") @@ -101,7 +102,8 @@ def get_context_data(self, **kwargs): results_accounts_open = self.paginate_queryset( queryset_open.distinct(), self.request, view=self ) - accounts_open = AccountSerializer(results_accounts_open, many=True).data + accounts_open = AccountSerializer( + results_accounts_open, many=True).data context["per_page"] = 10 context["active_accounts"] = { "accounts_count": self.count, @@ -115,7 +117,8 @@ def get_context_data(self, **kwargs): results_accounts_close = self.paginate_queryset( queryset_close.distinct(), self.request, view=self ) - accounts_close = AccountSerializer(results_accounts_close, many=True).data + accounts_close = AccountSerializer( + results_accounts_close, many=True).data context["closed_accounts"] = { "accounts_count": self.count, @@ -125,12 +128,14 @@ def get_context_data(self, **kwargs): "close_accounts": accounts_close, } - context["users"] = UserSerializer( - User.objects.filter(is_active=True).order_by("email"), + context["users"] = ProfileSerializer( + Profile.objects.filter(is_active=True, + org=self.request.org).order_by("user__email"), many=True, ).data context["industries"] = INDCHOICES - tag_ids = Account.objects.all().values_list("tags", flat=True).distinct() + tag_ids = Account.objects.filter( + org=self.request.org).values_list("tags", flat=True).distinct() context["tags"] = TagsSerailizer( Tags.objects.filter(id__in=tag_ids), many=True ).data @@ -155,14 +160,16 @@ def get(self, request, *args, **kwargs): tags=["Accounts"], manual_parameters=swagger_params.account_post_params ) def post(self, request, *args, **kwargs): - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data data = {} serializer = AccountCreateSerializer( data=params, request_obj=request, account=True ) # Save Account if serializer.is_valid(): - account_object = serializer.save(created_by=request.user) + account_object = serializer.save( + created_by=request.profile, org=request.org) if params.get("contacts"): contacts = json.loads(params.get("contacts")) for contact in contacts: @@ -185,11 +192,13 @@ def post(self, request, *args, **kwargs): else: tag_obj = Tags.objects.create(name=tag) account_object.tags.add(tag_obj) - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter( + id=team, org=request.org + ) if teams_ids.exists(): account_object.teams.add(team) else: @@ -200,10 +209,12 @@ def post(self, request, *args, **kwargs): status=status.HTTP_400_BAD_REQUEST, ) if params.get("assigned_to"): - assinged_to_users_ids = json.loads(params.get("assigned_to")) + assinged_to_users_ids = json.loads( + params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter( + id=user_id, org=request.org) if user.exists(): account_object.assigned_to.add(user_id) else: @@ -216,8 +227,9 @@ def post(self, request, *args, **kwargs): if self.request.FILES.get("account_attachment"): attachment = Attachments() - attachment.created_by = request.user - attachment.file_name = request.FILES.get("account_attachment").name + attachment.created_by = request.profile + attachment.file_name = request.FILES.get( + "account_attachment").name attachment.account = account_object attachment.attachment = request.FILES.get("account_attachment") attachment.save() @@ -227,10 +239,11 @@ def post(self, request, *args, **kwargs): ) recipients = assigned_to_list + current_site = get_current_site(self.request) send_email_to_assigned_user.delay( recipients, account_object.id, - domain=settings.Domain, + domain=current_site.domain, protocol=self.request.scheme, ) return Response( @@ -254,18 +267,24 @@ def get_object(self, pk): tags=["Accounts"], manual_parameters=swagger_params.account_post_params ) def put(self, request, pk, format=None): - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data account_object = self.get_object(pk=pk) data = {} + if account_object.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_404_NOT_FOUND, + ) serializer = AccountCreateSerializer( account_object, data=params, request_obj=request, account=True ) if serializer.is_valid(): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: if not ( - (self.request.user == account_object.created_by) - or (self.request.user in account_object.assigned_to.all()) + (self.request.profile == account_object.created_by) + or (self.request.profile in account_object.assigned_to.all()) ): return Response( { @@ -282,7 +301,9 @@ def put(self, request, pk, format=None): if params.get("contacts"): contacts = json.loads(params.get("contacts")) for contact in contacts: - obj_contact = Contact.objects.filter(id=contact) + obj_contact = Contact.objects.filter( + id=contact, org=request.org + ) if obj_contact.exists(): account_object.contacts.add(contact) else: @@ -302,12 +323,14 @@ def put(self, request, pk, format=None): tag_obj = Tags.objects.create(name=tag) account_object.tags.add(tag_obj) - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": account_object.teams.clear() if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter( + id=team, org=request.org + ) if teams_ids.exists(): account_object.teams.add(team) else: @@ -319,9 +342,11 @@ def put(self, request, pk, format=None): account_object.assigned_to.clear() if params.get("assigned_to"): - assinged_to_users_ids = json.loads(params.get("assigned_to")) + assinged_to_users_ids = json.loads( + params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter( + id=user_id, org=request.org) if user.exists(): account_object.assigned_to.add(user_id) else: @@ -333,20 +358,24 @@ def put(self, request, pk, format=None): 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.created_by = self.request.profile + attachment.file_name = self.request.FILES.get( + "account_attachment").name attachment.account = account_object - attachment.attachment = self.request.FILES.get("account_attachment") + attachment.attachment = self.request.FILES.get( + "account_attachment") attachment.save() assigned_to_list = list( account_object.assigned_to.all().values_list("id", flat=True) ) - recipients = list(set(assigned_to_list) - set(previous_assigned_to_users)) + recipients = list(set(assigned_to_list) - + set(previous_assigned_to_users)) + current_site = get_current_site(self.request) send_email_to_assigned_user.delay( recipients, account_object.id, - domain=settings.Domain, + domain=current_site.domain, protocol=self.request.scheme, ) return Response( @@ -358,11 +387,17 @@ def put(self, request, pk, format=None): status=status.HTTP_400_BAD_REQUEST, ) - @swagger_auto_schema(tags=["Accounts"]) + @swagger_auto_schema(tags=["Accounts"], manual_parameters=swagger_params.organization_params) def delete(self, request, pk, format=None): self.object = self.get_object(pk) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: - if self.request.user != self.object.created_by: + if self.object.org != request.org: + return Response( + {"error": True, + "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: + if self.request.profile != self.object.created_by: return Response( { "error": True, @@ -377,16 +412,21 @@ def delete(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["Accounts"], + tags=["Accounts"], manual_parameters=swagger_params.organization_params ) def get(self, request, pk, format=None): self.account = self.get_object(pk=pk) + if self.account.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_404_NOT_FOUND, + ) context = {} context["account_obj"] = AccountSerializer(self.account).data - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: if not ( - (self.request.user == self.account.created_by) - or (self.request.user in self.account.assigned_to.all()) + (self.request.profile == self.account.created_by) + or (self.request.profile in self.account.assigned_to.all()) ): return Response( { @@ -399,18 +439,20 @@ def get(self, request, pk, format=None): comment_permission = ( True if ( - self.request.user == self.account.created_by - or self.request.user.is_superuser - or self.request.user.role == "ADMIN" + self.request.profile == self.account.created_by + or self.request.profile.is_admin + or self.request.profile.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).values("username")) - elif self.request.user != self.account.created_by: + if self.request.profile.is_admin or self.request.profile.role == "ADMIN": + users_mention = list(Profile.objects.filter( + is_active=True, org=self.request.org).values("user__username")) + elif self.request.profile != self.account.created_by: if self.account.created_by: - users_mention = [{"username": self.account.created_by.username}] + users_mention = [ + {"username": self.account.created_by.user.username}] else: users_mention = [] else: @@ -429,8 +471,10 @@ def get(self, request, pk, format=None): "opportunity_list": OpportunitySerializer( Opportunity.objects.filter(account=self.account), many=True ).data, - "users": UserSerializer( - User.objects.filter(is_active=True).order_by("email"), + "users": ProfileSerializer( + Profile.objects.filter( + is_active=True, org=self.request.org).order_by( + "user__email"), many=True, ).data, "cases": CaseSerializer( @@ -469,10 +513,15 @@ def post(self, request, pk, **kwargs): ) context = {} self.account_obj = Account.objects.get(pk=pk) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.account_obj.org != request.org: + return Response( + {"error": True, "errors": "User company does not match with header...."}, + status=status.HTTP_400_BAD_REQUEST + ) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: if not ( - (self.request.user == self.account_obj.created_by) - or (self.request.user in self.account_obj.assigned_to.all()) + (self.request.profile == self.account_obj.created_by) + or (self.request.profile in self.account_obj.assigned_to.all()) ): return Response( { @@ -486,15 +535,17 @@ def post(self, request, pk, **kwargs): if params.get("comment"): comment_serializer.save( account_id=self.account_obj.id, - commented_by_id=self.request.user.id, + commented_by=self.request.profile, ) 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.created_by = self.request.profile + attachment.file_name = self.request.FILES.get( + "account_attachment").name attachment.account = self.account_obj - attachment.attachment = self.request.FILES.get("account_attachment") + attachment.attachment = self.request.FILES.get( + "account_attachment") attachment.save() comments = Comment.objects.filter(account__id=self.account_obj.id).order_by( @@ -525,12 +576,13 @@ def get_object(self, pk): tags=["Accounts"], manual_parameters=swagger_params.account_comment_edit_params ) def put(self, request, pk, format=None): - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data obj = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == obj.commented_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == obj.commented_by ): serializer = CommentSerializer(obj, data=params) if params.get("comment"): @@ -553,13 +605,13 @@ def put(self, request, pk, format=None): status=status.HTTP_403_FORBIDDEN, ) - @swagger_auto_schema(tags=["Accounts"]) + @swagger_auto_schema(tags=["Accounts"], manual_parameters=swagger_params.organization_params) def delete(self, request, pk, format=None): self.object = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.commented_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.commented_by ): self.object.delete() return Response( @@ -581,13 +633,13 @@ class AccountAttachmentView(APIView): authentication_classes = (JSONWebTokenAuthentication,) permission_classes = (IsAuthenticated,) - @swagger_auto_schema(tags=["Accounts"]) + @swagger_auto_schema(tags=["Accounts"], manual_parameters=swagger_params.organization_params) def delete(self, request, pk, format=None): self.object = self.model.objects.get(pk=pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.created_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.created_by ): self.object.delete() return Response( @@ -614,7 +666,8 @@ class AccountCreateMailView(APIView): tags=["Accounts"], manual_parameters=swagger_params.account_mail_params ) def post(self, request, pk, *args, **kwargs): - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data scheduled_later = params.get("scheduled_later") scheduled_date_time = params.get("scheduled_date_time") account = Account.objects.filter(id=pk).first() @@ -636,7 +689,9 @@ def post(self, request, pk, *args, **kwargs): if params.get("recipients"): contacts = json.loads(params.get("recipients")) for contact in contacts: - obj_contact = Contact.objects.filter(id=contact) + obj_contact = Contact.objects.filter( + id=contact, org=request.org + ) if obj_contact.exists(): email_obj.recipients.add(contact) else: diff --git a/cases/migrations/0008_auto_20210913_1918.py b/cases/migrations/0008_auto_20210913_1918.py new file mode 100644 index 0000000..ed89c63 --- /dev/null +++ b/cases/migrations/0008_auto_20210913_1918.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2 on 2021-09-13 13:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0034_auto_20210913_1918'), + ('cases', '0007_remove_case_company'), + ] + + operations = [ + migrations.AddField( + model_name='case', + name='company', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.company'), + ), + migrations.AlterField( + model_name='case', + name='assigned_to', + field=models.ManyToManyField(related_name='case_assigned_users', to='common.Profile'), + ), + migrations.AlterField( + model_name='case', + name='created_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='case_created_by', to='common.profile'), + ), + ] diff --git a/cases/migrations/0009_rename_company_case_org.py b/cases/migrations/0009_rename_company_case_org.py new file mode 100644 index 0000000..b6489d2 --- /dev/null +++ b/cases/migrations/0009_rename_company_case_org.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-09-22 12:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('cases', '0008_auto_20210913_1918'), + ] + + operations = [ + migrations.RenameField( + model_name='case', + old_name='company', + new_name='org', + ), + ] diff --git a/cases/migrations/0010_alter_case_org.py b/cases/migrations/0010_alter_case_org.py new file mode 100644 index 0000000..c9eda45 --- /dev/null +++ b/cases/migrations/0010_alter_case_org.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.7 on 2021-10-06 07:21 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0037_alter_profile_org'), + ('cases', '0009_rename_company_case_org'), + ] + + operations = [ + migrations.AlterField( + model_name='case', + name='org', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='case_org', to='common.org'), + ), + ] diff --git a/cases/models.py b/cases/models.py index 9bb4249..b2223f7 100644 --- a/cases/models.py +++ b/cases/models.py @@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType from accounts.models import Account from contacts.models import Contact -from common.models import User +from common.models import Org, Profile from common.utils import CASE_TYPE, PRIORITY_CHOICE, STATUS_CHOICE from planner.models import Event from teams.models import Teams @@ -29,13 +29,17 @@ class Case(models.Model): # closed_on = models.DateTimeField() closed_on = models.DateField() description = models.TextField(blank=True, null=True) - assigned_to = models.ManyToManyField(User, related_name="case_assigned_users") + assigned_to = models.ManyToManyField(Profile, related_name="case_assigned_users") created_by = models.ForeignKey( - User, related_name="case_created_by", on_delete=models.SET_NULL, null=True + Profile, related_name="case_created_by", on_delete=models.SET_NULL, null=True ) created_on = models.DateTimeField(_("Created on"), auto_now_add=True) is_active = models.BooleanField(default=False) teams = models.ManyToManyField(Teams, related_name="cases_teams") + org = models.ForeignKey( + Org, on_delete=models.SET_NULL, null=True, blank=True, related_name="case_org" + ) + class Meta: ordering = ["-created_on"] @@ -46,21 +50,21 @@ def __str__(self): @property def get_team_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) - return User.objects.filter(id__in=team_user_ids) + return Profile.objects.filter(id__in=team_user_ids) @property def get_team_and_assigned_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = team_user_ids + assigned_user_ids - return User.objects.filter(id__in=user_ids) + return Profile.objects.filter(id__in=user_ids) @property def get_assigned_users_not_in_teams(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = set(assigned_user_ids) - set(team_user_ids) - return User.objects.filter(id__in=list(user_ids)) + return Profile.objects.filter(id__in=list(user_ids)) def get_meetings(self): content_type = ContentType.objects.get(app_label="cases", model="case") @@ -108,7 +112,7 @@ def get_completed_calls(self): ).exclude(status="Planned") def get_assigned_user(self): - return User.objects.get(id=self.assigned_to_id) + return Profile.objects.get(id=self.assigned_to_id) @property def created_on_arrow(self): diff --git a/cases/serializer.py b/cases/serializer.py index 4d957e3..3c4726e 100644 --- a/cases/serializer.py +++ b/cases/serializer.py @@ -1,6 +1,6 @@ from rest_framework import serializers from cases.models import Case -from common.serializer import UserSerializer +from common.serializer import ProfileSerializer, OrganizationSerializer from teams.serializer import TeamsSerializer from accounts.serializer import AccountSerializer from contacts.serializer import ContactSerializer @@ -9,9 +9,10 @@ class CaseSerializer(serializers.ModelSerializer): account = AccountSerializer() contacts = ContactSerializer(read_only=True, many=True) - assigned_to = UserSerializer(read_only=True, many=True) - created_by = UserSerializer(read_only=True) + assigned_to = ProfileSerializer(read_only=True, many=True) + created_by = ProfileSerializer(read_only=True) teams = TeamsSerializer(read_only=True, many=True) + org = OrganizationSerializer() class Meta: model = Case @@ -30,6 +31,7 @@ class Meta: "contacts", "teams", "assigned_to", + "org", "created_on_arrow", ) @@ -41,18 +43,19 @@ def __init__(self, *args, **kwargs): case_view = kwargs.pop("case", False) request_obj = kwargs.pop("request_obj", None) super(CaseCreateSerializer, self).__init__(*args, **kwargs) + self.org = request_obj.org def validate_name(self, name): if self.instance: if ( - Case.objects.filter(name__iexact=name) + Case.objects.filter(name__iexact=name, org=self.org) .exclude(id=self.instance.id) .exists() ): raise serializers.ValidationError("Case already exists with this name") else: - if Case.objects.filter(name__iexact=name).exists(): + if Case.objects.filter(name__iexact=name, org=self.org).exists(): raise serializers.ValidationError("Case already exists with this name") return name @@ -67,5 +70,6 @@ class Meta: "created_on", "is_active", "account", + "org", "created_on_arrow", ) diff --git a/cases/swagger_params.py b/cases/swagger_params.py index d1ed0ea..2236abf 100644 --- a/cases/swagger_params.py +++ b/cases/swagger_params.py @@ -1,6 +1,14 @@ from drf_yasg import openapi +organization_params_in_header = openapi.Parameter( + 'org', openapi.IN_HEADER, required=True, type=openapi.TYPE_INTEGER) + +organization_params = [ + organization_params_in_header, +] + cases_list_get_params = [ + organization_params_in_header, openapi.Parameter("name", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("status", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("priority", openapi.IN_QUERY, type=openapi.TYPE_STRING), @@ -8,6 +16,7 @@ ] cases_detail_get_params = [ + organization_params_in_header, openapi.Parameter( "case_attachment", openapi.IN_QUERY, @@ -17,6 +26,7 @@ ] cases_create_post_params = [ + organization_params_in_header, openapi.Parameter( "name", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING ), @@ -47,5 +57,6 @@ ] cases_comment_edit_params = [ + organization_params_in_header, openapi.Parameter("comment", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] diff --git a/cases/tasks.py b/cases/tasks.py index 7c7176c..789df1d 100644 --- a/cases/tasks.py +++ b/cases/tasks.py @@ -5,7 +5,7 @@ from django.shortcuts import reverse from django.template.loader import render_to_string -from accounts.models import User +from accounts.models import Profile from cases.models import Case app = Celery("redis://") @@ -18,14 +18,14 @@ def send_email_to_assigned_user( """ Send Mail To Users When they are assigned to a case """ case = Case.objects.get(id=case_id) created_by = case.created_by - for user in recipients: + for profile_id in recipients: recipients_list = [] - user = User.objects.filter(id=user, is_active=True).first() - if user: - recipients_list.append(user.email) + profile = Profile.objects.filter(id=profile_id, is_active=True).first() + if profile: + recipients_list.append(profile.user.email) context = {} context["url"] = protocol + "://" + domain - context["user"] = user + context["user"] = profile.user context["case"] = case context["created_by"] = created_by subject = "Assigned to case." diff --git a/cases/views.py b/cases/views.py index 909faa6..f98448f 100644 --- a/cases/views.py +++ b/cases/views.py @@ -12,10 +12,10 @@ ) from accounts.models import Account from accounts.serializer import AccountSerializer -from common.models import User, Attachments, Comment +from common.models import User, Attachments, Comment, Profile from common.custom_auth import JSONWebTokenAuthentication from common.serializer import ( - UserSerializer, + ProfileSerializer, CommentSerializer, AttachmentsSerializer, ) @@ -36,7 +36,6 @@ from rest_framework.pagination import LimitOffsetPagination from drf_yasg.utils import swagger_auto_schema import json -from django.conf import settings class CaseListView(APIView, LimitOffsetPagination): @@ -51,21 +50,25 @@ def get_context_data(self, **kwargs): if len(self.request.data) == 0 else self.request.data ) - queryset = self.model.objects.all() - accounts = Account.objects.all() - contacts = Contact.objects.all() - users = User.objects.filter(is_active=True) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + queryset = self.model.objects.filter(org=self.request.org) + accounts = Account.objects.filter(org=self.request.org) + contacts = Contact.objects.filter(org=self.request.org) + profiles = Profile.objects.filter( + is_active=True, org=self.request.org) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: queryset = queryset.filter( - Q(created_by=self.request.user) | Q(assigned_to=self.request.user) + Q(created_by=self.request.profile) | Q( + assigned_to=self.request.profile) ).distinct() accounts = accounts.filter( - Q(created_by=self.request.user) | Q(assigned_to=self.request.user) + Q(created_by=self.request.profile) | Q( + assigned_to=self.request.profile) ).distinct() contacts = contacts.filter( - Q(created_by=self.request.user) | Q(assigned_to=self.request.user) + Q(created_by=self.request.profile) | Q( + assigned_to=self.request.profile) ).distinct() - users = users.filter(role="ADMIN") + profiles = profiles.filter(role="ADMIN") if params: if params.get("name"): @@ -88,7 +91,8 @@ def get_context_data(self, **kwargs): search = True context["search"] = search - results_cases = self.paginate_queryset(queryset, self.request, view=self) + results_cases = self.paginate_queryset( + queryset, self.request, view=self) cases = CaseSerializer(results_cases, many=True).data context["per_page"] = 10 @@ -104,14 +108,15 @@ def get_context_data(self, **kwargs): context["cases"] = cases return context context["cases"] = cases - context["users"] = UserSerializer(users, many=True).data + context["users"] = ProfileSerializer(profiles, many=True).data context["status"] = STATUS_CHOICE context["priority"] = PRIORITY_CHOICE context["type_of_case"] = CASE_TYPE context["accounts_list"] = AccountSerializer(accounts, many=True).data context["contacts_list"] = ContactSerializer(contacts, many=True).data - if self.request.user == "ADMIN": - context["teams_list"] = TeamsSerializer(Teams.objects.all(), many=True).data + if self.request.profile == "ADMIN": + context["teams_list"] = TeamsSerializer( + Teams.objects.filter(org=self.request.org), many=True).data return context @swagger_auto_schema( @@ -125,12 +130,14 @@ def get(self, request, *args, **kwargs): tags=["Cases"], manual_parameters=swagger_params.cases_create_post_params ) def post(self, request, *args, **kwargs): - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data data = {} serializer = CaseCreateSerializer(data=params, request_obj=request) if serializer.is_valid(): cases_obj = serializer.save( - created_by=request.user, + created_by=request.profile, + org=request.org, closed_on=params.get("closed_on"), case_type=params.get("type_of_case"), ) @@ -138,7 +145,7 @@ def post(self, request, *args, **kwargs): if params.get("contacts"): contacts = json.loads(params.get("contacts")) for contact in contacts: - obj_contact = Contact.objects.filter(id=contact) + obj_contact = Contact.objects.filter(id=contact, org=request.org) if obj_contact.exists(): cases_obj.contacts.add(contact) else: @@ -149,11 +156,11 @@ def post(self, request, *args, **kwargs): status=status.HTTP_400_BAD_REQUEST, ) - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - obj_team = Teams.objects.filter(id=team) + obj_team = Teams.objects.filter(id=team, org=request.org) if obj_team.exists(): cases_obj.teams.add(team) else: @@ -164,10 +171,11 @@ def post(self, request, *args, **kwargs): status=status.HTTP_400_BAD_REQUEST, ) if params.get("assigned_to"): - assinged_to_users_ids = json.loads(params.get("assigned_to")) + assinged_to_users_ids = json.loads( + params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter(id=user_id, org=request.org) if user.exists(): cases_obj.assigned_to.add(user_id) else: @@ -180,20 +188,23 @@ def post(self, request, *args, **kwargs): if self.request.FILES.get("case_attachment"): attachment = Attachments() - attachment.created_by = self.request.user - attachment.file_name = self.request.FILES.get("case_attachment").name + attachment.created_by = self.request.profile + attachment.file_name = self.request.FILES.get( + "case_attachment").name attachment.cases = cases_obj - attachment.attachment = self.request.FILES.get("case_attachment") + attachment.attachment = self.request.FILES.get( + "case_attachment") attachment.save() assigned_to_list = list( cases_obj.assigned_to.all().values_list("id", flat=True) ) + current_site = get_current_site(request) recipients = assigned_to_list send_email_to_assigned_user.delay( recipients, cases_obj.id, - domain=settings.Domain, + domain=current_site.domain, protocol=self.request.scheme, ) return Response( @@ -219,13 +230,19 @@ def get_object(self, pk): tags=["Cases"], manual_parameters=swagger_params.cases_create_post_params ) def put(self, request, pk, format=None): - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data cases_object = self.get_object(pk=pk) data = {} - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if cases_object.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_404_NOT_FOUND, + ) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: if not ( - (self.request.user == cases_object.created_by) - or (self.request.user in cases_object.assigned_to.all()) + (self.request.profile == cases_object.created_by) + or (self.request.profile in cases_object.assigned_to.all()) ): return Response( { @@ -253,7 +270,7 @@ def put(self, request, pk, format=None): if params.get("contacts"): contacts = json.loads(params.get("contacts")) for contact in contacts: - obj_contact = Contact.objects.filter(id=contact) + obj_contact = Contact.objects.filter(id=contact, org=request.org) if obj_contact.exists(): cases_object.contacts.add(contact) else: @@ -263,12 +280,12 @@ def put(self, request, pk, format=None): status=status.HTTP_400_BAD_REQUEST, ) - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": cases_object.teams.clear() if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - obj_team = Teams.objects.filter(id=team) + obj_team = Teams.objects.filter(id=team, org=request.org) if obj_team.exists(): cases_object.teams.add(team) else: @@ -280,9 +297,10 @@ def put(self, request, pk, format=None): cases_object.assigned_to.clear() if params.get("assigned_to"): - assinged_to_users_ids = json.loads(params.get("assigned_to")) + assinged_to_users_ids = json.loads( + params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter(id=user_id, org=request.org) if user.exists(): cases_object.assigned_to.add(user_id) else: @@ -294,20 +312,24 @@ def put(self, request, pk, format=None): if self.request.FILES.get("case_attachment"): attachment = Attachments() - attachment.created_by = self.request.user - attachment.file_name = self.request.FILES.get("case_attachment").name - attachment.cases = cases_object - attachment.attachment = self.request.FILES.get("case_attachment") + attachment.created_by = self.request.profile + attachment.file_name = self.request.FILES.get( + "case_attachment").name + attachment.case = cases_object + attachment.attachment = self.request.FILES.get( + "case_attachment") attachment.save() assigned_to_list = list( cases_object.assigned_to.all().values_list("id", flat=True) ) - recipients = list(set(assigned_to_list) - set(previous_assigned_to_users)) + recipients = list(set(assigned_to_list) - + set(previous_assigned_to_users)) + current_site = get_current_site(self.request) send_email_to_assigned_user.delay( recipients, cases_object.id, - domain=settings.Domain, + domain=current_site.domain, protocol=self.request.scheme, ) return Response( @@ -320,12 +342,17 @@ def put(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["Cases"], + tags=["Cases"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.get_object(pk) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: - if self.request.user != self.object.created_by: + if self.object.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_404_NOT_FOUND + ) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: + if self.request.profile != self.object.created_by: return Response( { "error": True, @@ -340,16 +367,21 @@ def delete(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["Cases"], + tags=["Cases"], manual_parameters=swagger_params.organization_params ) def get(self, request, pk, format=None): self.cases = self.get_object(pk=pk) + if self.cases.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_404_NOT_FOUND, + ) context = {} context["cases_obj"] = CaseSerializer(self.cases).data - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: if not ( - (self.request.user == self.cases.created_by) - or (self.request.user in self.cases.assigned_to.all()) + (self.request.profile == self.cases.created_by) + or (self.request.profile in self.cases.assigned_to.all()) ): return Response( { @@ -362,28 +394,29 @@ def get(self, request, pk, format=None): comment_permission = ( True if ( - self.request.user == self.cases.created_by - or self.request.user.is_superuser - or self.request.user.role == "ADMIN" + self.request.profile == self.cases.created_by + or self.request.profile.is_admin + or self.request.profile.role == "ADMIN" ) else False ) - if self.request.user.is_superuser or self.request.user.role == "ADMIN": + if self.request.profile.is_admin or self.request.profile.role == "ADMIN": users_mention = list( - User.objects.filter( - is_active=True, - ).values("username") + Profile.objects.filter( + is_active=True, org=self.request.org + ).values("user__username") ) - elif self.request.user != self.cases.created_by: + elif self.request.profile != self.cases.created_by: if self.cases.created_by: - users_mention = [{"username": self.cases.created_by.username}] + users_mention = [{"username": self.cases.created_by.user.username}] else: users_mention = [] else: users_mention = [] - attachments = Attachments.objects.filter(case=self.cases).order_by("-id") + attachments = Attachments.objects.filter( + case=self.cases).order_by("-id") comments = Comment.objects.filter(case=self.cases).order_by("-id") context.update( @@ -393,10 +426,11 @@ def get(self, request, pk, format=None): "contacts": ContactSerializer( self.cases.contacts.all(), many=True ).data, - "users": UserSerializer( - User.objects.filter( + "users": ProfileSerializer( + Profile.objects.filter( is_active=True, - ).order_by("email"), + org=self.request.org + ).order_by("user__email"), many=True, ).data, "status": STATUS_CHOICE, @@ -417,13 +451,18 @@ def post(self, request, pk, **kwargs): if len(self.request.data) == 0 else self.request.data ) + if self.cases_obj.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_404_NOT_FOUND + ) context = {} self.cases_obj = Case.objects.get(pk=pk) comment_serializer = CommentSerializer(data=params) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: if not ( - (self.request.user == self.cases_obj.created_by) - or (self.request.user in self.cases_obj.assigned_to.all()) + (self.request.profile == self.cases_obj.created_by) + or (self.request.profile in self.cases_obj.assigned_to.all()) ): return Response( { @@ -436,18 +475,20 @@ def post(self, request, pk, **kwargs): if params.get("comment"): comment_serializer.save( case_id=self.cases_obj.id, - commented_by_id=self.request.user.id, + commented_by_id=self.request.profile.id, ) if self.request.FILES.get("case_attachment"): attachment = Attachments() - attachment.created_by = self.request.user - attachment.file_name = self.request.FILES.get("case_attachment").name + attachment.created_by = self.request.profile + attachment.file_name = self.request.FILES.get( + "case_attachment").name attachment.case = self.cases_obj attachment.attachment = self.request.FILES.get("case_attachment") attachment.save() - attachments = Attachments.objects.filter(case=self.cases_obj).order_by("-id") + attachments = Attachments.objects.filter( + case=self.cases_obj).order_by("-id") comments = Comment.objects.filter(case=self.cases_obj).order_by("-id") context.update( @@ -472,12 +513,13 @@ def get_object(self, pk): tags=["Cases"], manual_parameters=swagger_params.cases_comment_edit_params ) def put(self, request, pk, format=None): - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data obj = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == obj.commented_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == obj.commented_by ): serializer = CommentSerializer(obj, data=params) if params.get("comment"): @@ -501,14 +543,14 @@ def put(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["Cases"], + tags=["Cases"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.commented_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.commented_by ): self.object.delete() return Response( @@ -531,14 +573,14 @@ class CaseAttachmentView(APIView): permission_classes = (IsAuthenticated,) @swagger_auto_schema( - tags=["Cases"], + tags=["Cases"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.model.objects.get(pk=pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.created_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.created_by ): self.object.delete() return Response( diff --git a/common/custom_auth.py b/common/custom_auth.py index 65b56a5..689a4f2 100644 --- a/common/custom_auth.py +++ b/common/custom_auth.py @@ -36,7 +36,7 @@ def authenticate(self, request): """ authorization = request.META.get("HTTP_AUTHORIZATION", None) - self.company = request.META.get("HTTP_COMPANY", None) + self.org = request.META.get("HTTP_COMPANY", None) jwt_value = self.get_jwt_value(request) if jwt_value is None: return None @@ -119,3 +119,23 @@ def authenticate_header(self, request): return '{0} realm="{1}"'.format( api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm ) + + +class TokenAuthMiddleware(object): + """adding profile and company to request object + """ + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + self.process_request(request) + return self.get_response(request) + + def process_request(self, request): + try: + token_auth_user = JSONWebTokenAuthentication().authenticate(request) + except exceptions.AuthenticationFailed: + token_auth_user = None + if isinstance(token_auth_user, tuple): + request.user = token_auth_user[0] diff --git a/common/middleware/get_company.py b/common/middleware/get_company.py new file mode 100644 index 0000000..5267ede --- /dev/null +++ b/common/middleware/get_company.py @@ -0,0 +1,36 @@ +from rest_framework.response import Response +from rest_framework import status +from django.contrib.auth import logout +from common.models import Org, Profile + + +def set_profile_request(request, org): + if request.user.is_authenticated: + request.profile = Profile.objects.filter( + user=request.user, org=org, is_active=True).first() + if request.profile is None: + logout(request) + return Response( + {"error": False}, status=status.HTTP_200_OK, + ) + + +class GetProfileAndOrg(object): + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + self.process_request(request) + return self.get_response(request) + + def process_request(self, request): + if request.headers.get("org"): + org_id = request.headers.get("org") + org = Org.objects.filter(id=org_id).first() + if org: + request.org = org + set_profile_request(request, org) + else: + request.org = None + else: + request.org = None diff --git a/common/migrations/0028_user_phone.py b/common/migrations/0028_user_phone.py new file mode 100644 index 0000000..9e829bf --- /dev/null +++ b/common/migrations/0028_user_phone.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2 on 2021-06-18 07:33 + +from django.db import migrations +import phonenumber_field.modelfields + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0027_auto_20210418_1112'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='phone', + field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None), + ), + ] diff --git a/common/migrations/0029_auto_20210730_1328.py b/common/migrations/0029_auto_20210730_1328.py new file mode 100644 index 0000000..facafbe --- /dev/null +++ b/common/migrations/0029_auto_20210730_1328.py @@ -0,0 +1,58 @@ +# Generated by Django 3.2.5 on 2021-07-30 07:58 + +from django.db import migrations, models +import django.db.models.deletion +import phonenumber_field.modelfields + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0028_user_phone'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='address', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='adress_users', to='common.address'), + ), + migrations.AddField( + model_name='user', + name='alternate_email', + field=models.EmailField(default='', max_length=255), + preserve_default=False, + ), + migrations.AddField( + model_name='user', + name='alternate_phone', + field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None), + ), + migrations.AddField( + model_name='user', + name='description', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='user', + name='skype_ID', + field=models.CharField(default='', max_length=50), + preserve_default=False, + ), + migrations.AddField( + model_name='user', + name='type', + field=models.CharField(default='', max_length=50), + preserve_default=False, + ), + migrations.AlterField( + model_name='user', + name='phone', + field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None, unique=True), + ), + migrations.AlterField( + model_name='user', + name='role', + field=models.CharField(max_length=50), + ), + ] diff --git a/common/migrations/0030_alter_user_role.py b/common/migrations/0030_alter_user_role.py new file mode 100644 index 0000000..9b3fba6 --- /dev/null +++ b/common/migrations/0030_alter_user_role.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.5 on 2021-07-30 08:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0029_auto_20210730_1328'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='role', + field=models.CharField(choices=[('ADMIN', 'ADMIN'), ('USER', 'USER')], max_length=50), + ), + ] diff --git a/common/migrations/0031_auto_20210805_1214.py b/common/migrations/0031_auto_20210805_1214.py new file mode 100644 index 0000000..79a4ab8 --- /dev/null +++ b/common/migrations/0031_auto_20210805_1214.py @@ -0,0 +1,113 @@ +# Generated by Django 3.2.5 on 2021-08-05 06:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0030_alter_user_role'), + ] + + operations = [ + migrations.RenameField( + model_name='user', + old_name='type', + new_name='user_type', + ), + migrations.AlterField( + model_name='address', + name='address_line', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Address'), + ), + migrations.AlterField( + model_name='address', + name='city', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='City'), + ), + migrations.AlterField( + model_name='address', + name='postcode', + field=models.CharField(blank=True, max_length=64, null=True, verbose_name='Post/Zip-code'), + ), + migrations.AlterField( + model_name='address', + name='state', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='State'), + ), + migrations.AlterField( + model_name='address', + name='street', + field=models.CharField(blank=True, max_length=55, null=True, verbose_name='Street'), + ), + migrations.AlterField( + model_name='apisettings', + name='website', + field=models.URLField(max_length=255, null=True), + ), + migrations.AlterField( + model_name='comment_files', + name='comment_file', + field=models.FileField(null=True, upload_to='comment_files', verbose_name='File'), + ), + migrations.AlterField( + model_name='company', + name='address', + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name='company', + name='name', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AlterField( + model_name='document', + name='title', + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name='google', + name='dob', + field=models.CharField(max_length=50, null=True), + ), + migrations.AlterField( + model_name='google', + name='email', + field=models.CharField(db_index=True, max_length=200, null=True), + ), + migrations.AlterField( + model_name='google', + name='family_name', + field=models.CharField(max_length=200, null=True), + ), + migrations.AlterField( + model_name='google', + name='gender', + field=models.CharField(max_length=10, null=True), + ), + migrations.AlterField( + model_name='google', + name='given_name', + field=models.CharField(max_length=200, null=True), + ), + migrations.AlterField( + model_name='google', + name='google_id', + field=models.CharField(max_length=200, null=True), + ), + migrations.AlterField( + model_name='google', + name='google_url', + field=models.TextField(null=True), + ), + migrations.AlterField( + model_name='google', + name='name', + field=models.CharField(max_length=200, null=True), + ), + migrations.AlterField( + model_name='google', + name='verified_email', + field=models.CharField(max_length=200, null=True), + ), + ] diff --git a/common/migrations/0032_remove_user_user_type.py b/common/migrations/0032_remove_user_user_type.py new file mode 100644 index 0000000..683974a --- /dev/null +++ b/common/migrations/0032_remove_user_user_type.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.5 on 2021-08-20 06:57 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0031_auto_20210805_1214'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='user_type', + ), + ] diff --git a/common/migrations/0033_alter_user_alternate_email.py b/common/migrations/0033_alter_user_alternate_email.py new file mode 100644 index 0000000..b578aba --- /dev/null +++ b/common/migrations/0033_alter_user_alternate_email.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.5 on 2021-09-02 06:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0032_remove_user_user_type'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='alternate_email', + field=models.EmailField(max_length=255, null=True), + ), + ] diff --git a/common/migrations/0034_auto_20210913_1918.py b/common/migrations/0034_auto_20210913_1918.py new file mode 100644 index 0000000..1fae066 --- /dev/null +++ b/common/migrations/0034_auto_20210913_1918.py @@ -0,0 +1,181 @@ +# Generated by Django 3.2 on 2021-09-13 13:48 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import phonenumber_field.modelfields + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0033_alter_user_alternate_email'), + ] + + operations = [ + migrations.RemoveField( + model_name='comment', + name='user', + ), + migrations.RemoveField( + model_name='google', + name='user', + ), + migrations.RemoveField( + model_name='user', + name='address', + ), + migrations.RemoveField( + model_name='user', + name='alternate_phone', + ), + migrations.RemoveField( + model_name='user', + name='has_marketing_access', + ), + migrations.RemoveField( + model_name='user', + name='has_sales_access', + ), + migrations.RemoveField( + model_name='user', + name='is_admin', + ), + migrations.RemoveField( + model_name='user', + name='phone', + ), + migrations.RemoveField( + model_name='user', + name='role', + ), + migrations.AddField( + model_name='apisettings', + name='company', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.company'), + ), + migrations.AddField( + model_name='comment', + name='profile', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='user_comments', to='common.profile'), + ), + migrations.AddField( + model_name='document', + name='company', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.company'), + ), + migrations.AddField( + model_name='google', + name='profile', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='google', to='common.profile'), + ), + migrations.AddField( + model_name='profile', + name='address', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='adress_users', to='common.address'), + ), + migrations.AddField( + model_name='profile', + name='alternate_phone', + field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None), + ), + migrations.AddField( + model_name='profile', + name='company', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='common.company'), + ), + migrations.AddField( + model_name='profile', + name='date_of_joining', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='profile', + name='has_marketing_access', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='profile', + name='has_sales_access', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='profile', + name='is_active', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='profile', + name='is_organization_admin', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='profile', + name='phone', + field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None, unique=True), + ), + migrations.AddField( + model_name='profile', + name='role', + field=models.CharField(choices=[('ADMIN', 'ADMIN'), ('USER', 'USER')], default='USER', max_length=50), + ), + migrations.AlterField( + model_name='apisettings', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='settings_created_by', to='common.profile'), + ), + migrations.AlterField( + model_name='apisettings', + name='lead_assigned_to', + field=models.ManyToManyField(related_name='lead_assignee_users', to='common.Profile'), + ), + migrations.AlterField( + model_name='attachments', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='attachment_created_by', to='common.profile'), + ), + migrations.AlterField( + model_name='comment', + name='commented_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='common.profile'), + ), + migrations.AlterField( + model_name='company', + name='sub_domain', + field=models.CharField(blank=True, max_length=30, null=True), + ), + migrations.AlterField( + model_name='document', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='document_uploaded', to='common.profile'), + ), + migrations.AlterField( + model_name='document', + name='shared_to', + field=models.ManyToManyField(related_name='document_shared_to', to='common.Profile'), + ), + migrations.AlterField( + model_name='profile', + name='activation_key', + field=models.CharField(blank=True, max_length=150, null=True), + ), + migrations.AlterField( + model_name='profile', + name='key_expires', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name='profile', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='user', + name='username', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AlterUniqueTogether( + name='profile', + unique_together={('user', 'company')}, + ), + ] diff --git a/common/migrations/0035_remove_company_sub_domain.py b/common/migrations/0035_remove_company_sub_domain.py new file mode 100644 index 0000000..1d44012 --- /dev/null +++ b/common/migrations/0035_remove_company_sub_domain.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2 on 2021-09-22 05:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0034_auto_20210913_1918'), + ] + + operations = [ + migrations.RemoveField( + model_name='company', + name='sub_domain', + ), + ] diff --git a/common/migrations/0036_auto_20210922_1801.py b/common/migrations/0036_auto_20210922_1801.py new file mode 100644 index 0000000..4892418 --- /dev/null +++ b/common/migrations/0036_auto_20210922_1801.py @@ -0,0 +1,45 @@ +# Generated by Django 3.2 on 2021-09-22 12:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0007_rename_company_event_org'), + ('cases', '0009_rename_company_case_org'), + ('teams', '0007_rename_company_teams_org'), + ('leads', '0016_rename_company_lead_org'), + ('contacts', '0009_rename_company_contact_org'), + ('invoices', '0011_rename_company_invoice_org'), + ('tasks', '0008_rename_company_task_org'), + ('accounts', '0014_rename_company_account_org'), + ('opportunity', '0008_rename_company_opportunity_org'), + ('common', '0035_remove_company_sub_domain'), + ] + + operations = [ + migrations.RenameModel( + old_name='Company', + new_name='Org', + ), + migrations.RenameField( + model_name='apisettings', + old_name='company', + new_name='org', + ), + migrations.RenameField( + model_name='document', + old_name='company', + new_name='org', + ), + migrations.RenameField( + model_name='profile', + old_name='company', + new_name='org', + ), + migrations.AlterUniqueTogether( + name='profile', + unique_together={('user', 'org')}, + ), + ] diff --git a/common/migrations/0037_alter_profile_org.py b/common/migrations/0037_alter_profile_org.py new file mode 100644 index 0000000..6a86e6a --- /dev/null +++ b/common/migrations/0037_alter_profile_org.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.7 on 2021-10-06 07:21 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0036_auto_20210922_1801'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='org', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='common.org'), + ), + ] diff --git a/common/models.py b/common/models.py index 2974729..8aa76da 100644 --- a/common/models.py +++ b/common/models.py @@ -7,6 +7,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, UserManager +from phonenumber_field.modelfields import PhoneNumberField from common.templatetags.common_tags import ( is_document_file_image, is_document_file_audio, @@ -27,35 +28,81 @@ def img_url(self, filename): return "%s/%s/%s" % ("profile_pics", hash_, filename) -class Company(models.Model): - name = models.CharField(max_length=100, blank=True, default="") - address = models.TextField(blank=True, default="") - sub_domain = models.CharField(max_length=30) +class Address(models.Model): + address_line = models.CharField( + _("Address"), max_length=255, blank=True, null=True + ) + street = models.CharField( + _("Street"), max_length=55, blank=True, null=True) + city = models.CharField(_("City"), max_length=255, blank=True, null=True) + state = models.CharField(_("State"), max_length=255, blank=True, null=True) + postcode = models.CharField( + _("Post/Zip-code"), max_length=64, blank=True, null=True + ) + country = models.CharField( + max_length=3, choices=COUNTRIES, blank=True, null=True) + + def __str__(self): + return self.city if self.city else "" + + def get_complete_address(self): + address = "" + if self.address_line: + address += self.address_line + if self.street: + if address: + address += ", " + self.street + else: + address += self.street + if self.city: + if address: + address += ", " + self.city + else: + address += self.city + if self.state: + if address: + address += ", " + self.state + else: + address += self.state + if self.postcode: + if address: + address += ", " + self.postcode + else: + address += self.postcode + if self.country: + if address: + address += ", " + self.get_country_display() + else: + address += self.get_country_display() + return address + + +class Org(models.Model): + name = models.CharField(max_length=100, blank=True, null=True) + address = models.TextField(blank=True, null=True) user_limit = models.IntegerField(default=5) - country = models.CharField(max_length=3, choices=COUNTRIES, blank=True, null=True) + country = models.CharField( + max_length=3, choices=COUNTRIES, blank=True, null=True) class User(AbstractBaseUser, PermissionsMixin): file_prepend = "users/profile_pics" - username = models.CharField(max_length=100, unique=True) + username = models.CharField(max_length=100, null=True, blank=True) first_name = models.CharField(max_length=150, blank=True) last_name = models.CharField(max_length=150, blank=True) email = models.EmailField(max_length=255, unique=True) + alternate_email = models.EmailField(max_length=255, null=True) is_active = models.BooleanField(default=True) - is_admin = models.BooleanField(default=False) is_staff = models.BooleanField(default=False) date_joined = models.DateTimeField(("date joined"), auto_now_add=True) - role = models.CharField(max_length=50, choices=ROLES) profile_pic = models.FileField( max_length=1000, upload_to=img_url, null=True, blank=True ) - has_sales_access = models.BooleanField(default=False) - has_marketing_access = models.BooleanField(default=False) + skype_ID = models.CharField(max_length=50) + description = models.TextField(blank=True, null=True) USERNAME_FIELD = "email" - REQUIRED_FIELDS = [ - "username", - ] + REQUIRED_FIELDS = ['username'] objects = UserManager() @@ -86,51 +133,38 @@ def __str__(self): return self.email -class Address(models.Model): - address_line = models.CharField( - _("Address"), max_length=255, blank=True, default="" - ) - street = models.CharField(_("Street"), max_length=55, blank=True, default="") - city = models.CharField(_("City"), max_length=255, blank=True, default="") - state = models.CharField(_("State"), max_length=255, blank=True, default="") - postcode = models.CharField( - _("Post/Zip-code"), max_length=64, blank=True, default="" +class Profile(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + org = models.ForeignKey(Org, null=True, on_delete=models.CASCADE, blank=True) + phone = PhoneNumberField(null=True, unique=True) + alternate_phone = PhoneNumberField(null=True) + address = models.ForeignKey( + Address, + related_name="adress_users", + on_delete=models.CASCADE, + blank=True, + null=True, ) - country = models.CharField(max_length=3, choices=COUNTRIES, blank=True, null=True) + role = models.CharField(max_length=50, choices=ROLES, default="USER") + has_sales_access = models.BooleanField(default=False) + has_marketing_access = models.BooleanField(default=False) + is_active = models.BooleanField(default=True) + is_organization_admin = models.BooleanField(default=False) + date_of_joining = models.DateField(null=True, blank=True) + activation_key = models.CharField(max_length=150, null=True, blank=True) + key_expires = models.DateTimeField(null=True, blank=True) - def __str__(self): - return self.city if self.city else "" + class Meta: + unique_together = (("user", "org"),) - def get_complete_address(self): - address = "" - if self.address_line: - address += self.address_line - if self.street: - if address: - address += ", " + self.street - else: - address += self.street - if self.city: - if address: - address += ", " + self.city - else: - address += self.city - if self.state: - if address: - address += ", " + self.state - else: - address += self.state - if self.postcode: - if address: - address += ", " + self.postcode - else: - address += self.postcode - if self.country: - if address: - address += ", " + self.get_country_display() - else: - address += self.get_country_display() - return address + def save(self, *args, **kwargs): + """ by default the expiration time is set to 2 hours """ + self.key_expires = timezone.now() + datetime.timedelta(hours=2) + super(Profile, self).save(*args, **kwargs) + + @property + def is_admin(self): + return self.is_organization_admin class Comment(models.Model): @@ -144,7 +178,7 @@ class Comment(models.Model): comment = models.CharField(max_length=255) commented_on = models.DateTimeField(auto_now_add=True) commented_by = models.ForeignKey( - User, on_delete=models.CASCADE, blank=True, null=True + Profile, on_delete=models.CASCADE, blank=True, null=True ) account = models.ForeignKey( "accounts.Account", @@ -174,8 +208,8 @@ class Comment(models.Model): related_name="contact_comments", on_delete=models.CASCADE, ) - user = models.ForeignKey( - "User", + profile = models.ForeignKey( + "Profile", blank=True, null=True, related_name="user_comments", @@ -217,7 +251,8 @@ def commented_on_arrow(self): class Comment_Files(models.Model): comment = models.ForeignKey(Comment, on_delete=models.CASCADE) updated_on = models.DateTimeField(auto_now_add=True) - comment_file = models.FileField("File", upload_to="comment_files", default="") + comment_file = models.FileField( + "File", upload_to="comment_files", null=True) def get_file_name(self): if self.comment_file: @@ -228,7 +263,7 @@ def get_file_name(self): class Attachments(models.Model): created_by = models.ForeignKey( - User, + Profile, related_name="attachment_created_by", on_delete=models.SET_NULL, null=True, @@ -236,7 +271,8 @@ class Attachments(models.Model): ) file_name = models.CharField(max_length=60) created_on = models.DateTimeField(_("Created on"), auto_now_add=True) - attachment = models.FileField(max_length=1001, upload_to="attachments/%Y/%m/") + attachment = models.FileField( + max_length=1001, upload_to="attachments/%Y/%m/") lead = models.ForeignKey( "leads.Lead", null=True, @@ -338,10 +374,10 @@ class Document(models.Model): DOCUMENT_STATUS_CHOICE = (("active", "active"), ("inactive", "inactive")) - title = models.TextField(blank=True, default="") + title = models.TextField(blank=True, null=True) document_file = models.FileField(upload_to=document_path, max_length=5000) created_by = models.ForeignKey( - User, + Profile, related_name="document_uploaded", on_delete=models.SET_NULL, null=True, @@ -351,8 +387,12 @@ class Document(models.Model): status = models.CharField( choices=DOCUMENT_STATUS_CHOICE, max_length=64, default="active" ) - shared_to = models.ManyToManyField(User, related_name="document_shared_to") - teams = models.ManyToManyField("teams.Teams", related_name="document_teams") + shared_to = models.ManyToManyField(Profile, related_name="document_shared_to") + teams = models.ManyToManyField( + "teams.Teams", related_name="document_teams") + org = models.ForeignKey( + Org, on_delete=models.SET_NULL, null=True, blank=True + ) class Meta: ordering = ("-created_on",) @@ -386,21 +426,21 @@ def file_type(self): @property def get_team_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) - return User.objects.filter(id__in=team_user_ids) + return Profile.objects.filter(id__in=team_user_ids) @property def get_team_and_assigned_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.shared_to.values_list("id", flat=True)) user_ids = team_user_ids + assigned_user_ids - return User.objects.filter(id__in=user_ids) + return Profile.objects.filter(id__in=user_ids) @property def get_assigned_users_not_in_teams(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.shared_to.values_list("id", flat=True)) user_ids = set(assigned_user_ids) - set(team_user_ids) - return User.objects.filter(id__in=list(user_ids)) + return Profile.objects.filter(id__in=list(user_ids)) @property def created_on_arrow(self): @@ -414,16 +454,19 @@ def generate_key(): class APISettings(models.Model): title = models.TextField() apikey = models.CharField(max_length=16, blank=True) - website = models.URLField(max_length=255, default="") - lead_assigned_to = models.ManyToManyField(User, related_name="lead_assignee_users") + website = models.URLField(max_length=255, null=True) + lead_assigned_to = models.ManyToManyField( + Profile, related_name="lead_assignee_users") tags = models.ManyToManyField("accounts.Tags", blank=True) created_by = models.ForeignKey( - User, + Profile, related_name="settings_created_by", on_delete=models.SET_NULL, null=True, blank=True, ) + org = models.ForeignKey( + Org, blank=True, on_delete=models.SET_NULL, null=True) created_on = models.DateTimeField(auto_now_add=True) class Meta: @@ -439,31 +482,17 @@ def save(self, *args, **kwargs): class Google(models.Model): - user = models.ForeignKey(User, related_name="google", on_delete=models.CASCADE) - google_id = models.CharField(max_length=200, default="") - google_url = models.TextField(default="") - verified_email = models.CharField(max_length=200, default="") - family_name = models.CharField(max_length=200, default="") - name = models.CharField(max_length=200, default="") - gender = models.CharField(max_length=10, default="") - dob = models.CharField(max_length=50, default="") - given_name = models.CharField(max_length=200, default="") - email = models.CharField(max_length=200, default="", db_index=True) + profile = models.ForeignKey( + Profile, related_name="google", null=True, on_delete=models.CASCADE) + google_id = models.CharField(max_length=200, null=True) + google_url = models.TextField(null=True) + verified_email = models.CharField(max_length=200, null=True) + family_name = models.CharField(max_length=200, null=True) + name = models.CharField(max_length=200, null=True) + gender = models.CharField(max_length=10, null=True) + dob = models.CharField(max_length=50, null=True) + given_name = models.CharField(max_length=200, null=True) + email = models.CharField(max_length=200, null=True, db_index=True) def __str__(self): return self.email - - -class Profile(models.Model): - """ this model is used for activating the user within a particular expiration time """ - - user = models.OneToOneField( - User, related_name="profile", on_delete=models.CASCADE - ) # 1 to 1 link with Django User - activation_key = models.CharField(max_length=150) - key_expires = models.DateTimeField() - - def save(self, *args, **kwargs): - """ by default the expiration time is set to 2 hours """ - self.key_expires = timezone.now() + datetime.timedelta(hours=2) - super(Profile, self).save(*args, **kwargs) diff --git a/common/serializer.py b/common/serializer.py index 0e3b20a..7084003 100644 --- a/common/serializer.py +++ b/common/serializer.py @@ -1,44 +1,24 @@ import re +from django.contrib.auth.hashers import check_password from rest_framework import serializers from common.models import ( User, - Company, + Org, Comment, Address, Attachments, Document, APISettings, + Profile ) from django.utils.http import urlsafe_base64_decode from django.contrib.auth.tokens import default_token_generator -class CompanySerializer(serializers.ModelSerializer): +class OrganizationSerializer(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", - # "get_app_name", - ) + model = Org + fields = ("id", "name") class CommentSerializer(serializers.ModelSerializer): @@ -57,7 +37,7 @@ class Meta: "task", "invoice", "event", - "user", + "profile", ) @@ -73,24 +53,11 @@ class Meta: ) -class RegisterUserSerializer(serializers.ModelSerializer): - password = serializers.CharField() - - class Meta: - model = User - fields = ( - "email", - "username", - "password", - ) - - def __init__(self, *args, **kwargs): - self.request_user = kwargs.pop("request_user", None) - super(RegisterUserSerializer, self).__init__(*args, **kwargs) - if not self.instance: - self.fields["password"].required = True - else: - self.fields["password"].required = False +class RegisterOrganizationSerializer(serializers.Serializer): + email = serializers.EmailField() + first_name = serializers.CharField(max_length=255) + password = serializers.CharField(max_length=100) + org_name = serializers.CharField(max_length=100) def validate_password(self, password): if password: @@ -100,12 +67,47 @@ def validate_password(self, password): ) return password - def validate_username(self, username): - if not username[0].isalpha(): - raise serializers.ValidationError("Username should start with Alphabet") - if User.objects.filter(username__iexact=username).exists(): - raise serializers.ValidationError("Username already exist") - return username + def validate_email(self, email): + org_name = self.initial_data.get('org_name') + if Profile.objects.filter(user__email__iexact=email, + org__name=org_name).exists(): + raise serializers.ValidationError( + "This email is already registered in this organization") + return email + + def validate_org_name(self, org_name): + if bool(re.search(r"[~\!_.@#\$%\^&\*\ \(\)\+{}\":;'/\[\]]", org_name)): + raise serializers.ValidationError( + "organization name should not contain any special characters") + if Org.objects.filter(name=org_name).exists(): + raise serializers.ValidationError( + "Organization already exists with this name") + return org_name + + +class BillingAddressSerializer(serializers.ModelSerializer): + country = serializers.SerializerMethodField() + + def get_country(self, obj): + return obj.get_country_display() + + 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 class CreateUserSerializer(serializers.ModelSerializer): @@ -114,68 +116,87 @@ class CreateUserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ( - "email", "first_name", "last_name", - "username", - "role", + "email", + "alternate_email", + "skype_ID", + "description", "profile_pic", - "has_sales_access", - "has_marketing_access", "password", ) def __init__(self, *args, **kwargs): - self.request_user = kwargs.pop("request_user", None) + self.org = kwargs.pop("org", None) super(CreateUserSerializer, self).__init__(*args, **kwargs) self.fields["first_name"].required = True - if not self.instance: - self.fields["password"].required = True - else: - self.fields["password"].required = False - self.fields["email"].required = False - self.fields["role"].required = False + self.fields["password"].required = False + self.fields["profile_pic"].required = False + self.fields["skype_ID"].required = False - def validate_password(self, 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, has_sales_access): - user_role = self.initial_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.initial_data.get("has_marketing_access", False) - if not has_sales_access and not marketing: - raise serializers.ValidationError("Select atleast one option.") - if self.request_user.role == "USER": - has_sales_access = self.instance.has_sales_access - return has_sales_access - - def validate_has_marketing_access(self, marketing): - if self.request_user.role == "USER": - marketing = self.instance.has_marketing_access - return marketing def validate_email(self, email): if self.instance: if self.instance.email != email: - if not User.objects.filter(email=email).exists(): + if not Profile.objects.filter( + user__email=email, org=self.org).exists(): return email raise serializers.ValidationError("Email already exists") - else: - return email + return email else: - if not User.objects.filter(email=email).exists(): + if not Profile.objects.filter(user__email=email.lower(), org=self.org).exists(): return email - raise serializers.ValidationError("User already exists with this email") + raise serializers.ValidationError('Given Email id already exists') + + +class CreateProfileSerializer(serializers.ModelSerializer): + + class Meta: + model = Profile + fields = ( + "role", + "phone", + "alternate_phone", + "has_sales_access", + "has_marketing_access", + "is_organization_admin" + ) + + def __init__(self, *args, **kwargs): + super(CreateProfileSerializer, self).__init__(*args, **kwargs) + self.fields["alternate_phone"].required = False + self.fields["role"].required = True + self.fields["phone"].required = True + + +class UserSerializer(serializers.ModelSerializer): + + class Meta: + model = User + fields = ( + "id", + "first_name", + "last_name", + "email", + "alternate_email", + "skype_ID", + "description", + "profile_pic", + ) + + +class ProfileSerializer(serializers.ModelSerializer): + user_details = serializers.SerializerMethodField() + address = BillingAddressSerializer() + + def get_user_details(self, obj): + return UserSerializer(obj.user).data + + class Meta: + model = Profile + fields = ("id", 'user_details', 'role', 'address', + 'has_marketing_access', 'has_sales_access', + 'phone', 'date_of_joining', 'is_active') class ForgotPasswordSerializer(serializers.Serializer): @@ -223,7 +244,8 @@ def validate(self, data): 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.") + raise serializers.ValidationError( + "The two password fields didn't match.") return new_password2 @@ -240,29 +262,11 @@ class Meta: fields = ["id", "created_by", "file_name", "created_on", "file_path"] -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 - - class DocumentSerializer(serializers.ModelSerializer): - shared_to = UserSerializer(read_only=True, many=True) + shared_to = ProfileSerializer(read_only=True, many=True) teams = serializers.SerializerMethodField() - created_by = UserSerializer() + created_by = ProfileSerializer() + org = OrganizationSerializer() def get_teams(self, obj): return obj.teams.all().values() @@ -278,6 +282,7 @@ class Meta: "teams", "created_on", "created_by", + "org" ] @@ -286,11 +291,13 @@ def __init__(self, *args, **kwargs): request_obj = kwargs.pop("request_obj", None) super(DocumentCreateSerializer, self).__init__(*args, **kwargs) self.fields["title"].required = True + self.org = request_obj.org def validate_title(self, title): if self.instance: if ( - Document.objects.filter(title__iexact=title) + Document.objects.filter( + title__iexact=title, org=self.org) .exclude(id=self.instance.id) .exists() ): @@ -298,7 +305,7 @@ def validate_title(self, title): "Document with this Title already exists" ) else: - if Document.objects.filter(title__iexact=title).exists(): + if Document.objects.filter(title__iexact=title, org=self.org).exists(): raise serializers.ValidationError( "Document with this Title already exists" ) @@ -310,6 +317,7 @@ class Meta: "title", "document_file", "status", + "org" ] @@ -348,9 +356,10 @@ def validate_website(self, website): class APISettingsListSerializer(serializers.ModelSerializer): - created_by = UserSerializer() - lead_assigned_to = UserSerializer(read_only=True, many=True) + created_by = ProfileSerializer() + lead_assigned_to = ProfileSerializer(read_only=True, many=True) tags = serializers.SerializerMethodField() + org = OrganizationSerializer() def get_tags(self, obj): return obj.tags.all().values() @@ -365,4 +374,32 @@ class Meta: "created_by", "lead_assigned_to", "tags", + "org" ] + + +class PasswordChangeSerializer(serializers.Serializer): + old_password = serializers.CharField(max_length=100) + new_password = serializers.CharField(max_length=100) + retype_password = serializers.CharField(max_length=100) + + def __init__(self, *args, **kwargs): + super(PasswordChangeSerializer, self).__init__(*args, **kwargs) + + def validate_old_password(self, pwd): + if not check_password(pwd, self.context.get('user').password): + raise serializers.ValidationError( + "old password entered is incorrect.") + return pwd + + def validate(self, data): + if len(data.get('new_password')) < 8: + raise serializers.ValidationError( + "Password must be at least 8 characters long!") + if data.get('new_password') == data.get('old_password'): + raise serializers.ValidationError( + "New_password and old password should not be the same") + if data.get('new_password') != data.get('retype_password'): + raise serializers.ValidationError( + "New_password and Retype_password did not match.") + return data diff --git a/common/swagger_params.py b/common/swagger_params.py index 41f4ffc..578988f 100644 --- a/common/swagger_params.py +++ b/common/swagger_params.py @@ -1,5 +1,12 @@ from drf_yasg import openapi +organization_params_in_header = openapi.Parameter( + 'org', openapi.IN_HEADER, required=True, type=openapi.TYPE_INTEGER) + +organization_params = [ + organization_params_in_header, +] + login_page_params = [ openapi.Parameter( "email", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING @@ -14,6 +21,7 @@ ] change_password_params = [ + organization_params_in_header, openapi.Parameter( "old_password", openapi.IN_QUERY, @@ -38,30 +46,44 @@ ] user_update_params = [ - openapi.Parameter( - "username", openapi.IN_QUERY, type=openapi.TYPE_STRING, required=True - ), - openapi.Parameter( - "first_name", openapi.IN_QUERY, type=openapi.TYPE_STRING, required=True - ), + organization_params_in_header, + openapi.Parameter("first_name", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("last_name", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("role", openapi.IN_QUERY, type=openapi.TYPE_STRING, enum=["ADMIN", "USER"]), + openapi.Parameter("email", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("alternate_email", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("phone", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("alternate_phone", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("skype_ID", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("address_line", openapi.IN_QUERY, required=True, 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("pincode", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("country", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("description", 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("has_marketing_access", openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN), + openapi.Parameter("is_organization_admin", openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN), openapi.Parameter("teams", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] user_delete_params = [ + organization_params_in_header, openapi.Parameter( "user_id", openapi.IN_QUERY, required=True, type=openapi.TYPE_NUMBER ), ] + + registration_page_params = [ openapi.Parameter( - "username", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING + "org_name", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING + ), + openapi.Parameter( + "first_name", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING ), openapi.Parameter( "email", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING @@ -91,6 +113,7 @@ ] user_list_params = [ + organization_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( @@ -105,27 +128,31 @@ ] user_create_params = [ - 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, - ), + organization_params_in_header, openapi.Parameter("first_name", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("last_name", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("role", openapi.IN_QUERY, type=openapi.TYPE_STRING, enum=["ADMIN", "USER"]), + openapi.Parameter("email", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("alternate_email", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("phone", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("alternate_phone", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("skype_ID", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("address_line", openapi.IN_QUERY, required=True, 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("pincode", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("country", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("description", 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("has_marketing_access", openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN), + openapi.Parameter("is_organization_admin", openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN), openapi.Parameter("status", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] document_create_params = [ + organization_params_in_header, openapi.Parameter( "title", openapi.IN_QUERY, type=openapi.TYPE_STRING, required=True ), @@ -137,6 +164,7 @@ ] document_get_params = [ + organization_params_in_header, openapi.Parameter("title", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter( "status", @@ -148,6 +176,7 @@ ] document_update_params = [ + organization_params_in_header, openapi.Parameter( "title", openapi.IN_QUERY, type=openapi.TYPE_STRING, required=True ), @@ -165,6 +194,7 @@ ] users_status_params = [ + organization_params_in_header, openapi.Parameter( "status", openapi.IN_QUERY, @@ -174,6 +204,7 @@ ] api_setting_create_params = [ + organization_params_in_header, openapi.Parameter( "title", openapi.IN_QUERY, type=openapi.TYPE_STRING, required=True ), @@ -187,3 +218,12 @@ type=openapi.TYPE_STRING, ), ] + +users_delete_params = [ + organization_params_in_header, + openapi.Parameter( + "users_list", + openapi.IN_QUERY, + type=openapi.TYPE_STRING, + ), +] diff --git a/common/tasks.py b/common/tasks.py index 4184584..d618a9a 100644 --- a/common/tasks.py +++ b/common/tasks.py @@ -1,46 +1,40 @@ import datetime -from django.conf import settings from celery import Celery -from django.contrib.auth.tokens import PasswordResetTokenGenerator + +from django.conf import settings from django.core.mail import EmailMessage -from django.shortcuts import reverse from django.template.loader import render_to_string from django.utils import timezone -import six -from django.utils.encoding import force_bytes, force_text -from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode +from django.contrib.auth.tokens import default_token_generator from common.models import Comment, Profile, User from common.token_generator import account_activation_token -from django.contrib.auth.tokens import default_token_generator app = Celery("redis://") @app.task def send_email_to_new_user( - user_email, created_by, domain="demo.django-crm.io", protocol="http" + profile_id, org_id, domain="demo.django-crm.io", protocol="http" ): """ Send Mail To Users When their account is created """ + + profile_obj = Profile.objects.filter(id=profile_id).last() + user_obj = profile_obj.user - user_obj = User.objects.filter(email=user_email).first() - user_obj.is_active = False - user_obj.save() - if user_obj: + if profile_obj: context = {} - context["user_email"] = user_email - context["created_by"] = created_by + user_email = user_obj.email context["url"] = protocol + "://" + domain context["uid"] = (urlsafe_base64_encode(force_bytes(user_obj.pk)),) context["token"] = account_activation_token.make_token(user_obj) time_delta_two_hours = datetime.datetime.strftime( timezone.now() + datetime.timedelta(hours=2), "%Y-%m-%d-%H-%M-%S" ) - context["token"] = context["token"] activation_key = context["token"] + time_delta_two_hours - profile_obj = Profile.objects.create( - user=user_obj, activation_key=activation_key - ) + profile_obj.activation_key = activation_key profile_obj.save() context["complete_url"] = context[ "url" @@ -54,7 +48,7 @@ def send_email_to_new_user( subject = "Welcome to Django CRM" html_content = render_to_string("user_status_in.html", context=context) if recipients: - msg = EmailMessage(subject, html_content, to=recipients) + msg = EmailMessage(subject, html_content, from_email=settings.DEFAULT_FROM_EMAIL, to=recipients) msg.content_subtype = "html" msg.send() @@ -120,11 +114,12 @@ def send_email_user_mentions( recipient, ] context["mentioned_user"] = recipient - html_content = render_to_string("comment_email.html", context=context) + html_content = render_to_string( + "comment_email.html", context=context) msg = EmailMessage( subject, html_content, - from_email=comment.commented_by.email, + from_email=settings.DEFAULT_FROM_EMAIL, to=recipients_list, ) msg.content_subtype = "html" @@ -163,7 +158,7 @@ def send_email_user_status( recipients = [] recipients.append(user.email) if recipients: - msg = EmailMessage(subject, html_content, to=recipients) + msg = EmailMessage(subject, html_content, from_email=settings.DEFAULT_FROM_EMAIL, to=recipients) msg.content_subtype = "html" msg.send() @@ -181,9 +176,10 @@ 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 = EmailMessage(subject, html_content, from_email=settings.DEFAULT_FROM_EMAIL, to=recipients) msg.content_subtype = "html" msg.send() @@ -224,7 +220,7 @@ def resend_activation_link_to_user( subject = "Welcome to Django CRM" html_content = render_to_string("user_status_in.html", context=context) if recipients: - msg = EmailMessage(subject, html_content, to=recipients) + msg = EmailMessage(subject, html_content, from_email=settings.DEFAULT_FROM_EMAIL, to=recipients) msg.content_subtype = "html" msg.send() @@ -246,10 +242,10 @@ def send_email_to_reset_password( ] + "/auth/reset-password/{uidb64}/{token}/".format( uidb64=context["uid"][0], token=context["token"] ) + subject = "Set a New Password" recipients = [] recipients.append(user_email) - subject = "Password Reset" - html_content = render_to_string("password_reset_email.html", context=context) + html_content = render_to_string("registration/password_reset_email.html", context=context) if recipients: msg = EmailMessage( subject, html_content, from_email=settings.DEFAULT_FROM_EMAIL, to=recipients diff --git a/common/urls.py b/common/urls.py index a844b6c..f7988a8 100644 --- a/common/urls.py +++ b/common/urls.py @@ -9,6 +9,7 @@ path("dashboard/", views.ApiHomeView.as_view()), path("auth/register/", views.RegistrationView.as_view()), path("auth/login/", views.LoginView.as_view()), + path("auth/companies-list/", views.OrganizationListView.as_view()), path("profile/", views.ProfileView.as_view()), path("users/get-teams-and-users/", views.GetTeamsAndUsersView.as_view()), path("profile/change-password/", views.ChangePasswordView.as_view()), @@ -29,4 +30,5 @@ path("api-settings/", views.DomainList.as_view()), path("api-settings//", views.DomainDetailView.as_view()), path("users//status/", views.UserStatusView.as_view()), + # path("delete_users/", views.UsersDelete.as_view()) ] diff --git a/common/utils.py b/common/utils.py index cd59ff5..26181b6 100644 --- a/common/utils.py +++ b/common/utils.py @@ -18,15 +18,15 @@ def jwt_payload_handler(user): "id": user.pk, # 'name': user.name, "email": user.email, - "role": user.role, - "has_sales_access": user.has_sales_access, - "has_marketing_access": user.has_marketing_access, + # "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_admin": user.is_admin, "is_staff": user.is_staff, # "date_joined" } diff --git a/common/views.py b/common/views.py index b40082b..b3b7a84 100644 --- a/common/views.py +++ b/common/views.py @@ -1,5 +1,6 @@ from django.conf import settings from drf_yasg.utils import swagger_auto_schema +from django.contrib.sites.shortcuts import get_current_site from django.shortcuts import get_object_or_404, redirect from rest_framework import status from accounts.serializer import AccountSerializer @@ -17,10 +18,11 @@ from teams.models import Teams from common.utils import ROLES from common.serializer import ( - RegisterUserSerializer, + RegisterOrganizationSerializer, CreateUserSerializer, + PasswordChangeSerializer ) -from common.models import User, Company, Document, APISettings +from common.models import User, Org, Document, APISettings, Profile from common.tasks import ( resend_activation_link_to_user, send_email_to_new_user, @@ -49,10 +51,7 @@ from common.models import Profile from django.utils import timezone from django.conf import settings - - -def index(request): - return redirect("/app/") +from common.utils import COUNTRIES class GetTeamsAndUsersView(APIView): @@ -61,19 +60,19 @@ class GetTeamsAndUsersView(APIView): permission_classes = (IsAuthenticated,) @swagger_auto_schema( - tags=["Users"], + tags=["Users"], manual_parameters=swagger_params.organization_params ) def get(self, request, *args, **kwargs): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: return Response( {"error": True, "errors": "Permission Denied"}, status=status.HTTP_403_FORBIDDEN, ) data = {} - teams = Teams.objects.filter.all() + teams = Teams.objects.filter(org=request.org) teams_data = TeamsSerializer(teams, many=True).data - users = User.objects.filter(is_active=True) - users_data = UserSerializer(users, many=True).data + users = Profile.objects.filter(is_active=True, org=request.org) + users_data = ProfileSerializer(users, many=True).data data["teams"] = teams_data data["users_data"] = users_data return Response(data) @@ -84,31 +83,36 @@ class UserDetailView(APIView): permission_classes = (IsAuthenticated,) def get_object(self, pk): - user = get_object_or_404(User, pk=pk) - return user + profile = get_object_or_404(Profile, pk=pk) + return profile @swagger_auto_schema( - tags=["Users"], + tags=["Users"], manual_parameters=swagger_params.organization_params ) def get(self, request, pk, format=None): user_obj = self.get_object(pk) if ( - self.request.user.role != "ADMIN" - and not self.request.user.is_superuser - and self.request.user.id != user_obj.id + self.request.profile.role != "ADMIN" + and not self.request.profile.is_admin + and self.request.profile.id != user_obj.id ): return Response( {"error": True, "errors": "Permission Denied"}, status=status.HTTP_403_FORBIDDEN, ) + if user_obj.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_404_NOT_FOUND, + ) users_data = [] - for each in User.objects.all(): + for each in Profile.objects.filter(org=request.org, is_active=True): assigned_dict = {} assigned_dict["id"] = each.id - assigned_dict["name"] = each.username + assigned_dict["name"] = each.user.first_name users_data.append(assigned_dict) context = {} - context["user_obj"] = UserSerializer(user_obj).data + context["user_obj"] = ProfileSerializer(user_obj).data opportunity_list = Opportunity.objects.filter(assigned_to=user_obj) context["opportunity_list"] = OpportunitySerializer( opportunity_list, many=True @@ -120,6 +124,7 @@ def get(self, request, pk, format=None): context["assigned_data"] = users_data comments = user_obj.user_comments.all() context["comments"] = CommentSerializer(comments, many=True).data + context["countries"] = COUNTRIES return Response( {"error": False, "data": context}, status=status.HTTP_200_OK, @@ -129,30 +134,60 @@ def get(self, request, pk, format=None): tags=["Users"], manual_parameters=swagger_params.user_update_params ) def put(self, request, pk, format=None): - params = request.query_params if len(request.data) == 0 else request.data - user = self.get_object(pk) + params = request.query_params if len( + request.data) == 0 else request.data + profile = self.get_object(pk) + address_obj = profile.address if ( - self.request.user.role != "ADMIN" - and not self.request.user.is_superuser - and self.request.user.id != user.id + self.request.profile.role != "ADMIN" + and not self.request.user.is_admin + and self.request.profile.id != profile.id ): return Response( {"error": True, "errors": "Permission Denied"}, status=status.HTTP_403_FORBIDDEN, ) - serializer = CreateUserSerializer(user, data=params, request_user=request.user) + + if profile.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_404_NOT_FOUND, + ) + serializer = CreateUserSerializer( + data=params, instance=profile.user, org=request.org) + address_serializer = BillingAddressSerializer( + data=params, instance=address_obj) + profile_serializer = CreateProfileSerializer( + data=params, instance=profile) + data = {} + if not serializer.is_valid(): + data["contact_errors"] = serializer.errors + if not address_serializer.is_valid(): + data["address_errors"] = (address_serializer.errors,) + if not profile_serializer.is_valid(): + data["profile_errors"] = (profile_serializer.errors,) + if data: + data["error"] = True + return Response( + data, + status=status.HTTP_400_BAD_REQUEST, + ) if serializer.is_valid(): + address_obj = address_serializer.save() user = serializer.save() - if self.request.user.role == "ADMIN": + user.username = user.first_name + user.save() + profile = profile_serializer.save() + if self.request.profile.role == "ADMIN": if params.getlist("teams"): - user_teams = user.user_teams.all() + user_teams = profile.user_teams.all() - team_obj = Teams.objects.all() + team_obj = Teams.objects.filter(org=request.org) for team in params.getlist("teams"): try: team_obj = team_obj.filter(id=team).first() if team_obj != user_teams: - team_obj.users.add(user) + team_obj.users.add(profile) except: return Response( {"detail": "No such Team Available"}, @@ -168,25 +203,25 @@ def put(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["Users"], + tags=["Users"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: return Response( {"error": True, "errors": "Permission Denied"}, status=status.HTTP_403_FORBIDDEN, ) self.object = self.get_object(pk) - if self.object.id == request.user.id: + if self.object.id == request.profile.id: return Response( {"error": True, "errors": "Permission Denied"}, status=status.HTTP_403_FORBIDDEN, ) - deleted_by = self.request.user.email + deleted_by = self.request.profile.user.email send_email_user_delete.delay( - self.object.email, + self.object.user.email, deleted_by=deleted_by, - domain=settings.Domain, + domain=settings.DOMAIN_NAME, protocol=request.scheme, ) self.object.delete() @@ -203,39 +238,21 @@ class ChangePasswordView(APIView): 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 - old_password = params.get("old_password", None) - new_password = params.get("new_password", None) - retype_password = params.get("retype_password", None) - errors = {} - 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: + params = request.query_params if len( + request.data) == 0 else request.data + context = {'user': request.user} + serializer = PasswordChangeSerializer(data=params, context=context) + if serializer.is_valid(): + user = request.user + user.set_password(params.get('new_password')) + user.save() return Response( - {"error": True, "errors": errors}, - status=status.HTTP_400_BAD_REQUEST, + {"error": False, "message": "Password Changed Successfully"}, + status=status.HTTP_200_OK, ) - user = request.user - user.set_password(new_password) - user.save() return Response( - {"error": False, "message": "Password Changed Successfully"}, - status=status.HTTP_200_OK, + {"error": True, "errors": serializer.errors}, + status=status.HTTP_400_BAD_REQUEST, ) @@ -246,33 +263,34 @@ class ApiHomeView(APIView): permission_classes = (IsAuthenticated,) @swagger_auto_schema( - tags=["dashboard"], + tags=["dashboard"], manual_parameters=swagger_params.organization_params ) def get(self, request, format=None): - - accounts = Account.objects.filter(status="open") - contacts = Contact.objects.all() - leads = Lead.objects.all().exclude(Q(status="converted") | Q(status="closed")) - opportunities = Opportunity.objects.all() - - if self.request.user.role == "ADMIN" or self.request.user.is_superuser: + accounts = Account.objects.filter( + status="open", org=request.org) + contacts = Contact.objects.filter(org=request.org) + leads = Lead.objects.filter(org=request.org).exclude( + Q(status="converted") | Q(status="closed")) + opportunities = Opportunity.objects.filter(org=request.org) + + if self.request.profile.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) + Q(assigned_to__id__in=[self.request.profile.id]) + | Q(created_by=self.request.profile.id) ) contacts = contacts.filter( - Q(assigned_to__id__in=[self.request.user.id]) - | Q(created_by=self.request.user.id) + Q(assigned_to__id__in=[self.request.profile.id]) + | Q(created_by=self.request.profile.id) ) leads = leads.filter( - Q(assigned_to__id__in=[self.request.user.id]) - | Q(created_by=self.request.user.id) + Q(assigned_to__id__in=[self.request.profile.id]) + | Q(created_by=self.request.profile.id) ).exclude(status="closed") opportunities = opportunities.filter( - Q(assigned_to__id__in=[self.request.user.id]) - | Q(created_by=self.request.user.id) + Q(assigned_to__id__in=[self.request.profile.id]) + | Q(created_by=self.request.profile.id) ) context = {} context["accounts_count"] = accounts.count() @@ -282,7 +300,8 @@ def get(self, request, format=None): context["accounts"] = AccountSerializer(accounts, many=True).data context["contacts"] = ContactSerializer(contacts, many=True).data context["leads"] = LeadSerializer(leads, many=True).data - context["opportunities"] = OpportunitySerializer(opportunities, many=True).data + context["opportunities"] = OpportunitySerializer( + opportunities, many=True).data return Response(context, status=status.HTTP_200_OK) @@ -293,19 +312,19 @@ class LoginView(APIView): 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 - username = params.get("email", None) + params = request.query_params if len( + request.data) == 0 else request.data + email = params.get("email", None) password = params.get("password", None) - if not username: - username_field = "User Name/Email" - msg = _('Must include "{username_field}"') - msg = msg.format(username_field=username_field) - return Response( - {"error": True, "errors": msg}, - status=status.HTTP_400_BAD_REQUEST, - ) + errors = {} + if not email: + errors['email'] = ['This field is required'] + if not password: + errors['password'] = ['This field is required'] + if errors: + return Response({'error': True, 'errors': errors}, status=status.HTTP_400_BAD_REQUEST) - user = User.objects.filter(email=username).first() + user = User.objects.filter(email=email).first() if not user: return Response( {"error": True, "errors": "user not avaliable in our records"}, @@ -340,41 +359,47 @@ class RegistrationView(APIView): 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 - user_serializer = RegisterUserSerializer( - data=params, - request_user=request.user, - ) - errors = {} - if not user_serializer.is_valid(): - errors.update(user_serializer.errors) - if errors: - return Response( - {"error": True, "errors": errors}, - status=status.HTTP_400_BAD_REQUEST, - ) - if user_serializer.is_valid(): - user = user_serializer.save( - role="ADMIN", - is_superuser=False, - has_marketing_access=True, - has_sales_access=True, - is_admin=True, - ) - if params.get("password"): - user.set_password(params.get("password")) + params = request.query_params if len( + request.data) == 0 else request.data + + form = RegisterOrganizationSerializer(data=params) + if form.is_valid(): + org_name = params.get('org_name') + email = params.get('email') + first_name = params.get('first_name') + password = params.get('password') + user, created = User.objects.get_or_create(email=email) + user.first_name = first_name + user.set_password(password) + user.save() + org = Org.objects.create(name=org_name) + user.set_password(password) user.save() - protocol = request.scheme - send_email_to_new_user.delay( - user.email, - user.email, - domain=settings.Domain, - protocol=protocol, + profile = Profile.objects.create( + user=user, org=org, date_of_joining=timezone.now() ) + if created: + user.is_active = False + user.save() + protocol = request.scheme + current_site = get_current_site(self.request) + send_email_to_new_user.delay( + profile.id, + org.id, + domain=current_site.domain, + protocol=protocol, + ) + return Response( + {"error": False, "message": "User created Successfully."}, + status=status.HTTP_200_OK, + ) return Response( - {"error": False, "message": "User created Successfully."}, + {"error": False, + "message": "Please login to check the account"}, status=status.HTTP_200_OK, ) + return Response({'error': True, 'errors': form.errors}, + status=status.HTTP_400_BAD_REQUEST) class ProfileView(APIView): @@ -382,11 +407,11 @@ class ProfileView(APIView): permission_classes = (IsAuthenticated,) @swagger_auto_schema( - tags=["Profile"], + tags=["Profile"], manual_parameters=swagger_params.organization_params ) def get(self, request, format=None): context = {} - context["user_obj"] = UserSerializer(request.user).data + context["user_obj"] = ProfileSerializer(request.profile).data return Response(context, status=status.HTTP_200_OK) @@ -399,62 +424,83 @@ class UsersListView(APIView, LimitOffsetPagination): tags=["Users"], manual_parameters=swagger_params.user_create_params ) def post(self, request, format=None): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: return Response( {"error": True, "errors": "Permission Denied"}, status=status.HTTP_403_FORBIDDEN, ) else: - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data if params: user_serializer = CreateUserSerializer( - data=params, request_user=request.user + data=params, org=request.org ) + address_serializer = BillingAddressSerializer(data=params) + profile_serializer = CreateProfileSerializer(data=params) + data = {} + if not user_serializer.is_valid(): + data["user_errors"] = dict(user_serializer.errors) + if not profile_serializer.is_valid(): + data['profile_errors'] = profile_serializer.errors + if not address_serializer.is_valid(): + data["address_errors"] = (address_serializer.errors,) + if data: + return Response( + {"error": True, "errors": data}, + status=status.HTTP_400_BAD_REQUEST, + ) if user_serializer.is_valid(): - user = user_serializer.save() + address_obj = address_serializer.save() + user = user_serializer.save( + is_active=False, + ) + user.username = user.first_name + user.save() if params.get("password"): user.set_password(params.get("password")) - user.is_active = False - + user.save() + profile = Profile.objects.create(user=user, + date_of_joining=timezone.now(), + role=params.get( + 'role'), + address=address_obj, + org=request.org, + ), + + current_site = get_current_site(self.request) protocol = request.scheme send_email_to_new_user.delay( - user.email, - self.request.user.email, - domain=settings.Domain, + profile[0].id, + request.org.id, + domain=current_site.domain, protocol=protocol, ) return Response( {"error": False, "message": "User Created Successfully"}, status=status.HTTP_201_CREATED, ) - return Response( - {"error": True, "errors": user_serializer.errors}, - status=status.HTTP_400_BAD_REQUEST, - ) @swagger_auto_schema( tags=["Users"], manual_parameters=swagger_params.user_list_params ) def get(self, request, format=None): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: return Response( {"error": True, "errors": "Permission Denied"}, status=status.HTTP_403_FORBIDDEN, ) else: - queryset = User.objects.all() + queryset = Profile.objects.filter(org=request.org) params = ( self.request.query_params if len(self.request.data) == 0 else self.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")) + queryset = queryset.filter( + user__email__icontains=params.get("email")) if params.get("role"): queryset = queryset.filter(role=params.get("role")) if params.get("status"): @@ -465,7 +511,8 @@ def get(self, request, format=None): results_active_users = self.paginate_queryset( queryset_active_users.distinct(), self.request, view=self ) - active_users = UserSerializer(results_active_users, many=True).data + active_users = ProfileSerializer( + results_active_users, many=True).data context["per_page"] = 10 context["active_users"] = { "active_users_count": self.count, @@ -479,7 +526,8 @@ def get(self, request, format=None): results_inactive_users = self.paginate_queryset( queryset_inactive_users.distinct(), self.request, view=self ) - inactive_users = UserSerializer(results_inactive_users, many=True).data + inactive_users = ProfileSerializer( + results_inactive_users, many=True).data context["inactive_users"] = { "inactive_users_count": self.count, "next": self.get_next_link(), @@ -505,25 +553,29 @@ def get_context_data(self, **kwargs): if len(self.request.data) == 0 else self.request.data ) - queryset = self.model.objects.all() - if self.request.user.is_superuser or self.request.user.role == "ADMIN": + queryset = self.model.objects.filter(org=self.request.org) + if self.request.user.is_superuser or self.request.profile.role == "ADMIN": queryset = queryset else: - if self.request.user.documents(): - doc_ids = self.request.user.documents().values_list("id", flat=True) + if self.request.profile.documents(): + doc_ids = self.request.profile.documents().values_list("id", flat=True) shared_ids = queryset.filter( - Q(status="active") & Q(shared_to__id__in=[self.request.user.id]) + Q(status="active") & Q( + shared_to__id__in=[self.request.profile.id]) ).values_list("id", flat=True) - queryset = queryset.filter(Q(id__in=doc_ids) | Q(id__in=shared_ids)) + queryset = queryset.filter( + Q(id__in=doc_ids) | Q(id__in=shared_ids)) else: queryset = queryset.filter( - Q(status="active") & Q(shared_to__id__in=[self.request.user.id]) + Q(status="active") & Q( + shared_to__id__in=[self.request.profile.id]) ) request_post = params if request_post: if request_post.get("title"): - queryset = queryset.filter(title__icontains=request_post.get("title")) + queryset = queryset.filter( + title__icontains=request_post.get("title")) if request_post.get("status"): queryset = queryset.filter(status=request_post.get("status")) @@ -533,10 +585,13 @@ def get_context_data(self, **kwargs): ) context = {} - if self.request.user.role == "ADMIN" or self.request.user.is_superuser: - users = User.objects.filter(is_active=True).order_by("email") + profile_list = Profile.objects.filter( + is_active=True, org=self.request.org) + if self.request.profile.role == "ADMIN" or self.request.profile.is_admin: + profiles = profile_list.order_by("user__email") else: - users = User.objects.filter(role="ADMIN").order_by("email") + profiles = profile_list.filter( + role="ADMIN").order_by("user__email") search = False if ( params.get("document_file") @@ -550,7 +605,8 @@ def get_context_data(self, **kwargs): results_documents_active = self.paginate_queryset( queryset_documents_active.distinct(), self.request, view=self ) - documents_active = DocumentSerializer(results_documents_active, many=True).data + documents_active = DocumentSerializer( + results_documents_active, many=True).data context["per_page"] = 10 context["documents_active"] = { "documents_active_count": self.count, @@ -576,7 +632,7 @@ def get_context_data(self, **kwargs): "documents_inactive": documents_inactive, } - context["users"] = UserSerializer(users, many=True).data + context["users"] = ProfileSerializer(profiles, many=True).data context["status_choices"] = Document.DOCUMENT_STATUS_CHOICE return context @@ -591,17 +647,20 @@ def get(self, request, *args, **kwargs): tags=["documents"], manual_parameters=swagger_params.document_create_params ) def post(self, request, *args, **kwargs): - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data serializer = DocumentCreateSerializer(data=params, request_obj=request) if serializer.is_valid(): doc = serializer.save( - created_by=request.user, + created_by=request.profile, + org=request.org, document_file=request.FILES.get("document_file"), ) if params.get("shared_to"): assinged_to_users_ids = json.loads(params.get("shared_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter( + id=user_id, org=request.org) if user.exists(): doc.shared_to.add(user_id) else: @@ -610,11 +669,12 @@ def post(self, request, *args, **kwargs): {"error": True, "errors": "Enter Valid User"}, status=status.HTTP_400_BAD_REQUEST, ) - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter( + id=team, org=request.org) if teams_ids.exists(): doc.teams.add(team) else: @@ -642,7 +702,7 @@ def get_object(self, pk): return Document.objects.filter(id=pk).first() @swagger_auto_schema( - tags=["documents"], + tags=["documents"], manual_parameters=swagger_params.organization_params ) def get(self, request, pk, format=None): self.object = self.get_object(pk) @@ -651,10 +711,14 @@ def get(self, request, pk, format=None): {"error": True, "errors": "Document does not exist"}, status=status.HTTP_403_FORBIDDEN, ) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.object.org != self.request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN) + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: if not ( - (self.request.user == self.object.created_by) - or (self.request.user in self.object.shared_to.all()) + (self.request.profile == self.object.created_by) + or (self.request.profile in self.object.shared_to.all()) ): return Response( { @@ -663,22 +727,24 @@ def get(self, request, pk, format=None): }, status=status.HTTP_403_FORBIDDEN, ) - if request.user.role == "ADMIN" or request.user.is_superuser: - users = User.objects.filter(is_active=True).order_by("email") + profile_list = Profile.objects.filter(org=self.request.org) + if request.profile.role == "ADMIN" or request.user.is_superuser: + profiles = profile_list.order_by("user__email") else: - users = User.objects.filter(role="ADMIN").order_by("email") + profiles = profile_list.filter( + role="ADMIN").order_by("user__email") context = {} context.update( { "doc_obj": DocumentSerializer(self.object).data, "file_type_code": self.object.file_type()[1], - "users": UserSerializer(users, many=True).data, + "users": ProfileSerializer(profiles, many=True).data, } ) return Response(context, status=status.HTTP_200_OK) @swagger_auto_schema( - tags=["documents"], + tags=["documents"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): document = self.get_object(pk) @@ -687,11 +753,16 @@ def delete(self, request, pk, format=None): {"error": True, "errors": "Documdnt does not exist"}, status=status.HTTP_403_FORBIDDEN, ) + if document.org != self.request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: if ( - self.request.user != document.created_by - ): # or (self.request.user not in document.shared_to.all()): + self.request.profile != document.created_by + ): # or (self.request.profile not in document.shared_to.all()): return Response( { "error": True, @@ -710,16 +781,22 @@ def delete(self, request, pk, format=None): ) def put(self, request, pk, format=None): self.object = self.get_object(pk) - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data if not self.object: return Response( {"error": True, "errors": "Document does not exist"}, status=status.HTTP_403_FORBIDDEN, ) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.object.org != self.request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: if not ( - (self.request.user == self.object.created_by) - or (self.request.user in self.object.shared_to.all()) + (self.request.profile == self.object.created_by) + or (self.request.profile in self.object.shared_to.all()) ): return Response( { @@ -735,12 +812,14 @@ def put(self, request, pk, format=None): doc = serializer.save( document_file=request.FILES.get("document_file"), status=params.get("status"), + org=request.org ) doc.shared_to.clear() if params.get("shared_to"): assinged_to_users_ids = json.loads(params.get("shared_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter( + id=user_id, org=request.org) if user.exists(): doc.shared_to.add(user_id) else: @@ -749,12 +828,13 @@ def put(self, request, pk, format=None): status=status.HTTP_400_BAD_REQUEST, ) - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": doc.teams.clear() if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter( + id=team, org=request.org) if teams_ids.exists(): doc.teams.add(team) else: @@ -777,7 +857,8 @@ class ForgotPasswordView(APIView): tags=["Auth"], 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 + 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")) @@ -788,7 +869,7 @@ def post(self, request, format=None): ) protocol = self.request.scheme send_email_to_reset_password.delay( - user.email, protocol=protocol, domain=settings.Domain + user.email, protocol=protocol, domain=settings.DOMAIN_NAME ) data = { "error": False, @@ -806,20 +887,37 @@ class ResetPasswordView(APIView): tags=["Auth"], manual_parameters=swagger_params.reset_password_params ) def post(self, request, uid, token, 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", - } + params = request.query_params if len( + request.data) == 0 else request.data + try: + uid = force_text(urlsafe_base64_decode(uid)) + user_obj = User.objects.get(pk=uid) + if not user_obj.password: + if not user_obj.is_active: + user_obj.is_active = True + user_obj.save() + except (TypeError, ValueError, OverflowError, User.DoesNotExist): + user_obj = None + if user_obj is not None: + password1 = params.get("new_password1") + password2 = params.get("new_password2") + if password1 != password2: + return Response( + {"error": True, "errors": "The two password fields didn't match."}, + status=status.HTTP_400_BAD_REQUEST + ) + else: + user_obj.set_password(password1) + user_obj.save() + return Response( + {"error": False, + "message": "Password Updated Successfully. Please login"}, + status=status.HTTP_200_OK + ) else: - data = {"error": True, "errors": serializer.errors} - return Response(data, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": True, "errors": "Invalid Link"} + ) class UserStatusView(APIView): @@ -830,7 +928,7 @@ class UserStatusView(APIView): tags=["Users"], manual_parameters=swagger_params.users_status_params ) def post(self, request, pk, format=None): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: return Response( { "error": True, @@ -838,27 +936,31 @@ def post(self, request, pk, format=None): }, status=status.HTTP_403_FORBIDDEN, ) - params = request.query_params if len(request.data) == 0 else request.data - user = User.objects.get(id=pk) + params = request.query_params if len( + request.data) == 0 else request.data + profiles = Profile.objects.filter(org=request.org) + profile = profiles.get(id=pk) if params.get("status"): - status = params.get("status") - if status == "Active": - user.is_active = True - elif status == "Inactive": - user.is_active = False + user_status = params.get("status") + if user_status == "Active": + profile.is_active = True + elif user_status == "Inactive": + profile.is_active = False else: return Response( {"error": True, "errors": "Please enter Valid Status for user"}, status=status.HTTP_400_BAD_REQUEST, ) - user.save() + profile.save() context = {} - users_Active = User.objects.filter(is_active=True) - users_Inactive = User.objects.filter(is_active=False) - context["Users_Active"] = UserSerializer(users_Active, many=True).data - context["Users_Inactive"] = UserSerializer(users_Inactive, many=True).data + active_profiles = profiles.filter(is_active=True) + inactive_profiles = profiles.filter(is_active=False) + context["active_profiles"] = ProfileSerializer( + active_profiles, many=True).data + context["inactive_profiles"] = ProfileSerializer( + inactive_profiles, many=True).data return Response(context) @@ -868,16 +970,17 @@ class DomainList(APIView): permission_classes = (IsAuthenticated,) @swagger_auto_schema( - tags=["Settings"], + tags=["Settings"], manual_parameters=swagger_params.organization_params ) def get(self, request, *args, **kwargs): - api_settings = APISettings.objects.all() - users = User.objects.filter(is_active=True).order_by("email") + api_settings = APISettings.objects.filter(org=request.org) + users = Profile.objects.filter( + is_active=True, org=request.org).order_by("user__email") return Response( { "error": False, "api_settings": APISettingsListSerializer(api_settings, many=True).data, - "users": UserSerializer(users, many=True).data, + "users": ProfileSerializer(users, many=True).data, }, status=status.HTTP_200_OK, ) @@ -896,7 +999,8 @@ def post(self, request, *args, **kwargs): assign_to_list = json.loads(params.get("lead_assigned_to")) serializer = APISettingsSerializer(data=params) if serializer.is_valid(): - settings_obj = serializer.save(created_by=request.user) + settings_obj = serializer.save( + created_by=request.profile, org=request.org) if params.get("tags"): tags = json.loads(params.get("tags")) for tag in tags: @@ -925,12 +1029,13 @@ def get_object(self, pk): return self.model.objects.get(pk=pk) @swagger_auto_schema( - tags=["Settings"], + tags=["Settings"], manual_parameters=swagger_params.organization_params ) def get(self, request, pk, format=None): api_setting = self.get_object(pk) return Response( - {"error": False, "domain": APISettingsListSerializer(api_setting).data}, + {"error": False, "domain": APISettingsListSerializer( + api_setting).data}, status=status.HTTP_200_OK, ) @@ -971,7 +1076,7 @@ def put(self, request, pk, **kwargs): ) @swagger_auto_schema( - tags=["Settings"], + tags=["Settings"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, **kwargs): api_setting = self.get_object(pk) @@ -994,7 +1099,7 @@ def post(self, request, uid, token, activation_key, format=None): protocol = request.scheme resend_activation_link_to_user.delay( profile.user.email, - domain=settings.Domain, + domain=settings.DOMAIN_NAME, protocol=protocol, ) return Response( @@ -1034,7 +1139,8 @@ class ResendActivationLinkView(APIView): tags=["Auth"], 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 + params = request.query_params if len( + request.data) == 0 else request.data user = get_object_or_404(User, email=params.get("email")) if user.is_active: return Response( @@ -1044,7 +1150,7 @@ def post(self, request, format=None): protocol = request.scheme resend_activation_link_to_user.delay( user.email, - domain=settings.Domain, + domain=settings.DOMAIN_NAME, protocol=protocol, ) data = { @@ -1052,3 +1158,52 @@ def post(self, request, format=None): "message": "Please use the Activation link sent to your mail to activate account.", } return Response(data, status=status.HTTP_200_OK) + + +class OrganizationListView(APIView, LimitOffsetPagination): + + authentication_classes = (JSONWebTokenAuthentication,) + permission_classes = (IsAuthenticated,) + + @swagger_auto_schema( + tags=["Auth"] + ) + def get(self, request): + profiles = Profile.objects.filter(user=request.user, is_active=True) + companies = Org.objects.filter( + id__in=profiles.values_list('org', flat=True)) + return Response({'error': False, + 'companies': OrganizationSerializer( + companies, many=True).data}, + status=status.HTTP_200_OK) + + +# class UsersDelete(APIView): + +# authentication_classes = (JSONWebTokenAuthentication,) +# permission_classes = (IsAuthenticated,) + +# @swagger_auto_schema( +# tags=["Users"], manual_parameters=swagger_params.users_delete_params, +# operation_description="To delete multiple users", +# ) +# def post(self, request, *args, **kwargs): +# if self.request.user.role == "ADMIN" or self.request.user.is_superuser: +# params = request.query_params if len(request.data) == 0 else request.data +# users_list = json.loads(params.get("users_list")) +# users = User.objects.filter( +# id__in = users_list +# ) +# users.delete() +# return Response( +# { +# "error": False, +# "message": "Users deleted successfully", +# }, +# status=status.HTTP_200_OK, +# ) +# else: +# return Response( +# {"error": True, "errors": "Permission Denied"}, +# status=status.HTTP_403_FORBIDDEN, +# ) diff --git a/contacts/migrations/0007_auto_20210716_1554.py b/contacts/migrations/0007_auto_20210716_1554.py new file mode 100644 index 0000000..844b7d2 --- /dev/null +++ b/contacts/migrations/0007_auto_20210716_1554.py @@ -0,0 +1,84 @@ +# Generated by Django 3.2.5 on 2021-07-16 10:24 + +from django.db import migrations, models +import phonenumber_field.modelfields + + +class Migration(migrations.Migration): + + dependencies = [ + ('contacts', '0006_remove_contact_company'), + ] + + operations = [ + migrations.RenameField( + model_name='contact', + old_name='phone', + new_name='mobile_number', + ), + migrations.RenameField( + model_name='contact', + old_name='email', + new_name='primary_email', + ), + migrations.AddField( + model_name='contact', + name='date_of_birth', + field=models.DateField(null=True), + ), + migrations.AddField( + model_name='contact', + name='department', + field=models.CharField(max_length=255, null=True, verbose_name='Department'), + ), + migrations.AddField( + model_name='contact', + name='do_not_call', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='contact', + name='facebook_url', + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name='contact', + name='language', + field=models.CharField(max_length=255, null=True, verbose_name='Language'), + ), + migrations.AddField( + model_name='contact', + name='linked_in_url', + field=models.URLField(blank=True, null=True), + ), + migrations.AddField( + model_name='contact', + name='organization', + field=models.CharField(max_length=255, null=True, verbose_name='Organization'), + ), + migrations.AddField( + model_name='contact', + name='salutation', + field=models.CharField(max_length=255, null=True, verbose_name='Salutation'), + ), + migrations.AddField( + model_name='contact', + name='secondary_email', + field=models.EmailField(max_length=254, null=True), + ), + migrations.AddField( + model_name='contact', + name='secondary_number', + field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None), + ), + migrations.AddField( + model_name='contact', + name='title', + field=models.CharField(max_length=255, null=True, verbose_name='Title'), + ), + migrations.AddField( + model_name='contact', + name='twitter_username', + field=models.CharField(max_length=255, null=True), + ), + ] diff --git a/contacts/migrations/0008_auto_20210913_1918.py b/contacts/migrations/0008_auto_20210913_1918.py new file mode 100644 index 0000000..9d8e968 --- /dev/null +++ b/contacts/migrations/0008_auto_20210913_1918.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2 on 2021-09-13 13:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0034_auto_20210913_1918'), + ('contacts', '0007_auto_20210716_1554'), + ] + + operations = [ + migrations.AddField( + model_name='contact', + name='company', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.company'), + ), + migrations.AlterField( + model_name='contact', + name='assigned_to', + field=models.ManyToManyField(related_name='contact_assigned_users', to='common.Profile'), + ), + migrations.AlterField( + model_name='contact', + name='created_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contact_created_by', to='common.profile'), + ), + ] diff --git a/contacts/migrations/0009_rename_company_contact_org.py b/contacts/migrations/0009_rename_company_contact_org.py new file mode 100644 index 0000000..68303fb --- /dev/null +++ b/contacts/migrations/0009_rename_company_contact_org.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-09-22 12:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('contacts', '0008_auto_20210913_1918'), + ] + + operations = [ + migrations.RenameField( + model_name='contact', + old_name='company', + new_name='org', + ), + ] diff --git a/contacts/migrations/0010_auto_20211006_1251.py b/contacts/migrations/0010_auto_20211006_1251.py new file mode 100644 index 0000000..fb1fdaa --- /dev/null +++ b/contacts/migrations/0010_auto_20211006_1251.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.7 on 2021-10-06 07:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contacts', '0009_rename_company_contact_org'), + ] + + operations = [ + migrations.AlterField( + model_name='contact', + name='date_of_birth', + field=models.DateField(blank=True, null=True), + ), + migrations.AlterField( + model_name='contact', + name='salutation', + field=models.CharField(blank=True, default='', max_length=255, verbose_name='Salutation'), + ), + migrations.AlterField( + model_name='contact', + name='secondary_email', + field=models.EmailField(blank=True, default='', max_length=254), + ), + migrations.AlterField( + model_name='contact', + name='title', + field=models.CharField(blank=True, default='', max_length=255, verbose_name='Title'), + ), + ] diff --git a/contacts/models.py b/contacts/models.py index 154883e..ed17f16 100644 --- a/contacts/models.py +++ b/contacts/models.py @@ -2,16 +2,25 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.models import Address, User +from common.models import Address, Org, Profile from phonenumber_field.modelfields import PhoneNumberField from teams.models import Teams class Contact(models.Model): + salutation = models.CharField(_("Salutation"), max_length=255, default="", blank=True) first_name = models.CharField(_("First name"), max_length=255) last_name = models.CharField(_("Last name"), max_length=255) - email = models.EmailField(unique=True) - phone = PhoneNumberField(null=True, unique=True) + date_of_birth = models.DateField(null=True, blank=True) + organization = models.CharField(_("Organization"), max_length=255,null=True ) + title = models.CharField(_("Title"), max_length=255, default="", blank=True) + primary_email = models.EmailField(unique=True) + secondary_email = models.EmailField(default="", blank=True) + mobile_number = PhoneNumberField(null=True, unique=True) + secondary_number = PhoneNumberField(null=True) + department = models.CharField(_("Department"), max_length=255, null=True) + language = models.CharField(_("Language"), max_length=255, null=True) + do_not_call = models.BooleanField(default=False) address = models.ForeignKey( Address, related_name="adress_contacts", @@ -20,13 +29,20 @@ class Contact(models.Model): null=True, ) description = models.TextField(blank=True, null=True) - assigned_to = models.ManyToManyField(User, related_name="contact_assigned_users") + linked_in_url = models.URLField(blank=True, null=True) + facebook_url = models.URLField(blank=True, null=True) + twitter_username = models.CharField(max_length=255, null=True) created_by = models.ForeignKey( - User, related_name="contact_created_by", on_delete=models.SET_NULL, null=True + Profile, related_name="contact_created_by", on_delete=models.SET_NULL, null=True ) created_on = models.DateTimeField(_("Created on"), auto_now_add=True) is_active = models.BooleanField(default=False) + assigned_to = models.ManyToManyField(Profile, related_name="contact_assigned_users") teams = models.ManyToManyField(Teams, related_name="contact_teams") + org = models.ForeignKey( + Org, on_delete=models.SET_NULL, null=True, blank=True + ) + def __str__(self): return self.first_name @@ -41,18 +57,18 @@ def created_on_arrow(self): @property def get_team_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) - return User.objects.filter(id__in=team_user_ids) + return Profile.objects.filter(id__in=team_user_ids) @property def get_team_and_assigned_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = team_user_ids + assigned_user_ids - return User.objects.filter(id__in=user_ids) + return Profile.objects.filter(id__in=user_ids) @property def get_assigned_users_not_in_teams(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = set(assigned_user_ids) - set(team_user_ids) - return User.objects.filter(id__in=list(user_ids)) + return Profile.objects.filter(id__in=list(user_ids)) diff --git a/contacts/serializer.py b/contacts/serializer.py index be8a084..ad18e4e 100644 --- a/contacts/serializer.py +++ b/contacts/serializer.py @@ -1,33 +1,48 @@ from rest_framework import serializers from contacts.models import Contact from common.serializer import ( - UserSerializer, + ProfileSerializer, BillingAddressSerializer, AttachmentsSerializer, + OrganizationSerializer ) from teams.serializer import TeamsSerializer class ContactSerializer(serializers.ModelSerializer): - created_by = UserSerializer() + created_by = ProfileSerializer() teams = TeamsSerializer(read_only=True, many=True) - assigned_to = UserSerializer(read_only=True, many=True) + assigned_to = ProfileSerializer(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) + get_team_users = ProfileSerializer(read_only=True, many=True) + get_team_and_assigned_users = ProfileSerializer(read_only=True, many=True) + get_assigned_users_not_in_teams = ProfileSerializer(read_only=True, many=True) contact_attachment = AttachmentsSerializer(read_only=True, many=True) + date_of_birth = serializers.DateField() + org = OrganizationSerializer() class Meta: model = Contact fields = ( "id", + "salutation", "first_name", "last_name", - "email", - "phone", + "date_of_birth", + "organization", + "title", + "primary_email", + "secondary_email", + "mobile_number", + "secondary_number", + "department", + "language", + "do_not_call", "address", "description", + "linked_in_url", + "facebook_url", + "twitter_username", "contact_attachment", "assigned_to", "created_by", @@ -38,6 +53,7 @@ class Meta: "get_team_users", "get_team_and_assigned_users", "get_assigned_users_not_in_teams", + "org" ) @@ -46,11 +62,12 @@ def __init__(self, *args, **kwargs): contact_view = kwargs.pop("contact", False) request_obj = kwargs.pop("request_obj", None) super(CreateContactSerializer, self).__init__(*args, **kwargs) + self.org = request_obj.org def validate_first_name(self, first_name): if self.instance: if ( - Contact.objects.filter(first_name__iexact=first_name) + Contact.objects.filter(first_name__iexact=first_name, org=self.org) .exclude(id=self.instance.id) .exists() ): @@ -59,7 +76,7 @@ def validate_first_name(self, first_name): ) else: - if Contact.objects.filter(first_name__iexact=first_name).exists(): + if Contact.objects.filter(first_name__iexact=first_name, org=self.org).exists(): raise serializers.ValidationError( "Contact already exists with this name" ) @@ -68,10 +85,21 @@ def validate_first_name(self, first_name): class Meta: model = Contact fields = ( + "salutation", "first_name", "last_name", - "email", - "phone", + "organization", + "title", + "primary_email", + "secondary_email", + "mobile_number", + "secondary_number", + "department", + "language", + "do_not_call", "address", "description", + "linked_in_url", + "facebook_url", + "twitter_username", ) diff --git a/contacts/swagger_params.py b/contacts/swagger_params.py index 661b39c..cf2346e 100644 --- a/contacts/swagger_params.py +++ b/contacts/swagger_params.py @@ -1,12 +1,24 @@ from drf_yasg import openapi +organization_params_in_header = openapi.Parameter( + 'org', openapi.IN_HEADER, required=True, type=openapi.TYPE_INTEGER) + +organization_params = [ + organization_params_in_header, +] + contact_list_get_params = [ + organization_params_in_header, openapi.Parameter("name", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("city", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("assigned_to", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] contact_create_post_params = [ + organization_params_in_header, + openapi.Parameter( + "salutation", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING + ), openapi.Parameter( "first_name", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING ), @@ -14,24 +26,56 @@ "last_name", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING ), openapi.Parameter( - "phone", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING + "date_of_birth", + openapi.IN_QUERY, + type=openapi.TYPE_STRING, + format="date", + example="2021-01-01", ), openapi.Parameter( - "email", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING + "organization", openapi.IN_QUERY, type=openapi.TYPE_STRING ), - openapi.Parameter("teams", openapi.IN_QUERY, type=openapi.TYPE_STRING), - openapi.Parameter("assigned_to", openapi.IN_QUERY, type=openapi.TYPE_STRING), - openapi.Parameter("address_line", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter( + "title", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING + ), + openapi.Parameter( + "primary_email", openapi.IN_QUERY, type=openapi.TYPE_STRING + ), + openapi.Parameter( + "secondary_email", openapi.IN_QUERY, type=openapi.TYPE_STRING + ), + openapi.Parameter( + "mobile_number", openapi.IN_QUERY, type=openapi.TYPE_STRING + ), + openapi.Parameter( + "secondary_number", openapi.IN_QUERY, type=openapi.TYPE_STRING + ), + openapi.Parameter( + "department", openapi.IN_QUERY, type=openapi.TYPE_STRING + ), + openapi.Parameter( + "language", openapi.IN_QUERY, type=openapi.TYPE_STRING + ), + openapi.Parameter( + "do_not_call", openapi.IN_QUERY, type=openapi.TYPE_BOOLEAN + ), + openapi.Parameter("address_line", openapi.IN_QUERY, required=True, 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("pincode", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("country", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("description", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("linked_in_url", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("facebook_url", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("twitter_username", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("teams", openapi.IN_QUERY, type=openapi.TYPE_STRING), + openapi.Parameter("assigned_to", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("contact_attachment", openapi.IN_QUERY, type=openapi.TYPE_FILE), ] contact_detail_get_params = [ + organization_params_in_header, openapi.Parameter( "contact_attachment", openapi.IN_QUERY, diff --git a/contacts/tasks.py b/contacts/tasks.py index 3dd3a15..5ce5e19 100644 --- a/contacts/tasks.py +++ b/contacts/tasks.py @@ -3,7 +3,7 @@ from django.shortcuts import reverse from django.template.loader import render_to_string -from common.models import User +from common.models import User, Profile from contacts.models import Contact app = Celery("redis://") @@ -16,14 +16,14 @@ def send_email_to_assigned_user( """ Send Mail To Users When they are assigned to a contact """ contact = Contact.objects.get(id=contact_id) created_by = contact.created_by - for user in recipients: + for profile_id in recipients: recipients_list = [] - user = User.objects.filter(id=user, is_active=True).first() - if user: - recipients_list.append(user.email) + profile = Profile.objects.filter(id=profile_id, is_active=True).first() + if profile: + recipients_list.append(profile.user.email) context = {} context["url"] = protocol + "://" + domain - context["user"] = user + context["user"] = profile.user context["contact"] = contact context["created_by"] = created_by subject = "Assigned a contact for you." diff --git a/contacts/views.py b/contacts/views.py index b6badd8..ac8884f 100644 --- a/contacts/views.py +++ b/contacts/views.py @@ -1,6 +1,6 @@ from rest_framework import status from common.models import User, Attachments, Comment -from contacts.models import Contact +from contacts.models import Contact, Profile from teams.models import Teams from django.db.models import Q from django.shortcuts import get_object_or_404 @@ -14,7 +14,7 @@ from rest_framework.views import APIView from common.utils import COUNTRIES from common.serializer import ( - UserSerializer, + ProfileSerializer, CommentSerializer, AttachmentsSerializer, BillingAddressSerializer, @@ -38,12 +38,11 @@ def get_context_data(self, **kwargs): if len(self.request.data) == 0 else self.request.data ) - queryset = self.model.objects.all().order_by("-created_on") - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + queryset = self.model.objects.filter(org=self.request.org).order_by("-created_on") + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: queryset = queryset.filter( - Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user) + Q(assigned_to__in=[self.request.profile]) | Q(created_by=self.request.profile) ).distinct() - request_post = params if request_post: if request_post.get("name"): @@ -55,9 +54,9 @@ def get_context_data(self, **kwargs): address__city__icontains=request_post.get("city") ) if request_post.get("phone"): - queryset = queryset.filter(phone__icontains=request_post.get("phone")) + queryset = queryset.filter(mobile_number__icontains=request_post.get("phone")) if request_post.get("email"): - queryset = queryset.filter(email__icontains=request_post.get("email")) + queryset = queryset.filter(primary_email__icontains=request_post.get("email")) if request_post.getlist("assigned_to"): queryset = queryset.filter( assigned_to__id__in=json.loads(request_post.get("assigned_to")) @@ -74,10 +73,10 @@ def get_context_data(self, **kwargs): ): search = True context["search"] = search - results_contact = self.paginate_queryset( queryset.distinct(), self.request, view=self ) + profiles = Profile.objects.filter(org=self.request.org, is_active=True) contacts = ContactSerializer(results_contact, many=True).data context["per_page"] = 10 context.update( @@ -89,14 +88,13 @@ def get_context_data(self, **kwargs): } ) context["contact_obj_list"] = contacts - context["users"] = UserSerializer( - User.objects.filter(is_active=True).order_by("email"), + context["users"] = ProfileSerializer(profiles.order_by("user__email"), many=True, ).data context["countries"] = COUNTRIES - context["teams"] = TeamsSerializer(Teams.objects.all(), many=True).data + context["teams"] = TeamsSerializer(Teams.objects.filter(org=self.request.org), many=True).data context["per_page"] = params.get("per_page") - context["assignedto_list"] = UserSerializer(User.objects.all(), many=True).data + context["assignedto_list"] = ProfileSerializer(profiles, many=True).data return context @swagger_auto_schema( @@ -128,16 +126,19 @@ def post(self, request, *args, **kwargs): ) # if contact_serializer.is_valid() and address_serializer.is_valid(): address_obj = address_serializer.save() - contact_obj = contact_serializer.save() + contact_obj = contact_serializer.save( + date_of_birth = params.get("date_of_birth") + ) contact_obj.address = address_obj - contact_obj.created_by = self.request.user + contact_obj.created_by = self.request.profile + contact_obj.org = request.org contact_obj.save() - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter(id=team, org=request.org) if teams_ids.exists(): contact_obj.teams.add(team) else: @@ -150,9 +151,9 @@ def post(self, request, *args, **kwargs): assinged_to_users_ids = json.loads(params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) - if user.exists(): - contact_obj.assigned_to.add(user_id) + profile = Profile.objects.filter(id=user_id, org=request.org) + if profile.exists(): + contact_obj.assigned_to.add(profile) else: contact_obj.delete() data["assigned_to"] = "Please enter valid user" @@ -164,17 +165,18 @@ def post(self, request, *args, **kwargs): 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=settings.Domain, + domain=current_site.domain, protocol=self.request.scheme, ) if request.FILES.get("contact_attachment"): attachment = Attachments() - attachment.created_by = request.user + attachment.created_by = request.profile attachment.file_name = request.FILES.get("contact_attachment").name attachment.contact = contact_obj attachment.attachment = request.FILES.get("contact_attachment") @@ -200,7 +202,11 @@ def put(self, request, pk, format=None): params = request.query_params if len(request.data) == 0 else request.data contact_obj = self.get_object(pk=pk) address_obj = contact_obj.address - + if contact_obj.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) contact_serializer = CreateContactSerializer( data=params, instance=contact_obj, request_obj=request, contact=True ) @@ -218,10 +224,10 @@ def put(self, request, pk, format=None): ) if contact_serializer.is_valid(): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: if not ( - (self.request.user == contact_obj.created_by) - or (self.request.user in contact_obj.assigned_to.all()) + (self.request.profile == contact_obj.created_by) + or (self.request.profile in contact_obj.assigned_to.all()) ): return Response( { @@ -231,16 +237,18 @@ def put(self, request, pk, format=None): status=status.HTTP_403_FORBIDDEN, ) address_obj = address_serializer.save() - contact_obj = contact_serializer.save() + contact_obj = contact_serializer.save( + date_of_birth = params.get("date_of_birth") + ) contact_obj.address = address_obj contact_obj.save() contact_obj = contact_serializer.save() - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": contact_obj.teams.clear() if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter(id=team, org=request.org) if teams_ids.exists(): contact_obj.teams.add(team) else: @@ -254,8 +262,8 @@ def put(self, request, pk, format=None): if params.get("assigned_to"): assinged_to_users_ids = json.loads(params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) - if user.exists(): + profile = Profile.objects.filter(id=user_id, org=request.org) + if profile.exists(): contact_obj.assigned_to.add(user_id) else: data["assigned_to"] = "Please enter valid user" @@ -272,15 +280,16 @@ def put(self, request, pk, format=None): contact_obj.assigned_to.all().values_list("id", flat=True) ) recipients = list(set(assigned_to_list) - set(previous_assigned_to_users)) + current_site = get_current_site(self.request) send_email_to_assigned_user.delay( recipients, contact_obj.id, - domain=settings.Domain, + domain=current_site.domain, protocol=request.scheme, ) if request.FILES.get("contact_attachment"): attachment = Attachments() - attachment.created_by = request.user + attachment.created_by = request.profile attachment.file_name = request.FILES.get("contact_attachment").name attachment.contact = contact_obj attachment.attachment = request.FILES.get("contact_attachment") @@ -291,7 +300,7 @@ def put(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["contacts"], + tags=["contacts"], manual_parameters=swagger_params.organization_params ) def get(self, request, pk, format=None): context = {} @@ -301,17 +310,17 @@ def get(self, request, pk, format=None): 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) + self.request.profile.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: + user_assgn_list.append(self.request.profile.id) + if self.request.profile == contact_obj.created_by: + user_assgn_list.append(self.request.profile.id) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: + if self.request.profile.id not in user_assgn_list: return Response( { "error": True, @@ -326,23 +335,23 @@ def get(self, request, pk, format=None): 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).values("username")) - elif self.request.user != contact_obj.created_by: - users_mention = [{"username": contact_obj.created_by.username}] + if self.request.profile.is_admin or self.request.profile.role == "ADMIN": + users_mention = list(Profile.objects.filter(is_active=True, org=request.org).values("user__username")) + elif self.request.profile != contact_obj.created_by: + users_mention = [{"username": contact_obj.created_by.user.username}] else: users_mention = list(contact_obj.assigned_to.all().values("username")) - if request.user == contact_obj.created_by: - user_assgn_list.append(self.request.user.id) + if request.profile == contact_obj.created_by: + user_assgn_list.append(self.request.profile.id) context["address_obj"] = BillingAddressSerializer(contact_obj.address).data - context["users"] = UserSerializer( - User.objects.filter(is_active=True).order_by("email"), + context["users"] = ProfileSerializer( + Profile.objects.filter(is_active=True, org=request.org).order_by("user__email"), many=True, ).data context["countries"] = COUNTRIES - context["teams"] = TeamsSerializer(Teams.objects.all(), many=True).data + context["teams"] = TeamsSerializer(Teams.objects.filter(org=request.org), many=True).data context.update( { "comments": CommentSerializer( @@ -361,14 +370,19 @@ def get(self, request, pk, format=None): return Response(context) @swagger_auto_schema( - tags=["contacts"], + tags=["contacts"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.get_object(pk) + if self.object.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) if ( - self.request.user.role != "ADMIN" - and not self.request.user.is_superuser - and self.request.user != self.object.created_by + self.request.profile.role != "ADMIN" + and not self.request.profile.is_admin + and self.request.profile != self.object.created_by ): return Response( { @@ -397,10 +411,10 @@ def post(self, request, pk, **kwargs): ) context = {} self.contact_obj = Contact.objects.get(pk=pk) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: if not ( - (self.request.user == self.contact_obj.created_by) - or (self.request.user in self.contact_obj.assigned_to.all()) + (self.request.profile == self.contact_obj.created_by) + or (self.request.profile in self.contact_obj.assigned_to.all()) ): return Response( { @@ -414,12 +428,13 @@ def post(self, request, pk, **kwargs): if params.get("comment"): comment_serializer.save( contact_id=self.contact_obj.id, - commented_by_id=self.request.user.id, + commented_by_id=self.request.profile.id, + org=request.org ) if self.request.FILES.get("contact_attachment"): attachment = Attachments() - attachment.created_by = self.request.user + attachment.created_by = self.request.profile attachment.file_name = self.request.FILES.get("contact_attachment").name attachment.contact = self.contact_obj attachment.attachment = self.request.FILES.get("contact_attachment") @@ -456,9 +471,9 @@ def put(self, request, pk, format=None): params = request.query_params if len(request.data) == 0 else request.data obj = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == obj.commented_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == obj.commented_by ): serializer = CommentSerializer(obj, data=params) if params.get("comment"): @@ -481,13 +496,13 @@ def put(self, request, pk, format=None): status=status.HTTP_403_FORBIDDEN, ) - @swagger_auto_schema(tags=["contacts"]) + @swagger_auto_schema(tags=["contacts"], manual_parameters=swagger_params.organization_params) def delete(self, request, pk, format=None): self.object = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.commented_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.commented_by ): self.object.delete() return Response( @@ -509,13 +524,13 @@ class ContactAttachmentView(APIView): authentication_classes = (JSONWebTokenAuthentication,) permission_classes = (IsAuthenticated,) - @swagger_auto_schema(tags=["contacts"]) + @swagger_auto_schema(tags=["contacts"], manual_parameters=swagger_params.organization_params) def delete(self, request, pk, format=None): self.object = self.model.objects.get(pk=pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.created_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.created_by ): self.object.delete() return Response( diff --git a/crm/server_settings.py b/crm/server_settings.py index 88dbd41..75201f7 100644 --- a/crm/server_settings.py +++ b/crm/server_settings.py @@ -22,8 +22,8 @@ MEDIA_ROOT = "/%s/" % DEFAULT_S3_PATH MEDIA_URL = "//%s/%s/" % (S3_DOMAIN, DEFAULT_S3_PATH) -STATIC_URL = "https://%s/" % (S3_DOMAIN) -ADMIN_MEDIA_PREFIX = STATIC_URL + "admin/" +# STATIC_URL = "https://%s/" % (S3_DOMAIN) +# ADMIN_MEDIA_PREFIX = STATIC_URL + "admin/" CORS_ORIGIN_ALLOW_ALL = True diff --git a/crm/settings.py b/crm/settings.py index e6c9854..825a7d2 100644 --- a/crm/settings.py +++ b/crm/settings.py @@ -7,20 +7,18 @@ # 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" -load_dotenv(dotenv_path=env_path) +# env_path = Path(".") / ".env" +load_dotenv() # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = os.getenv( "SECRET_KEY", "&q1&ftrxho9lrzm$$%6!cplb5ac957-9y@t@17u(3yqqb#9xl%" ) -Domain = os.getenv("DOMAIN_NAME") - # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.getenv("DEBUG") -ALLOWED_HOSTS = ["localhost", "127.0.0.1"] +ALLOWED_HOSTS = ["localhost", "127.0.0.1", ".bottlecrm.com"] INSTALLED_APPS = [ "django.contrib.auth", @@ -56,6 +54,8 @@ "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "corsheaders.middleware.CorsMiddleware", + "common.custom_auth.TokenAuthMiddleware", + "common.middleware.get_company.GetProfileAndOrg", ] ROOT_URLCONF = "crm.urls" @@ -141,14 +141,6 @@ elif ENV_TYPE == "live": from .server_settings import * - INSTALLED_APPS = INSTALLED_APPS + [ - "raven.contrib.django.raven_compat", - ] - MIDDLEWARE = [ - "raven.contrib.django.raven_compat.middleware.Sentry404CatchMiddleware", - "raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware", - ] + MIDDLEWARE - DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "") ADMIN_EMAIL = os.getenv("ADMIN_EMAIL", "") MARKETING_REPLY_EMAIL = os.getenv("MARKETING_REPLY_EMAIL", "") @@ -243,7 +235,9 @@ }, } +CORS_ALLOW_HEADERS = default_headers + ("org",) CORS_ORIGIN_ALLOW_ALL = True + SECURE_HSTS_SECONDS = 3600 SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True @@ -252,8 +246,3 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" - - -SECURE_SSL_REDIRECT = os.getenv("SECURE_SSL_REDIRECT_ENABLED") != "False" - -SESSION_COOKIE_SECURE = os.getenv("SESSION_COOKIE_SECURE_ENABLED") != "False" diff --git a/crm/urls.py b/crm/urls.py index e20617a..bd78348 100644 --- a/crm/urls.py +++ b/crm/urls.py @@ -1,3 +1,4 @@ +import os from django.conf import settings from django.conf.urls.static import static from django.contrib.auth import views @@ -6,7 +7,6 @@ from drf_yasg import openapi from django.conf.urls import url from rest_framework import permissions -from common.views import index openapi_info = openapi.Info( @@ -17,6 +17,7 @@ schema_view = get_schema_view( openapi_info, public=True, + url=os.getenv("SWAGGER_ROOT_URL"), permission_classes=(permissions.AllowAny,), ) @@ -34,7 +35,6 @@ schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui", ), - path("", index), url( r"^redoc/$", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc" ), @@ -43,6 +43,6 @@ ] if settings.DEBUG: - urlpatterns = urlpatterns + static( - settings.MEDIA_URL, document_root=settings.MEDIA_ROOT + urlpatterns = ( + urlpatterns + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT), ) diff --git a/emails/tests.py b/emails/tests.py index 3eb7cd9..4f2a642 100644 --- a/emails/tests.py +++ b/emails/tests.py @@ -1,6 +1,6 @@ from django.test import TestCase from django.test import Client -from common.models import User, Company +from common.models import User, Org from emails.models import Email from emails.forms import EmailForm @@ -8,15 +8,15 @@ class UserCreation(TestCase): def setUp(self): self.client = Client() - self.company, _ = Company.objects.get_or_create( - name="test company", address="IN", sub_domain="test", country="IN" + self.org, _ = Org.objects.get_or_create( + name="test org", address="IN", country="IN" ) self.user = User.objects.create( first_name="janeEmail@example.com", username="jane", email="janeEmail@example.com", role="ADMIN", - company=self.company, + org=self.org, ) self.user.set_password("password") self.user.save() diff --git a/env.md b/env.md index 6fe5687..b324f75 100644 --- a/env.md +++ b/env.md @@ -27,6 +27,9 @@ SENTRY_DSN CELERY_BROKER_URL CELERY_RESULT_BACKEND +# Swagger +SWAGGER_ROOT_URL + #CACHES MEMCACHELOCATION diff --git a/events/migrations/0006_auto_20210913_1918.py b/events/migrations/0006_auto_20210913_1918.py new file mode 100644 index 0000000..33f8807 --- /dev/null +++ b/events/migrations/0006_auto_20210913_1918.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2 on 2021-09-13 13:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0034_auto_20210913_1918'), + ('events', '0005_remove_event_company'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='company', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.company'), + ), + migrations.AlterField( + model_name='event', + name='assigned_to', + field=models.ManyToManyField(blank=True, related_name='event_assigned', to='common.Profile'), + ), + migrations.AlterField( + model_name='event', + name='created_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='event_created_by_user', to='common.profile'), + ), + ] diff --git a/events/migrations/0007_rename_company_event_org.py b/events/migrations/0007_rename_company_event_org.py new file mode 100644 index 0000000..97708f7 --- /dev/null +++ b/events/migrations/0007_rename_company_event_org.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-09-22 12:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0006_auto_20210913_1918'), + ] + + operations = [ + migrations.RenameField( + model_name='event', + old_name='company', + new_name='org', + ), + ] diff --git a/events/models.py b/events/models.py index 64e816e..8b536aa 100644 --- a/events/models.py +++ b/events/models.py @@ -3,7 +3,7 @@ from django.db import models from django.utils.translation import pgettext_lazy from django.utils.translation import ugettext_lazy as _ -from common.models import User +from common.models import Org, Profile from contacts.models import Contact from teams.models import Teams @@ -35,7 +35,7 @@ class Event(models.Model): ) contacts = models.ManyToManyField(Contact, blank=True, related_name="event_contact") assigned_to = models.ManyToManyField( - User, blank=True, related_name="event_assigned" + Profile, blank=True, related_name="event_assigned" ) start_date = models.DateField(default=None) start_time = models.TimeField(default=None) @@ -44,12 +44,15 @@ class Event(models.Model): description = models.TextField(blank=True, null=True) created_on = models.DateTimeField(_("Created on"), auto_now_add=True) created_by = models.ForeignKey( - User, related_name="event_created_by_user", null=True, on_delete=models.SET_NULL + Profile, related_name="event_created_by_user", null=True, on_delete=models.SET_NULL ) is_active = models.BooleanField(default=True) disabled = models.BooleanField(default=False) date_of_meeting = models.DateField(blank=True, null=True) teams = models.ManyToManyField(Teams, related_name="event_teams") + org = models.ForeignKey( + Org, on_delete=models.SET_NULL, null=True, blank=True + ) # tags = models.ManyToManyField(Tag) @@ -60,18 +63,18 @@ def created_on_arrow(self): @property def get_team_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) - return User.objects.filter(id__in=team_user_ids) + return Profile.objects.filter(id__in=team_user_ids) @property def get_team_and_assigned_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = team_user_ids + assigned_user_ids - return User.objects.filter(id__in=user_ids) + return Profile.objects.filter(id__in=user_ids) @property def get_assigned_users_not_in_teams(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = set(assigned_user_ids) - set(team_user_ids) - return User.objects.filter(id__in=list(user_ids)) + return Profile.objects.filter(id__in=list(user_ids)) diff --git a/events/serializer.py b/events/serializer.py index efa7d27..123ff35 100644 --- a/events/serializer.py +++ b/events/serializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from events.models import Event from common.serializer import ( - UserSerializer, + ProfileSerializer, AttachmentsSerializer, CommentSerializer, + OrganizationSerializer, ) from contacts.serializer import ContactSerializer from teams.serializer import TeamsSerializer @@ -11,12 +12,13 @@ class EventSerializer(serializers.ModelSerializer): - created_by = UserSerializer() - assigned_to = UserSerializer(read_only=True, many=True) + created_by = ProfileSerializer() + assigned_to = ProfileSerializer(read_only=True, many=True) contacts = ContactSerializer(read_only=True, many=True) teams = TeamsSerializer(read_only=True, many=True) event_attachment = AttachmentsSerializer(read_only=True, many=True) event_comments = CommentSerializer(read_only=True, many=True) + org = OrganizationSerializer() class Meta: model = Event @@ -40,6 +42,7 @@ class Meta: "assigned_to", "event_attachment", "event_comments", + "org" ) @@ -48,17 +51,18 @@ def __init__(self, *args, **kwargs): request_obj = kwargs.pop("request_obj", None) super(EventCreateSerializer, self).__init__(*args, **kwargs) self.fields["event_type"].required = True + self.org = request_obj.org def validate_name(self, name): if self.instance: if ( - Event.objects.filter(name__iexact=name) + Event.objects.filter(name__iexact=name, org=self.org) .exclude(id=self.instance.id) .exists() ): raise serializers.ValidationError("Event already exists with this name") else: - if Event.objects.filter(name__iexact=name).exists(): + if Event.objects.filter(name__iexact=name, org=self.org).exists(): raise serializers.ValidationError("Event already exists with this name") return name @@ -121,4 +125,5 @@ class Meta: "description", "created_by", "created_on", + "org" ) diff --git a/events/swagger_params.py b/events/swagger_params.py index 1c90003..ab26abe 100644 --- a/events/swagger_params.py +++ b/events/swagger_params.py @@ -1,6 +1,15 @@ from drf_yasg import openapi +organization_params_in_header = openapi.Parameter( + "org", openapi.IN_HEADER, required=True, type=openapi.TYPE_INTEGER +) + +organization_params = [ + organization_params_in_header, +] + event_list_get_params = [ + organization_params_in_header, openapi.Parameter("name", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("created_by", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("assigned_users", openapi.IN_QUERY, type=openapi.TYPE_STRING), @@ -13,6 +22,7 @@ ] event_detail_post_params = [ + organization_params_in_header, openapi.Parameter( "event_attachment", openapi.IN_QUERY, @@ -22,6 +32,7 @@ ] event_create_post_params = [ + organization_params_in_header, openapi.Parameter( "name", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING ), @@ -61,5 +72,6 @@ ] event_comment_edit_params = [ + organization_params_in_header, openapi.Parameter("comment", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] diff --git a/events/tasks.py b/events/tasks.py index 6ad5dbb..20af82c 100644 --- a/events/tasks.py +++ b/events/tasks.py @@ -22,21 +22,21 @@ def send_email(event_id, recipients, domain="demo.django-crm.io", protocol="http context["event_date_of_meeting"] = event.date_of_meeting context["url"] = protocol + "://" + domain # recipients = event.assigned_to.filter(is_active=True) - for user in recipients: + for profile_id in recipients: recipients_list = [] - user = User.objects.filter(id=user, is_active=True).first() - if user: - recipients_list.append(user.email) + profile = Profile.objects.filter(id=profile_id, is_active=True).first() + if profile: + recipients_list.append(profile.user.email) event_members = event.assigned_to.filter(is_active=True) context["other_members"] = list( - event_members.exclude(id=user.id).values_list("email", flat=True) + event_members.exclude(id=profile.id).values_list("user__email", flat=True) ) if len(context["other_members"]) > 0: context["other_members"] = ", ".join(context["other_members"]) else: context["other_members"] = "" - context["user"] = user.email + context["user"] = profile.user.email html_content = render_to_string( "assigned_to_email_template_event.html", context=context ) diff --git a/events/views.py b/events/views.py index cdbee4a..f3c2446 100644 --- a/events/views.py +++ b/events/views.py @@ -2,10 +2,10 @@ from contacts.models import Contact from contacts.serializer import ContactSerializer -from common.models import User, Attachments, Comment +from common.models import User, Attachments, Comment, Profile from common.custom_auth import JSONWebTokenAuthentication from common.serializer import ( - UserSerializer, + ProfileSerializer, CommentSerializer, AttachmentsSerializer, CommentSerializer, @@ -48,14 +48,16 @@ def get_context_data(self, **kwargs): if len(self.request.data) == 0 else self.request.data ) - queryset = self.model.objects.all() - contacts = Contact.objects.all() - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + queryset = self.model.objects.filter(org=self.request.org) + contacts = Contact.objects.filter(org=self.request.org) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: queryset = queryset.filter( - Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user) + Q(assigned_to__in=[self.request.profile]) | Q( + created_by=self.request.profile) ) contacts = contacts.filter( - Q(created_by=self.request.user) | Q(assigned_to=self.request.user) + Q(created_by=self.request.profile) | Q( + assigned_to=self.request.profile) ).distinct() if params: @@ -65,7 +67,8 @@ def get_context_data(self, **kwargs): queryset = queryset.filter(created_by=params.get("created_by")) if params.getlist("assigned_users"): queryset = queryset.filter( - assigned_to__id__in=json.loads(params.get("assigned_users")) + assigned_to__id__in=json.loads( + params.get("assigned_users")) ) if params.get("date_of_meeting"): queryset = queryset.filter( @@ -81,7 +84,8 @@ def get_context_data(self, **kwargs): ): search = True context["search"] = search - results_events = self.paginate_queryset(queryset, self.request, view=self) + results_events = self.paginate_queryset( + queryset, self.request, view=self) events = EventSerializer(results_events, many=True).data context["per_page"] = 10 @@ -100,16 +104,18 @@ def get_context_data(self, **kwargs): context["events"] = events users = [] - if self.request.user.role == "ADMIN" or self.request.user.is_superuser: - users = User.objects.filter( - is_active=True, - ).order_by("email") + profile_list = Profile.objects.filter( + is_active=True, org=self.request.org) + if self.request.profile.role == "ADMIN" or self.request.profile.is_admin: + profiles = profile_list.order_by("user__email") else: - users = User.objects.filter(role="ADMIN").order_by("email") + profiles = profile_list.filter( + role="ADMIN").order_by("user__email") context["recurring_days"] = WEEKDAYS - context["users"] = UserSerializer(users, many=True).data - if self.request.user == "ADMIN": - context["teams_list"] = TeamsSerializer(Teams.objects.all(), many=True).data + context["users"] = ProfileSerializer(profiles, many=True).data + if self.request.profile == "ADMIN": + context["teams_list"] = TeamsSerializer( + Teams.objects.filter(org=self.request.org), many=True).data context["contacts_list"] = ContactSerializer(contacts, many=True).data return context @@ -137,15 +143,17 @@ def post(self, request, *args, **kwargs): recurring_days = json.dumps(params.get("recurring_days")) if params.get("event_type") == "Non-Recurring": event_obj = serializer.save( - created_by=request.user, + created_by=request.profile, date_of_meeting=params.get("start_date"), is_active=True, disabled=False, + org=request.org ) if params.get("contacts"): contacts = json.loads(params.get("contacts")) for contact in contacts: - obj_contact = Contact.objects.filter(id=contact) + obj_contact = Contact.objects.filter( + id=contact, org=request.org) if obj_contact.exists(): event_obj.contacts.add(contact) else: @@ -155,11 +163,12 @@ def post(self, request, *args, **kwargs): {"error": True, "errors": data}, status=status.HTTP_400_BAD_REQUEST, ) - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter( + id=team, org=request.org) if teams_ids.exists(): event_obj.teams.add(team) else: @@ -170,9 +179,11 @@ def post(self, request, *args, **kwargs): status=status.HTTP_400_BAD_REQUEST, ) if params.get("assigned_to"): - assinged_to_users_ids = json.loads(params.get("assigned_to")) + assinged_to_users_ids = json.loads( + params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter( + id=user_id, org=request.org) if user.exists(): event_obj.assigned_to.add(user_id) else: @@ -214,7 +225,7 @@ def post(self, request, *args, **kwargs): data = serializer.validated_data event = Event.objects.create( - created_by=request.user, + created_by=request.profile, start_date=start_date, end_date=end_date, name=data["name"], @@ -223,12 +234,14 @@ def post(self, request, *args, **kwargs): start_time=data["start_time"], end_time=data["end_time"], date_of_meeting=each, + org=request.org ) if params.get("contacts"): contacts = json.loads(params.get("contacts")) for contact in contacts: - obj_contact = Contact.objects.filter(id=contact) + obj_contact = Contact.objects.filter( + id=contact, org=request.org) if obj_contact.exists(): event.contacts.add(contact) else: @@ -238,11 +251,12 @@ def post(self, request, *args, **kwargs): {"error": True, "errors": data}, status=status.HTTP_400_BAD_REQUEST, ) - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter( + id=team, org=request.org) if teams_ids.exists(): event.teams.add(team) else: @@ -257,7 +271,8 @@ def post(self, request, *args, **kwargs): params.get("assigned_to") ) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter( + id=user_id, org=request.org) if user.exists(): event.assigned_to.add(user_id) else: @@ -299,10 +314,10 @@ def get_context_data(self, **kwargs): user_assgn_list = [ assigned_to.id for assigned_to in self.event_obj.assigned_to.all() ] - if self.request.user == self.event_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: + if self.request.profile == self.event_obj.created_by: + user_assgn_list.append(self.request.profile.id) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: + if self.request.profile.id not in user_assgn_list: return Response( { "error": True, @@ -312,31 +327,33 @@ def get_context_data(self, **kwargs): ) comments = Comment.objects.filter(event=self.event_obj).order_by("-id") - attachments = Attachments.objects.filter(event=self.event_obj).order_by("-id") - assigned_data = self.event_obj.assigned_to.values("id", "email") - if self.request.user.is_superuser or self.request.user.role == "ADMIN": + attachments = Attachments.objects.filter( + event=self.event_obj).order_by("-id") + assigned_data = self.event_obj.assigned_to.values("id", "user__email") + if self.request.profile.is_admin or self.request.profile.role == "ADMIN": users_mention = list( - User.objects.filter( + Profile.objects.filter( is_active=True, - ).values("username") + ).values("user__username") ) - elif self.request.user != self.event_obj.created_by: - users_mention = [{"username": self.event_obj.created_by.username}] + elif self.request.profile != self.event_obj.created_by: + users_mention = [ + {"username": self.event_obj.created_by.user.username}] else: - users_mention = list(self.event_obj.assigned_to.all().values("username")) - if self.request.user.role == "ADMIN" or self.request.user.is_superuser: - users = User.objects.filter( - is_active=True, - ).order_by("email") + users_mention = list( + self.event_obj.assigned_to.all().values("user__username")) + profile_list = Profile.objects.filter( + is_active=True, org=self.request.org) + if self.request.profile.role == "ADMIN" or self.request.profile.is_admin: + profiles = profile_list.order_by("user__email") else: - users = User.objects.filter( - role="ADMIN", - ).order_by("email") - - if self.request.user == self.event_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: + profiles = profile_list.filter( + role="ADMIN").order_by("user__email") + + if self.request.profile == self.event_obj.created_by: + user_assgn_list.append(self.request.profile.id) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: + if self.request.profile.id not in user_assgn_list: return Response( { "error": True, @@ -345,9 +362,10 @@ def get_context_data(self, **kwargs): status=status.HTTP_403_FORBIDDEN, ) team_ids = [user.id for user in self.event_obj.get_team_users] - all_user_ids = users.values_list("id", flat=True) + all_user_ids = profiles.values_list("id", flat=True) users_excluding_team_id = set(all_user_ids) - set(team_ids) - users_excluding_team = User.objects.filter(id__in=users_excluding_team_id) + users_excluding_team = Profile.objects.filter( + id__in=users_excluding_team_id) selected_recurring_days = Event.objects.filter( name=self.event_obj.name @@ -366,18 +384,23 @@ def get_context_data(self, **kwargs): } ) - context["users"] = UserSerializer(users, many=True).data - context["users_excluding_team"] = UserSerializer( + context["users"] = ProfileSerializer(profiles, many=True).data + context["users_excluding_team"] = ProfileSerializer( users_excluding_team, many=True ).data context["teams"] = TeamsSerializer(Teams.objects.all(), many=True).data return context @swagger_auto_schema( - tags=["Events"], + tags=["Events"], manual_parameters=swagger_params.organization_params ) def get(self, request, pk, **kwargs): self.event_obj = self.get_object(pk) + if self.event_obj.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) context = self.get_context_data(**kwargs) return Response(context) @@ -392,10 +415,16 @@ def post(self, request, pk, **kwargs): ) context = {} self.event_obj = Event.objects.get(pk=pk) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.event_obj.org != request.org: + return Response( + {"error": True, "errors": "User company does not match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) + + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: if not ( - (self.request.user == self.event_obj.created_by) - or (self.request.user in self.event_obj.assigned_to.all()) + (self.request.profile == self.event_obj.created_by) + or (self.request.profile in self.event_obj.assigned_to.all()) ): return Response( { @@ -409,18 +438,20 @@ def post(self, request, pk, **kwargs): if params.get("comment"): comment_serializer.save( event_id=self.event_obj.id, - commented_by_id=self.request.user.id, + commented_by_id=self.request.profile.id, ) if self.request.FILES.get("event_attachment"): attachment = Attachments() - attachment.created_by = self.request.user - attachment.file_name = self.request.FILES.get("event_attachment").name + attachment.created_by = self.request.profile + attachment.file_name = self.request.FILES.get( + "event_attachment").name attachment.event = self.event_obj attachment.attachment = self.request.FILES.get("event_attachment") attachment.save() - comments = Comment.objects.filter(event__id=self.event_obj.id).order_by("-id") + comments = Comment.objects.filter( + event__id=self.event_obj.id).order_by("-id") attachments = Attachments.objects.filter(event__id=self.event_obj.id).order_by( "-id" ) @@ -444,6 +475,11 @@ def put(self, request, pk, **kwargs): ) data = {} self.event_obj = self.get_object(pk) + if self.event_obj.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) serializer = EventCreateSerializer( data=params, instance=self.event_obj, @@ -461,7 +497,8 @@ def put(self, request, pk, **kwargs): if params.get("contacts"): contacts = json.loads(params.get("contacts")) for contact in contacts: - obj_contact = Contact.objects.filter(id=contact) + obj_contact = Contact.objects.filter( + id=contact, org=request.org) if obj_contact.exists(): event_obj.contacts.add(contact) else: @@ -470,12 +507,13 @@ def put(self, request, pk, **kwargs): {"error": True, "errors": data}, status=status.HTTP_400_BAD_REQUEST, ) - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": event_obj.teams.clear() if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter( + id=team, org=request.org) if teams_ids.exists(): event_obj.teams.add(team) else: @@ -490,9 +528,10 @@ def put(self, request, pk, **kwargs): event_obj.assigned_to.clear() if params.get("assigned_to"): - assinged_to_users_ids = json.loads(params.get("assigned_to")) + assinged_to_users_ids = json.loads( + params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter(id=user_id, org=request.org) if user.exists(): event_obj.assigned_to.add(user_id) else: @@ -508,7 +547,8 @@ def put(self, request, pk, **kwargs): assigned_to_list = list( event_obj.assigned_to.all().values_list("id", flat=True) ) - recipients = list(set(assigned_to_list) - set(previous_assigned_to_users)) + recipients = list(set(assigned_to_list) - + set(previous_assigned_to_users)) send_email.delay( event_obj.id, recipients, @@ -525,15 +565,15 @@ def put(self, request, pk, **kwargs): ) @swagger_auto_schema( - tags=["Events"], + tags=["Events"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, **kwargs): self.object = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.created_by - ): + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.created_by + ) and self.object.org == request.org: self.object.delete() return Response( {"error": False, "message": "Event deleted Successfully"}, @@ -557,12 +597,13 @@ def get_object(self, pk): tags=["Events"], manual_parameters=swagger_params.event_comment_edit_params ) def put(self, request, pk, format=None): - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data obj = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == obj.commented_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == obj.commented_by ): serializer = CommentSerializer(obj, data=params) if params.get("comment"): @@ -586,14 +627,14 @@ def put(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["Events"], + tags=["Events"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.commented_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.commented_by ): self.object.delete() return Response( @@ -616,14 +657,14 @@ class EventAttachmentView(APIView): permission_classes = (IsAuthenticated,) @swagger_auto_schema( - tags=["Events"], + tags=["Events"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.model.objects.get(pk=pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.created_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.created_by ): self.object.delete() return Response( diff --git a/installation.md b/installation.md new file mode 100644 index 0000000..dcd65f0 --- /dev/null +++ b/installation.md @@ -0,0 +1,56 @@ +# CRM Installation using docker + +- pull application image +```sh +docker pull micropyramid/crm:0.1 +``` + +- setup environment variables, below varialbes are required. +```sh +# environment type, eg: stage, live +export ENV_TYPE="" +# sentry dns value, logs backend django project errors +export SENTRY_DSN="" +# sentry dns value, logs frontend react errors +export REACT_APP_DSN="" +# swagger api base/root url, eg: http://127.0.0.1 +export SWAGGER_ROOT_URL="" +# postgresql database host address, eg: example.com or some ip address +export DBHOST="" +# postgresql database port +export DBPORT="" +# postgresql database name +export DBNAME="" +# postgresql database user name +export DBUSER="" +# postgresql database password +export DBPASSWORD="" +# s3 bucket name, required for storing media and static files. +export S3_BUCKET_NAME="" +# access key to access s3 bucket +export AWS_ACCESS_KEY_ID="" +# secret key to access s3 bucket +export AWS_SECRET_ACCESS_KEY="" +``` + +- run application +```sh +docker run \ + -n crm \ + -p 8000:80 \ + -e ENV_TYPE="$ENV_TYPE" \ + -e SENTRY_DSN="$SENTRY_DSN" \ + -e REACT_APP_DSN="$REACT_APP_DSN" \ + -e SWAGGER_ROOT_URL="$SWAGGER_ROOT_URL" \ + -e DBHOST="$DBHOST" \ + -e DBPORT="$DBPORT" \ + -e DBNAME="$DBNAME" \ + -e DBUSER="$DBUSER" \ + -e DBPASSWORD="$DBPASSWORD" \ + -e S3_BUCKET_NAME="$S3_BUCKET_NAME" \ + -e AWS_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" \ + -e AWS_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" \ + micropyramid/crm:0.1 +``` + +- GOTO: http://127.0.0.1:8000 diff --git a/invoices/api_views.py b/invoices/api_views.py index 79a26a7..409bfb3 100644 --- a/invoices/api_views.py +++ b/invoices/api_views.py @@ -39,7 +39,6 @@ from rest_framework.pagination import LimitOffsetPagination from drf_yasg.utils import swagger_auto_schema import json -from django.conf import settings INVOICE_STATUS = ( ("Draft", "Draft"), @@ -237,7 +236,7 @@ def post(self, request, *args, **kwargs): send_email.delay( recipients, invoice_obj.id, - domain=settings.Domain, + domain=settings.DOMAIN_NAME, protocol=self.request.scheme, ) return Response({"error": False, "message": "Invoice Created Successfully"}) @@ -376,7 +375,7 @@ def put(self, request, pk, format=None): send_email.delay( recipients, invoice_obj.id, - domain=settings.Domain, + domain=settings.DOMAIN_NAME, protocol=self.request.scheme, ) return Response( diff --git a/invoices/migrations/0011_rename_company_invoice_org.py b/invoices/migrations/0011_rename_company_invoice_org.py new file mode 100644 index 0000000..0bb6ace --- /dev/null +++ b/invoices/migrations/0011_rename_company_invoice_org.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-09-22 12:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('invoices', '0010_invoice_tax'), + ] + + operations = [ + migrations.RenameField( + model_name='invoice', + old_name='company', + new_name='org', + ), + ] diff --git a/invoices/models.py b/invoices/models.py index cdd5c17..dc038c8 100644 --- a/invoices/models.py +++ b/invoices/models.py @@ -2,7 +2,7 @@ import arrow from django.db import models from django.utils.translation import ugettext_lazy as _ -from common.models import Address, User, Company +from common.models import Address, User, Org from common.utils import CURRENCY_CODES from accounts.models import Account from phonenumber_field.modelfields import PhoneNumberField @@ -65,8 +65,8 @@ class Invoice(models.Model): accounts = models.ManyToManyField(Account, related_name="accounts_invoices") teams = models.ManyToManyField(Teams, related_name="invoices_teams") - company = models.ForeignKey( - Company, on_delete=models.SET_NULL, null=True, blank=True + org = models.ForeignKey( + Org, on_delete=models.SET_NULL, null=True, blank=True ) tax = models.DecimalField(blank=True, null=True, max_digits=12, decimal_places=2) diff --git a/invoices/serializer.py b/invoices/serializer.py index 1e3d5b7..4380f54 100644 --- a/invoices/serializer.py +++ b/invoices/serializer.py @@ -2,7 +2,7 @@ from invoices.models import Invoice, InvoiceHistory from common.serializer import ( UserSerializer, - CompanySerializer, + OrganizationSerializer, BillingAddressSerializer, ) from teams.serializer import TeamsSerializer @@ -12,7 +12,7 @@ class InvoiceSerailizer(serializers.ModelSerializer): from_address = BillingAddressSerializer() to_address = BillingAddressSerializer() created_by = UserSerializer() - company = CompanySerializer() + org = OrganizationSerializer() teams = TeamsSerializer(read_only=True, many=True) assigned_to = UserSerializer(read_only=True, many=True) @@ -42,7 +42,7 @@ class Meta: "details", "teams", "assigned_to", - "company", + "org", ) @@ -79,13 +79,13 @@ def __init__(self, *args, **kwargs): request_obj = kwargs.pop("request_obj", None) super(InvoiceCreateSerializer, self).__init__(*args, **kwargs) - self.company = request_obj.company + self.org = request_obj.org def validate_invoice_title(self, invoice_title): if self.instance: if ( Invoice.objects.filter( - invoice_title__iexact=invoice_title, company=self.company + invoice_title__iexact=invoice_title, org=self.org ) .exclude(id=self.instance.id) .exists() @@ -95,7 +95,7 @@ def validate_invoice_title(self, invoice_title): ) else: if Invoice.objects.filter( - invoice_title__iexact=invoice_title, company=self.company + invoice_title__iexact=invoice_title, org=self.org ).exists(): raise serializers.ValidationError( "Invoice already exists with this invoice_title" @@ -123,5 +123,5 @@ class Meta: "amount_paid", "is_email_sent", "details", - "company", + "org", ) diff --git a/invoices/swagger_params.py b/invoices/swagger_params.py index 17c5832..7c7d9e0 100644 --- a/invoices/swagger_params.py +++ b/invoices/swagger_params.py @@ -1,7 +1,7 @@ from drf_yasg import openapi company_params_in_header = openapi.Parameter( - "company", openapi.IN_HEADER, required=True, type=openapi.TYPE_STRING + "org", openapi.IN_HEADER, required=True, type=openapi.TYPE_STRING ) invoice_list_get_params = [ diff --git a/leads/migrations/0015_auto_20210913_1918.py b/leads/migrations/0015_auto_20210913_1918.py new file mode 100644 index 0000000..4abd5fa --- /dev/null +++ b/leads/migrations/0015_auto_20210913_1918.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2 on 2021-09-13 13:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0034_auto_20210913_1918'), + ('leads', '0014_auto_20210324_1208'), + ] + + operations = [ + migrations.AddField( + model_name='lead', + name='company', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.company'), + ), + migrations.AlterField( + model_name='lead', + name='assigned_to', + field=models.ManyToManyField(related_name='lead_assigned_users', to='common.Profile'), + ), + migrations.AlterField( + model_name='lead', + name='created_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lead_created_by', to='common.profile'), + ), + ] diff --git a/leads/migrations/0016_rename_company_lead_org.py b/leads/migrations/0016_rename_company_lead_org.py new file mode 100644 index 0000000..90bbf1e --- /dev/null +++ b/leads/migrations/0016_rename_company_lead_org.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-09-22 12:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('leads', '0015_auto_20210913_1918'), + ] + + operations = [ + migrations.RenameField( + model_name='lead', + old_name='company', + new_name='org', + ), + ] diff --git a/leads/migrations/0017_alter_lead_org.py b/leads/migrations/0017_alter_lead_org.py new file mode 100644 index 0000000..521bfc4 --- /dev/null +++ b/leads/migrations/0017_alter_lead_org.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.7 on 2021-10-06 07:21 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0037_alter_profile_org'), + ('leads', '0016_rename_company_lead_org'), + ] + + operations = [ + migrations.AlterField( + model_name='lead', + name='org', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lead_org', to='common.org'), + ), + ] diff --git a/leads/models.py b/leads/models.py index 2e1066b..49bcbeb 100644 --- a/leads/models.py +++ b/leads/models.py @@ -6,7 +6,7 @@ from phonenumber_field.modelfields import PhoneNumberField from accounts.models import Tags -from common.models import User +from common.models import Org, Profile from common.utils import COUNTRIES, LEAD_SOURCE, LEAD_STATUS, return_complete_address from contacts.models import Contact from teams.models import Teams @@ -36,13 +36,13 @@ class Lead(models.Model): country = models.CharField(max_length=3, choices=COUNTRIES, blank=True, null=True) website = models.CharField(_("Website"), max_length=255, blank=True, null=True) description = models.TextField(blank=True, null=True) - assigned_to = models.ManyToManyField(User, related_name="lead_assigned_users") + assigned_to = models.ManyToManyField(Profile, related_name="lead_assigned_users") account_name = models.CharField(max_length=255, null=True, blank=True) opportunity_amount = models.DecimalField( _("Opportunity Amount"), decimal_places=2, max_digits=12, blank=True, null=True ) created_by = models.ForeignKey( - User, related_name="lead_created_by", on_delete=models.SET_NULL, null=True + Profile, related_name="lead_created_by", on_delete=models.SET_NULL, null=True ) created_on = models.DateTimeField(_("Created on"), auto_now_add=True) is_active = models.BooleanField(default=False) @@ -51,6 +51,10 @@ class Lead(models.Model): contacts = models.ManyToManyField(Contact, related_name="lead_contacts") created_from_site = models.BooleanField(default=False) teams = models.ManyToManyField(Teams, related_name="lead_teams") + org = models.ForeignKey( + Org, on_delete=models.SET_NULL, null=True, blank=True, related_name="lead_org" + ) + class Meta: ordering = ["-created_on"] @@ -74,21 +78,21 @@ def created_on_arrow(self): @property def get_team_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) - return User.objects.filter(id__in=team_user_ids) + return Profile.objects.filter(id__in=team_user_ids) @property def get_team_and_assigned_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = team_user_ids + assigned_user_ids - return User.objects.filter(id__in=user_ids) + return Profile.objects.filter(id__in=user_ids) @property def get_assigned_users_not_in_teams(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = set(assigned_user_ids) - set(team_user_ids) - return User.objects.filter(id__in=list(user_ids)) + return Profile.objects.filter(id__in=list(user_ids)) # def save(self, *args, **kwargs): # super(Lead, self).save(*args, **kwargs) diff --git a/leads/serializer.py b/leads/serializer.py index c6febaf..f4aa0e4 100644 --- a/leads/serializer.py +++ b/leads/serializer.py @@ -2,7 +2,7 @@ from leads.models import Lead from accounts.models import Tags, Account from common.serializer import ( - UserSerializer, + ProfileSerializer, AttachmentsSerializer, LeadCommentSerializer, ) @@ -17,13 +17,17 @@ class Meta: class LeadSerializer(serializers.ModelSerializer): - assigned_to = UserSerializer(read_only=True, many=True) - created_by = UserSerializer() + assigned_to = ProfileSerializer(read_only=True, many=True) + created_by = ProfileSerializer() + country = serializers.SerializerMethodField() tags = TagsSerializer(read_only=True, many=True) lead_attachment = AttachmentsSerializer(read_only=True, many=True) teams = TeamsSerializer(read_only=True, many=True) lead_comments = LeadCommentSerializer(read_only=True, many=True) + def get_country(self, obj): + return obj.get_country_display() + class Meta: model = Lead # fields = ‘__all__’ @@ -69,6 +73,7 @@ def __init__(self, *args, **kwargs): self.fields["first_name"].required = False self.fields["last_name"].required = False self.fields["title"].required = True + self.org = request_obj.org if self.instance: if self.instance.created_from_site: @@ -81,6 +86,7 @@ def validate_account_name(self, account_name): if ( Account.objects.filter( name__iexact=account_name, + org=self.org ) .exclude(id=self.instance.id) .exists() @@ -91,6 +97,7 @@ def validate_account_name(self, account_name): else: if Account.objects.filter( name__iexact=account_name, + org=self.org ).exists(): raise serializers.ValidationError( "Account already exists with this name" @@ -102,13 +109,14 @@ def validate_title(self, title): if ( Lead.objects.filter( title__iexact=title, + org=self.org ) .exclude(id=self.instance.id) .exists() ): raise serializers.ValidationError("Lead already exists with this title") else: - if Lead.objects.filter(title__iexact=title).exists(): + if Lead.objects.filter(title__iexact=title, org=self.org).exists(): raise serializers.ValidationError("Lead already exists with this title") return title @@ -131,4 +139,5 @@ class Meta: "state", "postcode", "country", + "org" ) diff --git a/leads/swagger_params.py b/leads/swagger_params.py index b85930a..a2900ab 100644 --- a/leads/swagger_params.py +++ b/leads/swagger_params.py @@ -1,6 +1,15 @@ from drf_yasg import openapi +organization_params_in_header = openapi.Parameter( + "org", openapi.IN_HEADER, required=True, type=openapi.TYPE_INTEGER +) + +organization_params = [ + organization_params_in_header, +] + lead_list_get_params = [ + organization_params_in_header, openapi.Parameter("title", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("source", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("assigned_to", openapi.IN_QUERY, type=openapi.TYPE_STRING), @@ -9,6 +18,7 @@ ] lead_detail_get_params = [ + organization_params_in_header, openapi.Parameter( "lead_attachment", openapi.IN_QUERY, @@ -18,6 +28,7 @@ ] lead_create_post_params = [ + organization_params_in_header, openapi.Parameter( "title", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING ), @@ -47,6 +58,7 @@ ] lead_upload_post_params = [ + organization_params_in_header, openapi.Parameter( "leads_file", openapi.IN_QUERY, @@ -55,6 +67,7 @@ ] lead_comment_edit_params = [ + organization_params_in_header, openapi.Parameter("comment", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] diff --git a/leads/tasks.py b/leads/tasks.py index c7418a3..348108d 100644 --- a/leads/tasks.py +++ b/leads/tasks.py @@ -8,7 +8,7 @@ from django.shortcuts import reverse from django.template.loader import render_to_string -from accounts.models import User +from common.models import User from leads.models import Lead @@ -54,7 +54,7 @@ def send_lead_assigned_emails(lead_id, new_assigned_to_list, site_address): if not (lead_instance and new_assigned_to_list): return False - users = User.objects.filter(id__in=new_assigned_to_list).distinct() + users = Profile.objects.filter(id__in=new_assigned_to_list).distinct() subject = "Lead '%s' has been assigned to you" % lead_instance from_email = settings.DEFAULT_FROM_EMAIL template_name = "lead_assigned.html" @@ -67,12 +67,12 @@ def send_lead_assigned_emails(lead_id, new_assigned_to_list, site_address): "lead_detail_url": url, } mail_kwargs = {"subject": subject, "from_email": from_email} - for user in users: + for profile_id in users: if user.email: - context["user"] = user + context["user"] = profile.user html_content = get_rendered_html(template_name, context) mail_kwargs["html_content"] = html_content - mail_kwargs["recipients"] = [user.email] + mail_kwargs["recipients"] = [profile.user.email] send_email.delay(**mail_kwargs) @@ -104,12 +104,13 @@ def send_email_to_assigned_user( @app.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) + org = Org.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: @@ -131,6 +132,7 @@ 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.org = org lead.save() except Exception as e: print(e) diff --git a/leads/views.py b/leads/views.py index c4971c9..7b24549 100644 --- a/leads/views.py +++ b/leads/views.py @@ -6,11 +6,11 @@ from accounts.tasks import send_email_to_assigned_user from contacts.models import Contact from leads import swagger_params -from common.models import User, Attachments, Comment, APISettings +from common.models import User, Attachments, Comment, APISettings, Profile from common.utils import COUNTRIES, LEAD_SOURCE, LEAD_STATUS from common.custom_auth import JSONWebTokenAuthentication from common.serializer import ( - UserSerializer, + ProfileSerializer, CommentSerializer, AttachmentsSerializer, LeadCommentSerializer, @@ -49,7 +49,7 @@ def get_context_data(self, **kwargs): else self.request.data ) queryset = ( - self.model.objects.all() + self.model.objects.filter(org=self.request.org) .exclude(status="converted") .select_related("created_by") .prefetch_related( @@ -57,9 +57,10 @@ def get_context_data(self, **kwargs): "assigned_to", ) ) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.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) + Q(assigned_to__in=[self.request.profile]) | Q( + created_by=self.request.profile) ) request_post = params @@ -70,12 +71,14 @@ def get_context_data(self, **kwargs): & Q(last_name__icontains=request_post.get("name")) ) if request_post.get("title"): - queryset = queryset.filter(title__icontains=request_post.get("title")) + queryset = queryset.filter( + title__icontains=request_post.get("title")) if request_post.get("source"): queryset = queryset.filter(source=request_post.get("source")) if request_post.getlist("assigned_to"): queryset = queryset.filter( - assigned_to__id__in=json.loads(request_post.get("assigned_to")) + assigned_to__id__in=json.loads( + request_post.get("assigned_to")) ) if request_post.get("status"): queryset = queryset.filter(status=request_post.get("status")) @@ -84,9 +87,11 @@ def get_context_data(self, **kwargs): tags__in=json.loads(request_post.get("tags")) ) if request_post.get("city"): - queryset = queryset.filter(city__icontains=request_post.get("city")) + queryset = queryset.filter( + city__icontains=request_post.get("city")) if request_post.get("email"): - queryset = queryset.filter(email__icontains=request_post.get("email")) + queryset = queryset.filter( + email__icontains=request_post.get("email")) context = {} search = False if ( @@ -129,17 +134,17 @@ def get_context_data(self, **kwargs): context["status"] = LEAD_STATUS context["source"] = LEAD_SOURCE users = [] - if self.request.user.role == "ADMIN" or self.request.user.is_superuser: - users = User.objects.filter( - is_active=True, - ).order_by("email") - elif self.request.user.google.all(): + if self.request.profile.role == "ADMIN" or self.request.user.is_superuser: + users = Profile.objects.filter( + is_active=True, org=self.request.org + ).order_by("user__email") + elif self.request.profile.google.all(): users = [] else: - users = User.objects.filter( - role="ADMIN", - ).order_by("email") - context["users"] = UserSerializer(users, many=True).data + users = Profile.objects.filter( + role="ADMIN", org=self.request.org + ).order_by("user__email") + context["users"] = ProfileSerializer(users, many=True).data tag_ids = list( set( queryset.values_list( @@ -175,7 +180,8 @@ def post(self, request, *args, **kwargs): data = {} serializer = LeadCreateSerializer(data=params, request_obj=request) if serializer.is_valid(): - lead_obj = serializer.save(created_by=request.user) + lead_obj = serializer.save( + created_by=request.profile, org=request.org) if params.get("tags"): tags = json.loads(params.get("tags")) # for t in tags: @@ -189,27 +195,31 @@ def post(self, request, *args, **kwargs): tag = Tags.objects.create(name=t) lead_obj.tags.add(tag) - recipients = list(lead_obj.assigned_to.all().values_list("id", flat=True)) + recipients = list( + lead_obj.assigned_to.all().values_list("id", flat=True)) + current_site = get_current_site(self.request) send_email_to_assigned_user.delay( recipients, lead_obj.id, - domain=settings.Domain, + domain=current_site.domain, protocol=request.scheme, ) if request.FILES.get("lead_attachment"): attachment = Attachments() - attachment.created_by = request.user - attachment.file_name = request.FILES.get("lead_attachment").name + attachment.created_by = request.profile + attachment.file_name = request.FILES.get( + "lead_attachment").name attachment.lead = lead_obj attachment.attachment = request.FILES.get("lead_attachment") attachment.save() - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter( + id=team, org=request.org) if teams_ids.exists(): lead_obj.teams.add(team) else: @@ -220,9 +230,11 @@ def post(self, request, *args, **kwargs): status=status.HTTP_400_BAD_REQUEST, ) if params.get("assigned_to"): - assinged_to_users_ids = json.loads(params.get("assigned_to")) + assinged_to_users_ids = json.loads( + params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter( + id=user_id, org=request.org) if user.exists(): lead_obj.assigned_to.add(user_id) else: @@ -234,12 +246,13 @@ def post(self, request, *args, **kwargs): ) if params.get("status") == "converted": account_object = Account.objects.create( - created_by=request.user, + created_by=request.profile, name=lead_obj.account_name, email=lead_obj.email, phone=lead_obj.phone, description=params.get("description"), website=params.get("website"), + org=request.org ) account_object.billing_address_line = lead_obj.address_line account_object.billing_street = lead_obj.street @@ -259,12 +272,14 @@ def post(self, request, *args, **kwargs): account_object.tags.add(tag) if params.get("assigned_to"): - assigned_to_list = json.loads(params.getlist("assigned_to")) + assigned_to_list = json.loads( + params.getlist("assigned_to")) recipients = assigned_to_list + current_site = get_current_site(self.request) send_email_to_assigned_user.delay( recipients, lead_obj.id, - domain=settings.Domain, + domain=current_site.domain, protocol=request.scheme, ) return Response( @@ -302,10 +317,10 @@ def get_context_data(self, **kwargs): user_assgn_list = [ assigned_to.id for assigned_to in self.lead_obj.assigned_to.all() ] - if self.request.user == self.lead_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: + if self.request.profile == self.lead_obj.created_by: + user_assgn_list.append(self.request.profile.id) + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.id not in user_assgn_list: return Response( { "error": True, @@ -315,7 +330,8 @@ def get_context_data(self, **kwargs): ) comments = Comment.objects.filter(lead=self.lead_obj).order_by("-id") - attachments = Attachments.objects.filter(lead=self.lead_obj).order_by("-id") + attachments = Attachments.objects.filter( + lead=self.lead_obj).order_by("-id") assigned_data = [] for each in self.lead_obj.assigned_to.all(): assigned_dict = {} @@ -323,33 +339,34 @@ def get_context_data(self, **kwargs): assigned_dict["name"] = each.email assigned_data.append(assigned_dict) - if self.request.user.is_superuser or self.request.user.role == "ADMIN": + if self.request.user.is_superuser or self.request.profile.role == "ADMIN": users_mention = list( - User.objects.filter( - is_active=True, - ).values("username") + Profile.objects.filter( + is_active=True, org=self.request.org + ).values("user__username") ) - elif self.request.user != self.lead_obj.created_by: + elif self.request.profile != self.lead_obj.created_by: users_mention = [{"username": self.lead_obj.created_by.username}] else: - users_mention = list(self.lead_obj.assigned_to.all().values("username")) - if self.request.user.role == "ADMIN" or self.request.user.is_superuser: - users = User.objects.filter( - is_active=True, - ).order_by("email") + users_mention = list( + self.lead_obj.assigned_to.all().values("user__username")) + if self.request.profile.role == "ADMIN" or self.request.user.is_superuser: + users = Profile.objects.filter( + is_active=True, org=self.request.org + ).order_by("user__email") else: - users = User.objects.filter( - role="ADMIN", - ).order_by("email") + users = Profile.objects.filter( + role="ADMIN", org=self.request.org + ).order_by("user__email") user_assgn_list = [ assigned_to.id for assigned_to in self.lead_obj.get_assigned_users_not_in_teams ] - if self.request.user == self.lead_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: + if self.request.profile == self.lead_obj.created_by: + user_assgn_list.append(self.request.profile.id) + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.id not in user_assgn_list: return Response( { "error": True, @@ -360,7 +377,8 @@ def get_context_data(self, **kwargs): team_ids = [user.id for user in self.lead_obj.get_team_users] all_user_ids = [user.id for user in users] users_excluding_team_id = set(all_user_ids) - set(team_ids) - users_excluding_team = User.objects.filter(id__in=users_excluding_team_id) + users_excluding_team = Profile.objects.filter( + id__in=users_excluding_team_id) context.update( { "lead_obj": LeadSerializer(self.lead_obj).data, @@ -370,19 +388,20 @@ def get_context_data(self, **kwargs): "assigned_data": assigned_data, } ) - context["users"] = UserSerializer(users, many=True).data - context["users_excluding_team"] = UserSerializer( + context["users"] = ProfileSerializer(users, many=True).data + context["users_excluding_team"] = ProfileSerializer( users_excluding_team, many=True ).data context["source"] = LEAD_SOURCE context["status"] = LEAD_STATUS - context["teams"] = TeamsSerializer(Teams.objects.all(), many=True).data + context["teams"] = TeamsSerializer(Teams.objects.filter( + org=self.request.org), many=True).data context["countries"] = COUNTRIES return context @swagger_auto_schema( - tags=["Leads"], + tags=["Leads"], manual_parameters=swagger_params.organization_params ) def get(self, request, pk, **kwargs): self.lead_obj = self.get_object(pk) @@ -400,10 +419,15 @@ def post(self, request, pk, **kwargs): ) context = {} self.lead_obj = Lead.objects.get(pk=pk) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.lead_obj.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: if not ( - (self.request.user == self.lead_obj.created_by) - or (self.request.user in self.lead_obj.assigned_to.all()) + (self.request.profile == self.lead_obj.created_by) + or (self.request.profile in self.lead_obj.assigned_to.all()) ): return Response( { @@ -417,18 +441,21 @@ def post(self, request, pk, **kwargs): if params.get("comment"): comment_serializer.save( lead_id=self.lead_obj.id, - commented_by_id=self.request.user.id, + commented_by_id=self.request.profile.id, ) if self.request.FILES.get("lead_attachment"): attachment = Attachments() - attachment.created_by = self.request.user - attachment.file_name = self.request.FILES.get("lead_attachment").name + attachment.created_by = self.request.profile + attachment.file_name = self.request.FILES.get( + "lead_attachment").name attachment.lead = self.lead_obj - attachment.attachment = self.request.FILES.get("lead_attachment") + attachment.attachment = self.request.FILES.get( + "lead_attachment") attachment.save() - comments = Comment.objects.filter(lead__id=self.lead_obj.id).order_by("-id") + comments = Comment.objects.filter( + lead__id=self.lead_obj.id).order_by("-id") attachments = Attachments.objects.filter(lead__id=self.lead_obj.id).order_by( "-id" ) @@ -452,6 +479,11 @@ def put(self, request, pk, **kwargs): ) data = {} self.lead_obj = self.get_object(pk) + if self.lead_obj.org != request.org: + return Response( + {"error": True, "errors": "User company does not match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) serializer = LeadCreateSerializer( data=params, instance=self.lead_obj, @@ -479,27 +511,30 @@ def put(self, request, pk, **kwargs): assigned_to_list = list( lead_obj.assigned_to.all().values_list("id", flat=True) ) - recipients = list(set(assigned_to_list) - set(previous_assigned_to_users)) + recipients = list(set(assigned_to_list) - + set(previous_assigned_to_users)) + current_site = get_current_site(self.request) send_email_to_assigned_user.delay( recipients, lead_obj.id, - domain=settings.Domain, + domain=current_site.domain, protocol=request.scheme, ) if request.FILES.get("lead_attachment"): attachment = Attachments() - attachment.created_by = request.user - attachment.file_name = request.FILES.get("lead_attachment").name + attachment.created_by = request.profile + attachment.file_name = request.FILES.get( + "lead_attachment").name attachment.lead = lead_obj attachment.attachment = request.FILES.get("lead_attachment") attachment.save() - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": lead_obj.teams.clear() if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter(id=team, org=request.org) if teams_ids.exists(): lead_obj.teams.add(team) else: @@ -514,9 +549,10 @@ def put(self, request, pk, **kwargs): lead_obj.assigned_to.clear() if params.get("assigned_to"): - assinged_to_users_ids = json.loads(params.get("assigned_to")) + assinged_to_users_ids = json.loads( + params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter(id=user_id, org=request.org) if user.exists(): lead_obj.assigned_to.add(user_id) else: @@ -531,13 +567,14 @@ def put(self, request, pk, **kwargs): if params.get("status") == "converted": account_object = Account.objects.create( - created_by=request.user, + created_by=request.profile, name=lead_obj.account_name, email=lead_obj.email, phone=lead_obj.phone, description=params.get("description"), website=params.get("website"), lead=lead_obj, + org=request.org ) account_object.billing_address_line = lead_obj.address_line account_object.billing_street = lead_obj.street @@ -559,10 +596,11 @@ def put(self, request, pk, **kwargs): # account_object.assigned_to.add(*params.getlist('assigned_to')) assigned_to_list = json.loads(params.get("assigned_to")) recipients = assigned_to_list + current_site = get_current_site(self.request) send_email_to_assigned_user.delay( recipients, lead_obj.id, - domain=settings.Domain, + domain=current_site.domain, protocol=request.scheme, ) @@ -587,15 +625,15 @@ def put(self, request, pk, **kwargs): ) @swagger_auto_schema( - tags=["Leads"], + tags=["Leads"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, **kwargs): self.object = self.get_object(pk) if ( - request.user.role == "ADMIN" + request.profile.role == "ADMIN" or request.user.is_superuser - or request.user == self.object.created_by - ): + or request.profile == self.object.created_by + ) and self.object.org == request.org: self.object.delete() return Response( {"error": False, "message": "Lead deleted Successfully"}, @@ -622,8 +660,9 @@ def post(self, request, *args, **kwargs): create_lead_from_file.delay( lead_form.validated_rows, lead_form.invalid_rows, - request.user.id, + request.profile.id, request.get_host(), + request.org.id ) return Response( {"error": False, "message": "Leads created Successfully"}, @@ -648,12 +687,13 @@ def get_object(self, pk): tags=["Leads"], manual_parameters=swagger_params.lead_comment_edit_params ) def put(self, request, pk, format=None): - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data obj = self.get_object(pk) if ( - request.user.role == "ADMIN" + request.profile.role == "ADMIN" or request.user.is_superuser - or request.user == obj.commented_by + or request.profile == obj.commented_by ): serializer = LeadCommentSerializer(obj, data=params) if params.get("comment"): @@ -677,14 +717,14 @@ def put(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["Leads"], + tags=["Leads"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.get_object(pk) if ( - request.user.role == "ADMIN" + request.profile.role == "ADMIN" or request.user.is_superuser - or request.user == self.object.commented_by + or request.profile == self.object.commented_by ): self.object.delete() return Response( @@ -707,14 +747,14 @@ class LeadAttachmentView(APIView): permission_classes = (IsAuthenticated,) @swagger_auto_schema( - tags=["Leads"], + tags=["Leads"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.model.objects.get(pk=pk) if ( - request.user.role == "ADMIN" + request.profile.role == "ADMIN" or request.user.is_superuser - or request.user == self.object.created_by + or request.profile == self.object.created_by ): self.object.delete() return Response( @@ -736,7 +776,8 @@ class CreateLeadFromSite(APIView): tags=["Leads"], manual_parameters=swagger_params.create_lead_from_site ) def post(self, request, *args, **kwargs): - params = request.query_params if len(request.data) == 0 else request.data + params = request.query_params if len( + request.data) == 0 else request.data api_key = params.get("apikey") # api_setting = APISettings.objects.filter( # website=website_address, apikey=api_key).first() @@ -764,6 +805,7 @@ def post(self, request, *args, **kwargs): phone=params.get("phone"), is_active=True, created_by=user, + org=api_setting.org ) lead.assigned_to.add(user) # Send Email to Assigned Users @@ -778,6 +820,7 @@ def post(self, request, *args, **kwargs): description=params.get("message"), created_by=user, is_active=True, + org=api_setting.org ) contact.assigned_to.add(user) diff --git a/opportunity/migrations/0007_auto_20210913_1918.py b/opportunity/migrations/0007_auto_20210913_1918.py new file mode 100644 index 0000000..4a90fb5 --- /dev/null +++ b/opportunity/migrations/0007_auto_20210913_1918.py @@ -0,0 +1,35 @@ +# Generated by Django 3.2 on 2021-09-13 13:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0034_auto_20210913_1918'), + ('opportunity', '0006_remove_opportunity_company'), + ] + + operations = [ + migrations.AddField( + model_name='opportunity', + name='company', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.company'), + ), + migrations.AlterField( + model_name='opportunity', + name='assigned_to', + field=models.ManyToManyField(related_name='opportunity_assigned_to', to='common.Profile'), + ), + migrations.AlterField( + model_name='opportunity', + name='closed_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.profile'), + ), + migrations.AlterField( + model_name='opportunity', + name='created_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity_created_by', to='common.profile'), + ), + ] diff --git a/opportunity/migrations/0008_rename_company_opportunity_org.py b/opportunity/migrations/0008_rename_company_opportunity_org.py new file mode 100644 index 0000000..8bc51a2 --- /dev/null +++ b/opportunity/migrations/0008_rename_company_opportunity_org.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-09-22 12:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('opportunity', '0007_auto_20210913_1918'), + ] + + operations = [ + migrations.RenameField( + model_name='opportunity', + old_name='company', + new_name='org', + ), + ] diff --git a/opportunity/migrations/0009_auto_20211006_1251.py b/opportunity/migrations/0009_auto_20211006_1251.py new file mode 100644 index 0000000..c037dba --- /dev/null +++ b/opportunity/migrations/0009_auto_20211006_1251.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.7 on 2021-10-06 07:21 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0037_alter_profile_org'), + ('opportunity', '0008_rename_company_opportunity_org'), + ] + + operations = [ + migrations.AlterField( + model_name='opportunity', + name='closed_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='oppurtunity_closed_by', to='common.profile'), + ), + migrations.AlterField( + model_name='opportunity', + name='org', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='oppurtunity_org', to='common.org'), + ), + ] diff --git a/opportunity/models.py b/opportunity/models.py index dc0884b..10d23cc 100644 --- a/opportunity/models.py +++ b/opportunity/models.py @@ -5,7 +5,7 @@ from accounts.models import Account, Tags from contacts.models import Contact -from common.models import User +from common.models import Org, Profile from common.utils import STAGES, SOURCES, CURRENCY_CODES from teams.models import Teams @@ -33,13 +33,13 @@ class Opportunity(models.Model): ) probability = models.IntegerField(default=0, blank=True, null=True) contacts = models.ManyToManyField(Contact) - closed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) + closed_by = models.ForeignKey(Profile, on_delete=models.SET_NULL, null=True, blank=True, related_name="oppurtunity_closed_by") # closed_on = models.DateTimeField(blank=True, null=True) closed_on = models.DateField(blank=True, null=True) description = models.TextField(blank=True, null=True) - assigned_to = models.ManyToManyField(User, related_name="opportunity_assigned_to") + assigned_to = models.ManyToManyField(Profile, related_name="opportunity_assigned_to") created_by = models.ForeignKey( - User, + Profile, related_name="opportunity_created_by", on_delete=models.SET_NULL, null=True, @@ -48,6 +48,9 @@ class Opportunity(models.Model): is_active = models.BooleanField(default=False) tags = models.ManyToManyField(Tags, blank=True) teams = models.ManyToManyField(Teams, related_name="oppurtunity_teams") + org = models.ForeignKey( + Org, on_delete=models.SET_NULL, null=True, blank=True, related_name="oppurtunity_org" + ) class Meta: ordering = ["-created_on"] @@ -62,18 +65,18 @@ def created_on_arrow(self): @property def get_team_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) - return User.objects.filter(id__in=team_user_ids) + return Profile.objects.filter(id__in=team_user_ids) @property def get_team_and_assigned_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = team_user_ids + assigned_user_ids - return User.objects.filter(id__in=user_ids) + return Profile.objects.filter(id__in=user_ids) @property def get_assigned_users_not_in_teams(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = set(assigned_user_ids) - set(team_user_ids) - return User.objects.filter(id__in=list(user_ids)) + return Profile.objects.filter(id__in=list(user_ids)) diff --git a/opportunity/serializer.py b/opportunity/serializer.py index e82719f..f9905b9 100644 --- a/opportunity/serializer.py +++ b/opportunity/serializer.py @@ -3,7 +3,7 @@ from accounts.serializer import AccountSerializer from contacts.serializer import ContactSerializer from teams.serializer import TeamsSerializer -from common.serializer import UserSerializer, AttachmentsSerializer +from common.serializer import ProfileSerializer, AttachmentsSerializer from accounts.models import Tags @@ -15,10 +15,10 @@ class Meta: class OpportunitySerializer(serializers.ModelSerializer): account = AccountSerializer() - closed_by = UserSerializer() - created_by = UserSerializer() + closed_by = ProfileSerializer() + created_by = ProfileSerializer() tags = TagsSerializer(read_only=True, many=True) - assigned_to = UserSerializer(read_only=True, many=True) + assigned_to = ProfileSerializer(read_only=True, many=True) contacts = ContactSerializer(read_only=True, many=True) teams = TeamsSerializer(read_only=True, many=True) opportunity_attachment = AttachmentsSerializer(read_only=True, many=True) @@ -61,11 +61,12 @@ def __init__(self, *args, **kwargs): opportunity_view = kwargs.pop("opportunity", False) request_obj = kwargs.pop("request_obj", None) super(OpportunityCreateSerializer, self).__init__(*args, **kwargs) + self.org = request_obj.org def validate_name(self, name): if self.instance: if ( - Opportunity.objects.filter(name__iexact=name) + Opportunity.objects.filter(name__iexact=name, org=self.org) .exclude(id=self.instance.id) .exists() ): @@ -74,7 +75,7 @@ def validate_name(self, name): ) else: - if Opportunity.objects.filter(name__iexact=name).exists(): + if Opportunity.objects.filter(name__iexact=name, org=self.org).exists(): raise serializers.ValidationError( "Opportunity already exists with this name" ) @@ -96,6 +97,7 @@ class Meta: "created_on", "is_active", "created_on_arrow", + "org" # "get_team_users", # "get_team_and_assigned_users", # "get_assigned_users_not_in_teams", diff --git a/opportunity/swagger_params.py b/opportunity/swagger_params.py index bad0ccd..95f8b7b 100644 --- a/opportunity/swagger_params.py +++ b/opportunity/swagger_params.py @@ -1,6 +1,15 @@ from drf_yasg import openapi +organization_params_in_header = openapi.Parameter( + "org", openapi.IN_HEADER, required=True, type=openapi.TYPE_INTEGER +) + +organization_params = [ + organization_params_in_header, +] + opportunity_list_get_params = [ + organization_params_in_header, openapi.Parameter("name", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("account", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("stage", openapi.IN_QUERY, type=openapi.TYPE_STRING), @@ -9,6 +18,7 @@ ] opportunity_detail_get_params = [ + organization_params_in_header, openapi.Parameter( "opportunity_attachment", openapi.IN_QUERY, @@ -18,6 +28,7 @@ ] opportunity_create_post_params = [ + organization_params_in_header, openapi.Parameter( "name", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING ), @@ -43,5 +54,6 @@ ] opportunity_comment_edit_params = [ + organization_params_in_header, openapi.Parameter("comment", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] diff --git a/opportunity/tasks.py b/opportunity/tasks.py index 8b0d039..43384f4 100644 --- a/opportunity/tasks.py +++ b/opportunity/tasks.py @@ -5,7 +5,7 @@ from django.shortcuts import reverse from django.template.loader import render_to_string -from accounts.models import User +from common.models import User from opportunity.models import Opportunity app = Celery("redis://") @@ -20,12 +20,12 @@ def send_email_to_assigned_user( created_by = opportunity.created_by for user in recipients: recipients_list = [] - user = User.objects.filter(id=user, is_active=True).first() - if user: - recipients_list.append(user.email) + profile = Profile.objects.filter(id=user, is_active=True).first() + if profile: + recipients_list.append(profile.user.email) context = {} context["url"] = protocol + "://" + domain - context["user"] = user + context["user"] = profile.user context["opportunity"] = opportunity context["created_by"] = created_by subject = "Assigned an opportunity for you." diff --git a/opportunity/views.py b/opportunity/views.py index 66ff932..c7ae172 100644 --- a/opportunity/views.py +++ b/opportunity/views.py @@ -12,10 +12,10 @@ ) from accounts.models import Account, Tags from accounts.serializer import AccountSerializer -from common.models import User, Attachments, Comment +from common.models import User, Attachments, Comment, Profile from common.custom_auth import JSONWebTokenAuthentication from common.serializer import ( - UserSerializer, + ProfileSerializer, CommentSerializer, AttachmentsSerializer, LeadCommentSerializer, @@ -40,7 +40,6 @@ from rest_framework.pagination import LimitOffsetPagination from drf_yasg.utils import swagger_auto_schema import json -from django.conf import settings class OpportunityListView(APIView, LimitOffsetPagination): @@ -55,18 +54,18 @@ def get_context_data(self, **kwargs): if len(self.request.data) == 0 else self.request.data ) - queryset = self.model.objects.all() - accounts = Account.objects.all() - contacts = Contact.objects.all() - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + queryset = self.model.objects.filter(org=self.request.org) + accounts = Account.objects.filter(org=self.request.org) + contacts = Contact.objects.filter(org=self.request.org) + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: queryset = queryset.filter( - Q(created_by=self.request.user) | Q(assigned_to=self.request.user) + Q(created_by=self.request.profile) | Q(assigned_to=self.request.profile) ).distinct() accounts = accounts.filter( - Q(created_by=self.request.user) | Q(assigned_to=self.request.user) + Q(created_by=self.request.profile) | Q(assigned_to=self.request.profile) ).distinct() contacts = contacts.filter( - Q(created_by=self.request.user) | Q(assigned_to=self.request.user) + Q(created_by=self.request.profile) | Q(assigned_to=self.request.profile) ).distinct() if params: @@ -112,18 +111,18 @@ def get_context_data(self, **kwargs): } ) context["opportunities"] = opportunities - context["users"] = UserSerializer( - User.objects.filter(is_active=True).order_by("email"), + context["users"] = ProfileSerializer( + Profile.objects.filter(is_active=True, org=self.request.org).order_by("user__email"), many=True, ).data - tag_ids = list(set(Opportunity.objects.all().values_list("tags", flat=True))) + tag_ids = list(set(Opportunity.objects.filter(org=self.request.org).values_list("tags", flat=True))) context["tags"] = TagsSerializer( Tags.objects.filter(id__in=tag_ids), many=True ).data context["accounts_list"] = AccountSerializer(accounts, many=True).data context["contacts_list"] = ContactSerializer(contacts, many=True).data - if self.request.user == "ADMIN": - context["teams_list"] = TeamsSerializer(Teams.objects.all(), many=True).data + if self.request.profile == "ADMIN": + context["teams_list"] = TeamsSerializer(Teams.objects.filter(org=self.request.org), many=True).data context["stage"] = STAGES context["lead_source"] = SOURCES context["currency"] = CURRENCY_CODES @@ -148,14 +147,15 @@ def post(self, request, *args, **kwargs): serializer = OpportunityCreateSerializer(data=params, request_obj=request) if serializer.is_valid(): opportunity_obj = serializer.save( - created_by=request.user, + created_by=request.profile, closed_on=params.get("due_date"), + org=request.org ) if params.get("contacts"): contacts = json.loads(params.get("contacts")) for contact in contacts: - obj_contact = Contact.objects.filter(id=contact) + obj_contact = Contact.objects.filter(id=contact, org=request.org) if obj_contact.exists(): opportunity_obj.contacts.add(contact) else: @@ -178,13 +178,13 @@ def post(self, request, *args, **kwargs): if params.get("stage"): stage = params.get("stage") if stage in ["CLOSED WON", "CLOSED LOST"]: - opportunity_obj.closed_by = self.request.user + opportunity_obj.closed_by = self.request.profile - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - obj_team = Teams.objects.filter(id=team) + obj_team = Teams.objects.filter(id=team, org=request.org) if obj_team.exists(): opportunity_obj.teams.add(team) else: @@ -198,8 +198,8 @@ def post(self, request, *args, **kwargs): assinged_to_users_ids = json.loads(params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) - if user.exists(): + profile = Profile.objects.filter(id=user_id, org=request.org) + if profile.exists(): opportunity_obj.assigned_to.add(user_id) else: opportunity_obj.delete() @@ -211,7 +211,7 @@ def post(self, request, *args, **kwargs): if self.request.FILES.get("opportunity_attachment"): attachment = Attachments() - attachment.created_by = self.request.user + attachment.created_by = self.request.profile attachment.file_name = self.request.FILES.get( "opportunity_attachment" ).name @@ -224,10 +224,11 @@ def post(self, request, *args, **kwargs): ) recipients = assigned_to_list + current_site = get_current_site(self.request) send_email_to_assigned_user.delay( recipients, opportunity_obj.id, - domain=settings.Domain, + domain=current_site.domain, protocol=self.request.scheme, ) return Response( @@ -257,10 +258,15 @@ def put(self, request, pk, format=None): params = request.query_params if len(request.data) == 0 else request.data opportunity_object = self.get_object(pk=pk) data = {} - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if opportunity_object.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN, + ) + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: if not ( - (self.request.user == opportunity_object.created_by) - or (self.request.user in opportunity_object.assigned_to.all()) + (self.request.profile == opportunity_object.created_by) + or (self.request.profile in opportunity_object.assigned_to.all()) ): return Response( { @@ -286,7 +292,7 @@ def put(self, request, pk, format=None): if params.get("contacts"): contacts = json.loads(params.get("contacts")) for contact in contacts: - obj_contact = Contact.objects.filter(id=contact) + obj_contact = Contact.objects.filter(id=contact, org=request.org) if obj_contact.exists(): opportunity_object.contacts.add(contact) else: @@ -309,14 +315,14 @@ def put(self, request, pk, format=None): if params.get("stage"): stage = params.get("stage") if stage in ["CLOSED WON", "CLOSED LOST"]: - opportunity_object.closed_by_id = self.request.user.id + opportunity_object.closed_by_id = self.request.profile.id - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": opportunity_object.teams.clear() if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - obj_team = Teams.objects.filter(id=team) + obj_team = Teams.objects.filter(id=team, org=request.org) if obj_team.exists(): opportunity_object.teams.add(team) else: @@ -330,8 +336,8 @@ def put(self, request, pk, format=None): if params.get("assigned_to"): assinged_to_users_ids = json.loads(params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) - if user.exists(): + profile = Profile.objects.filter(id=user_id, org=request.org) + if profile.exists(): opportunity_object.assigned_to.add(user_id) else: data["assigned_to"] = "Please enter valid User" @@ -342,7 +348,7 @@ def put(self, request, pk, format=None): if self.request.FILES.get("opportunity_attachment"): attachment = Attachments() - attachment.created_by = self.request.user + attachment.created_by = self.request.profile attachment.file_name = self.request.FILES.get( "opportunity_attachment" ).name @@ -354,10 +360,11 @@ def put(self, request, pk, format=None): opportunity_object.assigned_to.all().values_list("id", flat=True) ) recipients = list(set(assigned_to_list) - set(previous_assigned_to_users)) + current_site = get_current_site(self.request) send_email_to_assigned_user.delay( recipients, opportunity_object.id, - domain=settings.Domain, + domain=current_site.domain, protocol=self.request.scheme, ) return Response( @@ -370,12 +377,17 @@ def put(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["Opportunities"], + tags=["Opportunities"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.get_object(pk) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: - if self.request.user != self.object.created_by: + if self.object.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile != self.object.created_by: return Response( { "error": True, @@ -390,16 +402,21 @@ def delete(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["Opportunities"], + tags=["Opportunities"], manual_parameters=swagger_params.organization_params ) def get(self, request, pk, format=None): self.opportunity = self.get_object(pk=pk) context = {} context["opportunity_obj"] = OpportunitySerializer(self.opportunity).data - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.opportunity.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN, + ) + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: if not ( - (self.request.user == self.opportunity.created_by) - or (self.request.user in self.opportunity.assigned_to.all()) + (self.request.profile == self.opportunity.created_by) + or (self.request.profile in self.opportunity.assigned_to.all()) ): return Response( { @@ -412,22 +429,22 @@ def get(self, request, pk, format=None): comment_permission = ( True if ( - self.request.user == self.opportunity.created_by + self.request.profile == self.opportunity.created_by or self.request.user.is_superuser - or self.request.user.role == "ADMIN" + or self.request.profile.role == "ADMIN" ) else False ) - if self.request.user.is_superuser or self.request.user.role == "ADMIN": + if self.request.user.is_superuser or self.request.profile.role == "ADMIN": users_mention = list( - User.objects.filter( - is_active=True, - ).values("username") + Profile.objects.filter( + is_active=True, org=self.request.org + ).values("user__username") ) - elif self.request.user != self.opportunity.created_by: + elif self.request.profile != self.opportunity.created_by: if self.opportunity.created_by: - users_mention = [{"username": self.opportunity.created_by.username}] + users_mention = [{"username": self.opportunity.created_by.user.username}] else: users_mention = [] else: @@ -444,10 +461,10 @@ def get(self, request, pk, format=None): "contacts": ContactSerializer( self.opportunity.contacts.all(), many=True ).data, - "users": UserSerializer( - User.objects.filter( - is_active=True, - ).order_by("email"), + "users": ProfileSerializer( + Profile.objects.filter( + is_active=True, org=self.request.org + ).order_by("user__email"), many=True, ).data, "stage": STAGES, @@ -471,11 +488,16 @@ def post(self, request, pk, **kwargs): ) context = {} self.opportunity_obj = Opportunity.objects.get(pk=pk) + if self.opportunity_obj.org != request.org: + return Response( + {"error": True, "errors": "User company doesnot match with header...."}, + status=status.HTTP_403_FORBIDDEN + ) comment_serializer = CommentSerializer(data=params) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.user.is_superuser: if not ( - (self.request.user == self.opportunity_obj.created_by) - or (self.request.user in self.opportunity_obj.assigned_to.all()) + (self.request.profile == self.opportunity_obj.created_by) + or (self.request.profile in self.opportunity_obj.assigned_to.all()) ): return Response( { @@ -488,12 +510,12 @@ def post(self, request, pk, **kwargs): if params.get("comment"): comment_serializer.save( opportunity_id=self.opportunity_obj.id, - commented_by_id=self.request.user.id, + commented_by_id=self.request.profile.id, ) if self.request.FILES.get("opportunity_attachment"): attachment = Attachments() - attachment.created_by = self.request.user + attachment.created_by = self.request.profile attachment.file_name = self.request.FILES.get( "opportunity_attachment" ).name @@ -533,9 +555,9 @@ def put(self, request, pk, format=None): params = request.query_params if len(request.data) == 0 else request.data obj = self.get_object(pk) if ( - request.user.role == "ADMIN" + request.profile.role == "ADMIN" or request.user.is_superuser - or request.user == obj.commented_by + or request.profile == obj.commented_by ): serializer = CommentSerializer(obj, data=params) if params.get("comment"): @@ -559,14 +581,14 @@ def put(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["Opportunities"], + tags=["Opportunities"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.get_object(pk) if ( - request.user.role == "ADMIN" + request.profile.role == "ADMIN" or request.user.is_superuser - or request.user == self.object.commented_by + or request.profile == self.object.commented_by ): self.object.delete() return Response( @@ -589,14 +611,14 @@ class OpportunityAttachmentView(APIView): permission_classes = (IsAuthenticated,) @swagger_auto_schema( - tags=["Opportunities"], + tags=["Opportunities"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.model.objects.get(pk=pk) if ( - request.user.role == "ADMIN" + request.profile.role == "ADMIN" or request.user.is_superuser - or request.user == self.object.created_by + or request.profile == self.object.created_by ): self.object.delete() return Response( diff --git a/requirements.txt b/requirements.txt index ef68329..9a4e914 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,46 +1,47 @@ +arrow==1.2.0 black==20.8b1 boto==2.49.0 +boto3==1.18.52 CacheControl==0.12.6 -celery==5.0.5 +celery==5.1.2 codacy-coverage==1.3.11 -contextlib2==0.6.0.post1 -coverage==5.5 +contextlib2==21.6.0 +coverage==6.0 cssselect==1.1.0 -distlib==0.3.1 -distro==1.5.0 -django-cors-headers==3.7.0 -django-phonenumber-field==5.1.0 -django-ses==2.0.0 +distlib==0.3.3 +distro==1.6.0 +django-cors-headers==3.10.0 +django-phonenumber-field==5.2.0 +django-ses==2.3.0 django-settings-export==1.2.1 django-storages==1.11.1 djangorestframework-jwt==1.11.0 -docker==5.0.0 +docker==5.0.2 drf-compound-fields==2.0.0 drf-yasg==1.20.0 html5lib==1.1 -importlib-metadata==4.0.1 +importlib-metadata==4.8.1 ipaddr==2.2.0 lockfile==0.12.2 lxml==4.6.3 -openpyxl==3.0.7 +openpyxl==3.0.9 pdfkit==0.6.1 -phonenumbers==8.12.23 -Pillow==8.2.0 +phonenumbers==8.12.33 +Pillow==8.3.2 pip-check==2.6 -pipdeptree==2.0.0 -progress==1.5 +pipdeptree==2.1.0 +progress==1.6 psycopg2==2.8.6 -psycopg2-binary==2.8.6 -pytest-django==4.3.0 -python-dotenv==0.17.1 +pytest-django==4.4.0 +python-dotenv==0.19.0 python-memcached==1.59 pytoml==0.1.21 redis==3.5.3 retrying==1.3.3 -sentry-sdk==1.1.0 -setuptools==56.2.0 +sentry-sdk==1.4.3 +setuptools==58.2.0 uWSGI==2.0.19.1 -wheel==0.36.2 -whitenoise==5.2.0 +wheel==0.37.0 +whitenoise==5.3.0 xlrd==2.0.1 xlwt==1.3.0 diff --git a/tasks/migrations/0007_auto_20210913_1918.py b/tasks/migrations/0007_auto_20210913_1918.py new file mode 100644 index 0000000..2d5dc29 --- /dev/null +++ b/tasks/migrations/0007_auto_20210913_1918.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2 on 2021-09-13 13:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0034_auto_20210913_1918'), + ('tasks', '0006_remove_task_company'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='company', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.company'), + ), + migrations.AlterField( + model_name='task', + name='assigned_to', + field=models.ManyToManyField(related_name='users_tasks', to='common.Profile'), + ), + migrations.AlterField( + model_name='task', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='task_created', to='common.profile'), + ), + ] diff --git a/tasks/migrations/0008_rename_company_task_org.py b/tasks/migrations/0008_rename_company_task_org.py new file mode 100644 index 0000000..31eb703 --- /dev/null +++ b/tasks/migrations/0008_rename_company_task_org.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-09-22 12:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tasks', '0007_auto_20210913_1918'), + ] + + operations = [ + migrations.RenameField( + model_name='task', + old_name='company', + new_name='org', + ), + ] diff --git a/tasks/migrations/0009_alter_task_org.py b/tasks/migrations/0009_alter_task_org.py new file mode 100644 index 0000000..f727870 --- /dev/null +++ b/tasks/migrations/0009_alter_task_org.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.7 on 2021-10-06 07:21 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0037_alter_profile_org'), + ('tasks', '0008_rename_company_task_org'), + ] + + operations = [ + migrations.AlterField( + model_name='task', + name='org', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='task_org', to='common.org'), + ), + ] diff --git a/tasks/models.py b/tasks/models.py index 4b2518d..6805a3b 100644 --- a/tasks/models.py +++ b/tasks/models.py @@ -1,6 +1,6 @@ import arrow from django.db import models -from common.models import User +from common.models import Profile, Org from accounts.models import Account from contacts.models import Contact from django.utils.translation import ugettext_lazy as _ @@ -32,16 +32,19 @@ class Task(models.Model): contacts = models.ManyToManyField(Contact, related_name="contacts_tasks") - assigned_to = models.ManyToManyField(User, related_name="users_tasks") + assigned_to = models.ManyToManyField(Profile, related_name="users_tasks") created_by = models.ForeignKey( - User, + Profile, related_name="task_created", blank=True, null=True, on_delete=models.SET_NULL, ) teams = models.ManyToManyField(Teams, related_name="tasks_teams") + org = models.ForeignKey( + Org, on_delete=models.SET_NULL, null=True, blank=True, related_name="task_org" + ) class Meta: ordering = ["-due_date"] @@ -56,11 +59,11 @@ def created_on_arrow(self): @property def get_team_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) - return User.objects.filter(id__in=team_user_ids) + return Profile.objects.filter(id__in=team_user_ids) @property def get_team_and_assigned_users(self): team_user_ids = list(self.teams.values_list("users__id", flat=True)) assigned_user_ids = list(self.assigned_to.values_list("id", flat=True)) user_ids = team_user_ids + assigned_user_ids - return User.objects.filter(id__in=user_ids) + return Profile.objects.filter(id__in=user_ids) diff --git a/tasks/serializer.py b/tasks/serializer.py index 50a2605..841f649 100644 --- a/tasks/serializer.py +++ b/tasks/serializer.py @@ -1,7 +1,7 @@ from rest_framework import serializers from tasks.models import Task from common.serializer import ( - UserSerializer, + ProfileSerializer, AttachmentsSerializer, CommentSerializer, ) @@ -10,8 +10,8 @@ class TaskSerializer(serializers.ModelSerializer): - created_by = UserSerializer() - assigned_to = UserSerializer(read_only=True, many=True) + created_by = ProfileSerializer() + assigned_to = ProfileSerializer(read_only=True, many=True) contacts = ContactSerializer(read_only=True, many=True) teams = TeamsSerializer(read_only=True, many=True) task_attachment = AttachmentsSerializer(read_only=True, many=True) diff --git a/tasks/swagger_params.py b/tasks/swagger_params.py index 40a37b4..fc95200 100644 --- a/tasks/swagger_params.py +++ b/tasks/swagger_params.py @@ -1,12 +1,21 @@ from drf_yasg import openapi +organization_params_in_header = openapi.Parameter( + 'org', openapi.IN_HEADER, required=True, type=openapi.TYPE_INTEGER) + +organization_params = [ + organization_params_in_header, +] + task_list_get_params = [ + organization_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("priority", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] task_detail_post_params = [ + organization_params_in_header, openapi.Parameter( "task_attachment", openapi.IN_QUERY, @@ -16,6 +25,7 @@ ] task_create_post_params = [ + organization_params_in_header, openapi.Parameter( "title", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING ), @@ -35,5 +45,6 @@ ] task_comment_edit_params = [ + organization_params_in_header, openapi.Parameter("comment", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] diff --git a/tasks/views.py b/tasks/views.py index f3b4a66..94f0120 100644 --- a/tasks/views.py +++ b/tasks/views.py @@ -6,10 +6,10 @@ from contacts.models import Contact from contacts.serializer import ContactSerializer -from common.models import User, Attachments, Comment +from common.models import Profile, Attachments, Comment from common.custom_auth import JSONWebTokenAuthentication from common.serializer import ( - UserSerializer, + ProfileSerializer, CommentSerializer, AttachmentsSerializer, CommentSerializer, @@ -40,18 +40,18 @@ def get_context_data(self, **kwargs): if len(self.request.data) == 0 else self.request.data ) - queryset = self.model.objects.all() - accounts = Account.objects.all() - contacts = Contact.objects.all() - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + queryset = self.model.objects.filter(org=self.request.org) + accounts = Account.objects.filter(org=self.request.org) + contacts = Contact.objects.filter(org=self.request.org) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: queryset = queryset.filter( - Q(assigned_to__in=[self.request.user]) | Q(created_by=self.request.user) + Q(assigned_to__in=[self.request.profile]) | Q(created_by=self.request.profile) ) accounts = accounts.filter( - Q(created_by=self.request.user) | Q(assigned_to=self.request.user) + Q(created_by=self.request.profile) | Q(assigned_to=self.request.profile) ).distinct() contacts = contacts.filter( - Q(created_by=self.request.user) | Q(assigned_to=self.request.user) + Q(created_by=self.request.profile) | Q(assigned_to=self.request.profile) ).distinct() request_post = params @@ -84,19 +84,20 @@ def get_context_data(self, **kwargs): context["status"] = STATUS_CHOICES context["priority"] = PRIORITY_CHOICES users = [] - if self.request.user.role == "ADMIN" or self.request.user.is_superuser: - users = User.objects.filter( - is_active=True, - ).order_by("email") - elif self.request.user.google.all(): + if self.request.profile.role == "ADMIN" or self.request.profile.is_admin: + users = Profile.objects.filter( + is_active=True, org=self.request.org + ).order_by("user__email") + elif self.request.profile.google.all(): users = [] else: - users = User.objects.filter( - role="ADMIN", - ).order_by("email") - context["users"] = UserSerializer(users, many=True).data - if self.request.user == "ADMIN": - context["teams_list"] = TeamsSerializer(Teams.objects.all(), many=True).data + users = Profile.objects.filter( + role="ADMIN", org=self.request.org + ).order_by("user__email") + context["users"] = ProfileSerializer(users, many=True).data + if self.request.profile == "ADMIN": + context["teams_list"] = TeamsSerializer(Teams.objects.filter( + org=self.request.org), many=True).data context["accounts_list"] = AccountSerializer(accounts, many=True).data context["contacts_list"] = ContactSerializer(contacts, many=True).data return context @@ -121,14 +122,15 @@ def post(self, request, *args, **kwargs): serializer = TaskCreateSerializer(data=params, request_obj=request) if serializer.is_valid(): task_obj = serializer.save( - created_by=request.user, + created_by=request.profile, due_date=params.get("due_date"), + org=request.org ) if params.get("contacts"): contacts = json.loads(params.get("contacts")) for contact in contacts: obj_contact = Contact.objects.filter( - id=contact, + id=contact, org=request.org ) if obj_contact.exists(): task_obj.contacts.add(contact) @@ -139,11 +141,11 @@ def post(self, request, *args, **kwargs): {"error": True, "errors": data}, status=status.HTTP_400_BAD_REQUEST, ) - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": if params.get("teams"): teams = json.loads(params.get("teams")) for team in teams: - teams_ids = Teams.objects.filter(id=team) + teams_ids = Teams.objects.filter(id=team, org=request.org) if teams_ids.exists(): task_obj.teams.add(team) else: @@ -156,8 +158,8 @@ def post(self, request, *args, **kwargs): if params.get("assigned_to"): assinged_to_users_ids = json.loads(params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) - if user.exists(): + profile = Profile.objects.filter(id=user_id, org=request.org) + if profile.exists(): task_obj.assigned_to.add(user_id) else: task_obj.delete() @@ -190,10 +192,10 @@ def get_context_data(self, **kwargs): user_assgn_list = [ assigned_to.id for assigned_to in self.task_obj.assigned_to.all() ] - if self.request.user == self.task_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: + if self.request.profile == self.task_obj.created_by: + user_assgn_list.append(self.request.profile.id) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: + if self.request.profile.id not in user_assgn_list: return Response( { "error": True, @@ -205,31 +207,31 @@ def get_context_data(self, **kwargs): comments = Comment.objects.filter(task=self.task_obj).order_by("-id") attachments = Attachments.objects.filter(task=self.task_obj).order_by("-id") - assigned_data = self.task_obj.assigned_to.values("id", "email") + assigned_data = self.task_obj.assigned_to.values("id", "user__email") - if self.request.user.is_superuser or self.request.user.role == "ADMIN": + if self.request.profile.is_admin or self.request.profile.role == "ADMIN": users_mention = list( - User.objects.filter( - is_active=True, - ).values("username") + Profile.objects.filter( + is_active=True, org=self.request.org + ).values("user__username") ) - elif self.request.user != self.task_obj.created_by: - users_mention = [{"username": self.task_obj.created_by.username}] + elif self.request.profile != self.task_obj.created_by: + users_mention = [{"username": self.task_obj.created_by.user.username}] else: - users_mention = list(self.task_obj.assigned_to.all().values("username")) - if self.request.user.role == "ADMIN" or self.request.user.is_superuser: - users = User.objects.filter( - is_active=True, - ).order_by("email") + users_mention = list(self.task_obj.assigned_to.all().values("user__username")) + if self.request.profile.role == "ADMIN" or self.request.profile.is_admin: + users = Profile.objects.filter( + is_active=True, org=self.request.org + ).order_by("user__email") else: - users = User.objects.filter( - role="ADMIN", - ).order_by("email") + users = Profile.objects.filter( + role="ADMIN", org=self.request.org + ).order_by("user__email") - if self.request.user == self.task_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: + if self.request.profile == self.task_obj.created_by: + user_assgn_list.append(self.request.profile.id) + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: + if self.request.profile.id not in user_assgn_list: return Response( { "error": True, @@ -240,7 +242,7 @@ def get_context_data(self, **kwargs): team_ids = [user.id for user in self.task_obj.get_team_users] all_user_ids = users.values_list("id", flat=True) users_excluding_team_id = set(all_user_ids) - set(team_ids) - users_excluding_team = User.objects.filter(id__in=users_excluding_team_id) + users_excluding_team = Profile.objects.filter(id__in=users_excluding_team_id) context.update( { "task_obj": TaskSerializer(self.task_obj).data, @@ -250,15 +252,15 @@ def get_context_data(self, **kwargs): "assigned_data": assigned_data, } ) - context["users"] = UserSerializer(users, many=True).data - context["users_excluding_team"] = UserSerializer( + context["users"] = ProfileSerializer(users, many=True).data + context["users_excluding_team"] = ProfileSerializer( users_excluding_team, many=True ).data context["teams"] = TeamsSerializer(Teams.objects.all(), many=True).data return context @swagger_auto_schema( - tags=["Tasks"], + tags=["Tasks"], manual_parameters=swagger_params.organization_params ) def get(self, request, pk, **kwargs): self.task_obj = self.get_object(pk) @@ -276,10 +278,10 @@ def post(self, request, pk, **kwargs): ) context = {} self.task_obj = Task.objects.get(pk=pk) - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: if not ( - (self.request.user == self.task_obj.created_by) - or (self.request.user in self.task_obj.assigned_to.all()) + (self.request.profile == self.task_obj.created_by) + or (self.request.profile in self.task_obj.assigned_to.all()) ): return Response( { @@ -293,12 +295,12 @@ def post(self, request, pk, **kwargs): if params.get("comment"): comment_serializer.save( task_id=self.task_obj.id, - commented_by_id=self.request.user.id, + commented_by_id=self.request.profile.id, ) if self.request.FILES.get("task_attachment"): attachment = Attachments() - attachment.created_by = self.request.user + attachment.created_by = self.request.profile attachment.file_name = self.request.FILES.get("task_attachment").name attachment.task = self.task_obj attachment.attachment = self.request.FILES.get("task_attachment") @@ -351,7 +353,7 @@ def put(self, request, pk, **kwargs): {"error": True, "errors": data}, status=status.HTTP_400_BAD_REQUEST, ) - if self.request.user.role == "ADMIN": + if self.request.profile.role == "ADMIN": task_obj.teams.clear() if params.get("teams"): teams = json.loads(params.get("teams")) @@ -373,8 +375,8 @@ def put(self, request, pk, **kwargs): if params.get("assigned_to"): assinged_to_users_ids = json.loads(params.get("assigned_to")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) - if user.exists(): + profile = Profile.objects.filter(id=user_id, org=request.org) + if profile.exists(): task_obj.assigned_to.add(user_id) else: task_obj.delete() @@ -395,14 +397,14 @@ def put(self, request, pk, **kwargs): ) @swagger_auto_schema( - tags=["Tasks"], + tags=["Tasks"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, **kwargs): self.object = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.created_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.created_by ): self.object.delete() return Response( @@ -430,9 +432,9 @@ def put(self, request, pk, format=None): params = request.query_params if len(request.data) == 0 else request.data obj = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == obj.commented_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == obj.commented_by ): serializer = CommentSerializer(obj, data=params) if params.get("comment"): @@ -456,14 +458,14 @@ def put(self, request, pk, format=None): ) @swagger_auto_schema( - tags=["Tasks"], + tags=["Tasks"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.get_object(pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.commented_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.commented_by ): self.object.delete() return Response( @@ -486,14 +488,14 @@ class TaskAttachmentView(APIView): permission_classes = (IsAuthenticated,) @swagger_auto_schema( - tags=["Tasks"], + tags=["Tasks"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, format=None): self.object = self.model.objects.get(pk=pk) if ( - request.user.role == "ADMIN" - or request.user.is_superuser - or request.user == self.object.created_by + request.profile.role == "ADMIN" + or request.profile.is_admin + or request.profile == self.object.created_by ): self.object.delete() return Response( diff --git a/teams/migrations/0006_auto_20210913_1918.py b/teams/migrations/0006_auto_20210913_1918.py new file mode 100644 index 0000000..c7d7712 --- /dev/null +++ b/teams/migrations/0006_auto_20210913_1918.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2 on 2021-09-13 13:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0034_auto_20210913_1918'), + ('teams', '0005_remove_teams_company'), + ] + + operations = [ + migrations.AddField( + model_name='teams', + name='company', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.company'), + ), + migrations.AlterField( + model_name='teams', + name='created_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='teams_created', to='common.profile'), + ), + migrations.AlterField( + model_name='teams', + name='users', + field=models.ManyToManyField(related_name='user_teams', to='common.Profile'), + ), + ] diff --git a/teams/migrations/0007_rename_company_teams_org.py b/teams/migrations/0007_rename_company_teams_org.py new file mode 100644 index 0000000..57a04a2 --- /dev/null +++ b/teams/migrations/0007_rename_company_teams_org.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-09-22 12:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('teams', '0006_auto_20210913_1918'), + ] + + operations = [ + migrations.RenameField( + model_name='teams', + old_name='company', + new_name='org', + ), + ] diff --git a/teams/models.py b/teams/models.py index c61effb..f38ef1e 100644 --- a/teams/models.py +++ b/teams/models.py @@ -1,22 +1,25 @@ import arrow from django.db import models -from common.models import User +from common.models import User, Org, Profile from django.utils.translation import ugettext_lazy as _ class Teams(models.Model): name = models.CharField(max_length=100) description = models.TextField() - users = models.ManyToManyField(User, related_name="user_teams") + users = models.ManyToManyField(Profile, related_name="user_teams") created_on = models.DateTimeField(_("Created on"), auto_now_add=True) created_by = models.ForeignKey( - User, + Profile, related_name="teams_created", blank=True, null=True, on_delete=models.SET_NULL, ) + org = models.ForeignKey( + Org, on_delete=models.SET_NULL, null=True, blank=True + ) class Meta: ordering = ("id",) diff --git a/teams/serializer.py b/teams/serializer.py index 0925db4..9d50bf0 100644 --- a/teams/serializer.py +++ b/teams/serializer.py @@ -1,11 +1,11 @@ from teams.models import Teams from rest_framework import serializers -from common.serializer import UserSerializer +from common.serializer import ProfileSerializer class TeamsSerializer(serializers.ModelSerializer): - users = UserSerializer(read_only=True, many=True) - created_by = UserSerializer() + users = ProfileSerializer(read_only=True, many=True) + created_by = ProfileSerializer() class Meta: model = Teams @@ -24,6 +24,7 @@ class TeamCreateSerializer(serializers.ModelSerializer): def __init__(self, *args, **kwargs): request_obj = kwargs.pop("request_obj", None) super(TeamCreateSerializer, self).__init__(*args, **kwargs) + self.org = request_obj.org self.fields["name"].required = True @@ -31,7 +32,7 @@ def validate_name(self, name): if self.instance: if ( Teams.objects.filter( - name__iexact=name, + name__iexact=name, org=self.org ) .exclude(id=self.instance.id) .exists() @@ -50,4 +51,5 @@ class Meta: "created_on", "created_by", "created_on_arrow", + "org" ) diff --git a/teams/swagger_params.py b/teams/swagger_params.py index 05bc3a6..9b399b2 100644 --- a/teams/swagger_params.py +++ b/teams/swagger_params.py @@ -1,12 +1,21 @@ from drf_yasg import openapi +organization_params_in_header = openapi.Parameter( + 'org', openapi.IN_HEADER, required=True, type=openapi.TYPE_INTEGER) + +organization_params = [ + organization_params_in_header, +] + teams_list_get_params = [ + organization_params_in_header, openapi.Parameter("team_name", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("created_by", openapi.IN_QUERY, type=openapi.TYPE_STRING), openapi.Parameter("assigned_users", openapi.IN_QUERY, type=openapi.TYPE_STRING), ] teams_create_post_params = [ + organization_params_in_header, openapi.Parameter( "name", openapi.IN_QUERY, required=True, type=openapi.TYPE_STRING ), diff --git a/teams/views.py b/teams/views.py index 297602c..fe7aa9d 100644 --- a/teams/views.py +++ b/teams/views.py @@ -5,9 +5,9 @@ from teams.models import Teams from teams.tasks import update_team_users, remove_users from teams.serializer import TeamsSerializer, TeamCreateSerializer -from common.models import User +from common.models import Profile from common.custom_auth import JSONWebTokenAuthentication -from common.serializer import UserSerializer +from common.serializer import ProfileSerializer from rest_framework import status from rest_framework.views import APIView @@ -29,7 +29,7 @@ def get_context_data(self, **kwargs): if len(self.request.data) == 0 else self.request.data ) - queryset = self.model.objects.all() + queryset = self.model.objects.filter(org=self.request.org) request_post = params if request_post: @@ -69,17 +69,17 @@ def get_context_data(self, **kwargs): ) context["teams"] = teams - users = User.objects.filter( - is_active=True, + users = Profile.objects.filter( + is_active=True, org=self.request.org ).order_by("id") - context["users"] = UserSerializer(users, many=True).data + context["users"] = ProfileSerializer(users, many=True).data return context @swagger_auto_schema( tags=["Teams"], manual_parameters=swagger_params.teams_list_get_params ) def get(self, request, *args, **kwargs): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: return Response( { "error": True, @@ -94,7 +94,7 @@ def get(self, request, *args, **kwargs): tags=["Teams"], manual_parameters=swagger_params.teams_create_post_params ) def post(self, request, *args, **kwargs): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: return Response( { "error": True, @@ -111,12 +111,12 @@ def post(self, request, *args, **kwargs): serializer = TeamCreateSerializer(data=params, request_obj=request) data = {} if serializer.is_valid(): - team_obj = serializer.save(created_by=request.user) + team_obj = serializer.save(created_by=request.profile, org=request.org) if params.get("assign_users"): assinged_to_users_ids = json.loads(params.get("assign_users")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter(id=user_id, org=request.org) if user.exists(): team_obj.users.add(user_id) else: @@ -142,13 +142,13 @@ class TeamsDetailView(APIView): permission_classes = (IsAuthenticated,) def get_object(self, pk): - return self.model.objects.get(pk=pk) + return self.model.objects.get(pk=pk, org=self.request.org) @swagger_auto_schema( - tags=["Teams"], + tags=["Teams"], manual_parameters=swagger_params.organization_params ) def get(self, request, pk, **kwargs): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: return Response( { "error": True, @@ -159,8 +159,8 @@ def get(self, request, pk, **kwargs): self.team_obj = self.get_object(pk) context = {} context["team"] = TeamsSerializer(self.team_obj).data - context["users"] = UserSerializer( - User.objects.filter(is_active=True).order_by("email"), + context["users"] = ProfileSerializer( + Profile.objects.filter(is_active=True, org=request.org).order_by("user__email"), many=True, ).data return Response(context) @@ -169,7 +169,7 @@ def get(self, request, pk, **kwargs): tags=["Teams"], manual_parameters=swagger_params.teams_create_post_params ) def put(self, request, pk, *args, **kwargs): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: return Response( { "error": True, @@ -196,7 +196,7 @@ def put(self, request, pk, *args, **kwargs): if params.get("assign_users"): assinged_to_users_ids = json.loads(params.get("assign_users")) for user_id in assinged_to_users_ids: - user = User.objects.filter(id=user_id) + user = Profile.objects.filter(id=user_id, org=request.org) if user.exists(): team_obj.users.add(user_id) else: @@ -223,10 +223,10 @@ def put(self, request, pk, *args, **kwargs): ) @swagger_auto_schema( - tags=["Teams"], + tags=["Teams"], manual_parameters=swagger_params.organization_params ) def delete(self, request, pk, **kwargs): - if self.request.user.role != "ADMIN" and not self.request.user.is_superuser: + if self.request.profile.role != "ADMIN" and not self.request.profile.is_admin: return Response( { "error": True, diff --git a/templates/registration/password_reset_email.html b/templates/registration/password_reset_email.html index 763f954..ab7696c 100644 --- a/templates/registration/password_reset_email.html +++ b/templates/registration/password_reset_email.html @@ -1,23 +1,23 @@ {% extends 'root_email_template_new.html' %} {% block content %} {% block heading %} -Password reset +Set a New Password {% endblock heading %} {% block content_body %} -We have received password reset request for your account. Click below link to reset your password +Click the below link to Set a New Password to login and enjoy the features of Bottle CRM. {% endblock content_body %} {% block button_link %}

- {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} + {{complete_url}}

{% endblock button_link %} {% block extra_content %}

This link will be valid for 3 days. - Please ignore this email, if you did not request a password reset. + Please ignore this email, if you did not request to set New Password.

{% endblock extra_content %} {% endblock %} \ No newline at end of file