Skip to content

Commit

Permalink
Feature/history rooms endpoint (#294)
Browse files Browse the repository at this point in the history
* feat: add history app module for history related code

* feat: remove unused files

* feat: add module to settings

* feat: add History Room endpoint to be used instead of the contacts endpoint
  • Loading branch information
helllllllder authored Oct 19, 2023
1 parent 5ed60a5 commit e3d4c1a
Show file tree
Hide file tree
Showing 17 changed files with 372 additions and 1 deletion.
11 changes: 10 additions & 1 deletion chats/apps/api/v1/contacts/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@


class ContactSerializer(serializers.ModelSerializer):

room = serializers.SerializerMethodField()

class Meta:
Expand Down Expand Up @@ -87,3 +86,13 @@ class ContactWSSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = "__all__"


class ContactSimpleSerializer(ContactSerializer):
class Meta:
model = Contact
fields = [
"uuid",
"name",
"external_id",
]
2 changes: 2 additions & 0 deletions chats/apps/api/v1/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
SectorViewset,
)
from chats.apps.api.v1.users.viewsets import ProfileViewset
from chats.apps.history.views import HistoryRoomViewset


class Router(routers.SimpleRouter):
Expand Down Expand Up @@ -95,6 +96,7 @@ def get_lookup_regex(self, viewset, lookup_prefix=""):
)
router.register("media", MessageMediaViewset)
router.register("contact", ContactViewset)
router.register("history/rooms", HistoryRoomViewset, basename="history_room")
router.register("sector", SectorViewset)
router.register("tag", SectorTagsViewset)
router.register("project", ProjectViewset)
Expand Down
6 changes: 6 additions & 0 deletions chats/apps/api/v1/sectors/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ class Meta:
]


class TagSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = SectorTag
fields = ["uuid", "name"]


class SectorAgentsSerializer(serializers.ModelSerializer):
class Meta:
model = User
Expand Down
Empty file added chats/apps/history/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions chats/apps/history/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class EventDrivenConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "chats.apps.history"
Empty file.
82 changes: 82 additions & 0 deletions chats/apps/history/filters/rooms_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from django_filters import rest_framework as filters
from rest_framework import exceptions

from chats.apps.rooms.models import Room


class HistoryRoomFilter(filters.FilterSet):
class Meta:
model = Room
fields = []

contact = filters.CharFilter(
field_name="contact",
required=False,
method="filter_contact",
help_text=_("Contact's External ID"),
)

project = filters.CharFilter(
field_name="project",
required=True,
method="filter_project",
help_text=_("Projects's UUID"),
)

sector = filters.CharFilter(
field_name="sector",
required=False,
method="filter_sector",
help_text=_("Sector's UUID"),
)

tag = filters.CharFilter(
required=False,
method="filter_tags",
help_text=_("Room Tags"),
)
created_on = filters.DateFromToRangeFilter(
field_name="created_on",
required=False,
help_text=_("Room created on"),
)
ended_at = filters.DateFromToRangeFilter(
field_name="ended_at",
required=False,
help_text=_("Room ended at"),
)

def filter_project(self, queryset, name, value):
qs = queryset
user = self.request.user
try:
user_permission = user.project_permissions.get(project=value)
except ObjectDoesNotExist:
raise exceptions.APIException(
detail="Access denied! Make sure you have the right permission to access this project"
)

queue_ids = user_permission.queue_ids

contacts_blocklist = user_permission.project.history_contacts_blocklist
if contacts_blocklist:
qs = qs.exclude(contact__external_id__in=contacts_blocklist)

return qs.filter(
Q(queue__in=queue_ids) | Q(user=user, queue__sector__project=value),
is_active=False,
ended_at__isnull=False,
)

def filter_sector(self, queryset, name, value):
return queryset.filter(queue__sector__uuid=value)

def filter_tags(self, queryset, name, value):
values = value.split(",")
return queryset.filter(tags__name__in=values)

def filter_contact(self, queryset, name, value):
return queryset.filter(contact__external_id=value)
Empty file.
Empty file.
51 changes: 51 additions & 0 deletions chats/apps/history/serializers/rooms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from rest_framework import serializers

from chats.apps.api.v1.accounts.serializers import UserNameSerializer
from chats.apps.api.v1.contacts.serializers import ContactSimpleSerializer
from chats.apps.api.v1.sectors.serializers import TagSimpleSerializer
from chats.apps.rooms.models import Room


class RoomHistorySerializer(serializers.ModelSerializer):
user = UserNameSerializer(many=False, read_only=True)
contact = ContactSimpleSerializer(many=False, read_only=True)
tags = TagSimpleSerializer(many=True, read_only=True)

class Meta:
model = Room
fields = [
"uuid",
"created_on",
"ended_at",
"user",
"contact",
"tags",
]


class RoomBasicSerializer(serializers.ModelSerializer):
class Meta:
model = Room
fields = [
"uuid",
"ended_at",
]


class RoomDetailSerializer(serializers.ModelSerializer):
user = UserNameSerializer(many=False, read_only=True)
contact = ContactSimpleSerializer(many=False, read_only=True)
tags = TagSimpleSerializer(many=True, read_only=True)

class Meta:
model = Room
fields = [
"uuid",
"custom_fields",
"urn",
"created_on",
"ended_at",
"user",
"contact",
"tags",
]
Empty file.
120 changes: 120 additions & 0 deletions chats/apps/history/tests/test_viewsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from django.urls import reverse
from django.utils import timezone
from rest_framework import status

from chats.core.tests.test_base import BaseAPIChatsTestCase


