diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e036cf0..755fca6ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ ## [Unreleased] +## [1.0.32] - 2022-11-17 +- Feat: Use internal_tickter false on TemplateOrg creation + +## [1.0.31] - 2022-11-08 +- Fix: fix org is_suspend endpoint + +## [1.0.30] - 2022-11-07 +- Feat: A endpoint to add a warning message to user showing org will be suspeded. +- Feat: A endpoint to suspend a org + +## [1.0.29] - 2022-10-20 +- Fix: Set Channel Stats start date to 01/01/2000 + ## [1.0.28] - 2022-10-07 - Update weni-protobuffers to 1.2.18 diff --git a/pyproject.toml b/pyproject.toml index 98e54a27e..9ed2b3f73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "weni-rp-apps" -version = "1.0.28" +version = "1.0.32" description = "Weni apps for Rapidpro Platform" authors = ["jcbalmeida"] license = "AGPL-3.0" diff --git a/weni/channel_stats/serializers.py b/weni/channel_stats/serializers.py index 59e32586b..092c7c5dc 100644 --- a/weni/channel_stats/serializers.py +++ b/weni/channel_stats/serializers.py @@ -1,6 +1,5 @@ -from datetime import timedelta +from datetime import timedelta, datetime -from dateutil.relativedelta import relativedelta from django.db.models import Sum from django.utils import timezone from rest_framework import serializers @@ -29,7 +28,7 @@ def get_daily_count(self, obj): channel = obj end_date = (timezone.now() + timedelta(days=1)).date() - start_date = end_date - relativedelta(months=12) + start_date = datetime(2000, 1, 1).date() message_stats = [] channels = [channel] diff --git a/weni/internal/orgs/serializers.py b/weni/internal/orgs/serializers.py index fdc7f09ab..958a227af 100644 --- a/weni/internal/orgs/serializers.py +++ b/weni/internal/orgs/serializers.py @@ -35,6 +35,6 @@ def create(self, validated_data): org = super().create(validated_data) org.administrators.add(validated_data.get("created_by")) - org.initialize(sample_flows=False) + org.initialize(sample_flows=False, internal_ticketer=False) return org diff --git a/weni/orgs_api/__init__.py b/weni/orgs_api/__init__.py new file mode 100644 index 000000000..e69dc2f61 --- /dev/null +++ b/weni/orgs_api/__init__.py @@ -0,0 +1 @@ +default_app_config = "weni.orgs_api.apps.OrgApiConfig" diff --git a/weni/orgs_api/apps.py b/weni/orgs_api/apps.py new file mode 100644 index 000000000..2ff4c8764 --- /dev/null +++ b/weni/orgs_api/apps.py @@ -0,0 +1,11 @@ +from django.apps import AppConfig + + +class OrgApiConfig(AppConfig): + name = "weni.orgs_api" + + def ready(self): + from .urls import urlpatterns + from ..utils.app_config import update_urlpatterns + + update_urlpatterns(urlpatterns) diff --git a/weni/orgs_api/serializers.py b/weni/orgs_api/serializers.py new file mode 100644 index 000000000..99b960e60 --- /dev/null +++ b/weni/orgs_api/serializers.py @@ -0,0 +1,34 @@ +from datetime import datetime + +from django.contrib.auth import get_user_model + +from rest_framework import serializers + +User = get_user_model() + + +class FlagOrgSerializer(serializers.Serializer): + date_billing_expired = serializers.DateField( + required=True, + format=r"%Y-%m-%d", + input_formats=[ + r"%Y-%m-%d", + ], + ) + date_org_will_suspend = serializers.DateField( + required=True, + format=r"%Y-%m-%d", + input_formats=[ + r"%Y-%m-%d", + ], + ) + + def to_internal_value(self, data): + return { + "date_billing_expired": datetime.strptime(data.get("date_billing_expired"), r"%Y-%m-%d").strftime( + r"%m/%d/%Y" + ), + "date_org_will_suspend": datetime.strptime(data.get("date_org_will_suspend"), r"%Y-%m-%d").strftime( + r"%m/%d/%Y" + ), + } diff --git a/weni/orgs_api/tests.py b/weni/orgs_api/tests.py new file mode 100644 index 000000000..07275ca39 --- /dev/null +++ b/weni/orgs_api/tests.py @@ -0,0 +1,123 @@ +import json +from datetime import datetime +from dateutil.relativedelta import relativedelta +from random import randint +from abc import ABC, abstractmethod + +from django.template.exceptions import TemplateDoesNotExist +from django.contrib.auth.models import Group +from django.contrib.auth.models import User +from django.utils.http import urlencode +from django.urls import reverse + +from temba.orgs.models import Org + +from temba.api.models import APIToken +from temba.tests import TembaTest + + +class TembaRequestMixin(ABC): + def reverse(self, viewname, kwargs=None, query_params=None): + url = reverse(viewname, kwargs=kwargs) + + if query_params: + return "%s?%s" % (url, urlencode(query_params)) + else: + return url + + def request_post(self, uuid, data): + url = reverse(self.get_url_namespace(), kwargs={"uuid": uuid}) + token = APIToken.get_or_create(self.org, self.admin, Group.objects.get(name="Administrators")) + + return self.client.post( + url, HTTP_AUTHORIZATION=f"Token {token.key}", data=json.dumps(data), content_type="application/json" + ) + + def request_patch(self, uuid, data): + url = self.reverse(self.get_url_namespace(), kwargs={"uuid": uuid}) + token = APIToken.get_or_create(self.org, self.admin, Group.objects.get(name="Administrators")) + + return self.client.patch( + url, HTTP_AUTHORIZATION=f"Token {token.key}", data=json.dumps(data), content_type="application/json" + ) + + @abstractmethod + def get_url_namespace(self): + ... + + +class SuspendOrgTest(TembaTest, TembaRequestMixin): + def setUp(self): + User.objects.create_user(username="testuser", password="123", email="test@weni.ai") + + user = User.objects.get(username="testuser") + + Org.objects.create(name="Tembinha", timezone="Africa/Kigali", created_by=user, modified_by=user) + + super().setUp() + + def test_block_org_without_config(self): + org = Org.objects.get(name="Tembinha") + + is_suspended = bool(randint(0, 1)) + + self.request_patch(uuid=org.uuid, data={"is_suspended": is_suspended}) + + self.assertEqual(org.is_suspended, False) + + org = Org.objects.get(name="Tembinha") + + self.assertEqual(org.is_suspended, is_suspended) + + def get_url_namespace(self): + return "org-is-suspended" + + +class FlagOrgTest(TembaTest, TembaRequestMixin): + def setUp(self): + User.objects.create_user(username="testuser", password="123", email="test@weni.ai") + + user = User.objects.get(username="testuser") + + Org.objects.create(name="Tembinha", timezone="Africa/Kigali", created_by=user, modified_by=user) + + super().setUp() + + def test_flag_org(self): + org = Org.objects.get(name="Tembinha") + org.config["another_key"] = "test" + org.save() + + now = datetime.now() + next_month = now + relativedelta(months=+1) + + data = { + "date_billing_expired": now.strftime(r"%Y-%m-%d"), + "date_org_will_suspend": next_month.strftime(r"%Y-%m-%d"), + } + + response = self.request_post(uuid=org.uuid, data=data).json() + + new_now = now.strftime(r"%m/%d/%Y") + new_next_month = next_month.strftime(r"%m/%d/%Y") + + org = Org.objects.get(name="Tembinha") + + self.assertEqual(new_now, response.get("date_billing_expired")) + self.assertEqual(new_next_month, response.get("date_org_will_suspend")) + self.assertEqual(new_now, org.config.get("date_billing_expired")) + self.assertEqual(new_next_month, org.config.get("date_org_will_suspend")) + + def test_flag_org_invalid_data(self): + org = Org.objects.get(name="Tembinha") + + data = { + "date_billing_expired": "10-11-2022", + "date_org_will_suspend": "10-12-2022", + } + + with self.assertRaises(TemplateDoesNotExist): + self.request_post(uuid=org.uuid, data=data).json() + + def get_url_namespace(self): + return "org-suspend-flag" diff --git a/weni/orgs_api/urls.py b/weni/orgs_api/urls.py new file mode 100644 index 000000000..ce21c12aa --- /dev/null +++ b/weni/orgs_api/urls.py @@ -0,0 +1,7 @@ +from rest_framework import routers +from .views import OrgViewSet + +router = routers.DefaultRouter() +router.register(r"org", OrgViewSet, basename="org") + +urlpatterns = router.urls diff --git a/weni/orgs_api/views.py b/weni/orgs_api/views.py new file mode 100644 index 000000000..9aa55995a --- /dev/null +++ b/weni/orgs_api/views.py @@ -0,0 +1,48 @@ +from django.shortcuts import get_object_or_404 +from django.http import JsonResponse + +from rest_framework.viewsets import GenericViewSet +from rest_framework.decorators import action + +from temba.orgs.models import Org + +from .serializers import FlagOrgSerializer + + +class OrgViewSet(GenericViewSet): + permission = "orgs.org_api" + lookup_field = "uuid" + + @action(detail=True, methods=["PATCH"]) + def is_suspended(self, request, uuid=None): + org = get_object_or_404(Org, uuid=uuid) + + org.is_suspended = bool(request.data.get("is_suspended")) + org.save() + + return JsonResponse(dict(is_suspended=org.is_suspended)) + + @action(detail=True, methods=["POST", "DELETE"]) + def suspend_flag(self, request, uuid=None): + org = get_object_or_404(Org, uuid=uuid) + + if request.method == "DELETE": + if org.config.get("date_billing_expired"): + org.config.pop("date_billing_expired") + + if org.config.get("date_org_will_suspend"): + org.config.pop("date_org_will_suspend") + + org.save() + + return JsonResponse(data=dict(success=True), status=200) + + serializer = FlagOrgSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + + for flag_name, date in serializer.data.items(): + org.config[flag_name] = date + + org.save() + + return JsonResponse(data=serializer.data, status=200)