class TestHistoryRoomViewsets(BaseAPIChatsTestCase):
def _list_request(self, token, data):
url = reverse("history_room-list")
client = self.client
client.credentials(HTTP_AUTHORIZATION="Token " + token.key)
response = client.get(url, format="json", data=data)
results = response.json().get("results")
return response, results

def test_admin_list_within_its_project(self):
payload = {"project": str(self.project.uuid)}
self.deactivate_rooms()
response = self._list_request(token=self.admin_token, data=payload)[0]
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json().get("count"), self.count_project_1_contact)

def test_manager_list_within_its_sectors(self):
payload = {"project": str(self.project.uuid)}
self.deactivate_rooms()
response = self._list_request(token=self.manager_token, data=payload)[0]
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json().get("count"), 3)

def test_agent_list_within_its_rooms(self):
payload = {"project": str(self.project.uuid)}
self.deactivate_rooms()
response, _ = self._list_request(token=self.agent_token, data=payload)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json().get("count"), 2)

def test_basic_list(self):
payload = {
"project": str(self.project.uuid),
"basic": True,
"contact": self.contact.external_id,
}
self.deactivate_rooms()
response = self._list_request(token=self.admin_token, data=payload)[0]
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json().get("count"), 1)
self.assertEqual(len(response.json().get("results")[0]), 2)

def test_admin_list_with_blocked_contacts(self):
self.project.add_contact_to_history_blocklist(self.contact_2.external_id)

payload = {"project": str(self.project.uuid)}
self.deactivate_rooms()
response = self._list_request(token=self.admin_token, data=payload)[0]

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json().get("count"), self.count_project_1_contact - 1)

def test_retrieve_room_ok(self):
"""
Ensure we can retrieve a contact
"""
client = self.client
client.credentials(HTTP_AUTHORIZATION="Token " + self.admin_token.key)
self.contact.rooms.update(is_active=False, ended_at=timezone.now())
url = (
reverse("history_room-detail", kwargs={"pk": str(self.room_1.pk)})
+ f"?project={str(self.project.uuid)}"
)
response = client.get(
url,
format="json",
)

self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_retrieve_contact_no_closed_rooms(self):
"""
Ensure we can retrieve a contact
"""
client = self.client
client.credentials(HTTP_AUTHORIZATION="Token " + self.admin_token.key)
url = (
reverse("history_room-detail", kwargs={"pk": str(self.room_1.pk)})
+ f"?project={str(self.project.uuid)}"
)
response = self.client.get(
url,
format="json",
)

self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_retrieve_contact_unauthorized(self):
"""
Ensure we can retrieve a contact
"""
client = self.client
client.credentials(HTTP_AUTHORIZATION="Token " + self.agent_token.key)
self.contact_2.rooms.update(is_active=False, ended_at=timezone.now())
url = (
reverse("history_room-detail", kwargs={"pk": str(self.room_2.pk)})
+ f"?project={str(self.project.uuid)}"
)
response = client.get(url, format="json")

self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_handling_not_allowed_http_methods(self):
url = reverse("history_room-detail", kwargs={"pk": 1})
client = self.client
client.credentials(HTTP_AUTHORIZATION="Token " + self.admin_token.key)
post = client.post(url, format="json")
put = client.put(url, format="json")
delete = client.delete(url, format="json")
actions = [post, put, delete]

for action in actions:
self.assertEqual(action.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
1 change: 1 addition & 0 deletions chats/apps/history/views/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .history_rooms import HistoryRoomViewset # NoQA
48 changes: 48 additions & 0 deletions chats/apps/history/views/history_rooms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ReadOnlyModelViewSet

from chats.apps.rooms.models import Room

from ..filters.rooms_filter import HistoryRoomFilter
from ..serializers.rooms import (
RoomBasicSerializer,
RoomDetailSerializer,
RoomHistorySerializer,
)
from .permissions import CanRetrieveRoomHistory


class HistoryRoomViewset(ReadOnlyModelViewSet):
queryset = Room.objects.all()
serializer_class = RoomHistorySerializer
filter_backends = [
DjangoFilterBackend,
SearchFilter,
OrderingFilter,
]
filterset_class = HistoryRoomFilter
permission_classes = [IsAuthenticated]
search_fields = [
"contact__name",
"urn",
"user__first_name",
"user__last_name",
"user__email",
]
ordering = ["-ended_at"]

def get_permissions(self):
permission_classes = self.permission_classes

if self.action == "retrieve":
permission_classes = (IsAuthenticated, CanRetrieveRoomHistory)
return [permission() for permission in permission_classes]

def get_serializer_class(self):
if self.request.GET.get("basic", None):
return RoomBasicSerializer
if self.action == "retrieve":
return RoomDetailSerializer
return super().get_serializer_class()
23 changes: 23 additions & 0 deletions chats/apps/history/views/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.contrib.auth.models import AnonymousUser
from rest_framework import permissions

from chats.apps.projects.models import ProjectPermission
from chats.core.permissions import GetPermission


class CanRetrieveRoomHistory(permissions.BasePermission):
def has_permission(self, request, view):
if view.action in ["list", "create"]:
permission = GetPermission(request).permission
return permission.is_admin

return super().has_permission(request, view)

def has_object_permission(self, request, view, obj) -> bool:
if isinstance(request.user, AnonymousUser):
return False
try:
perm = obj.get_permission(request.user)
except ProjectPermission.DoesNotExist:
return False
return perm.is_admin
Loading

0 comments on commit e3d4c1a

Please sign in to comment.