From ade7e86ae60995fe0dec8b7bbf6040c1baff0582 Mon Sep 17 00:00:00 2001 From: Paulo Bernardo Date: Thu, 20 Oct 2022 17:05:29 -0300 Subject: [PATCH 01/10] fix: set channel stats start date to 2000-01-01 (#165) --- weni/channel_stats/serializers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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] From 37b2e4bfde6fbc3459d9a6a57c8cd0818416c629 Mon Sep 17 00:00:00 2001 From: Paulo Bernardo Date: Thu, 20 Oct 2022 17:15:22 -0300 Subject: [PATCH 02/10] Bump to 1.0.29 (#167) --- CHANGELOG.md | 3 +++ pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e036cf0..519bb8347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## [Unreleased] +## [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..cdc7210cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "weni-rp-apps" -version = "1.0.28" +version = "1.0.29" description = "Weni apps for Rapidpro Platform" authors = ["jcbalmeida"] license = "AGPL-3.0" From 1283af7201b2d89718eb6a41a99ba67bb4379421 Mon Sep 17 00:00:00 2001 From: Paulo Abreu Date: Mon, 7 Nov 2022 17:20:27 -0300 Subject: [PATCH 03/10] Feature/suspend flag or block org endpoints (#174) * feat: Add endpoint to suspend a org * feat: Add flag Org andpoint --- weni/orgs_api/__init__.py | 1 + weni/orgs_api/apps.py | 11 +++ weni/orgs_api/serializers.py | 34 +++++++++ weni/orgs_api/tests.py | 136 +++++++++++++++++++++++++++++++++++ weni/orgs_api/urls.py | 7 ++ weni/orgs_api/views.py | 37 ++++++++++ 6 files changed, 226 insertions(+) create mode 100644 weni/orgs_api/__init__.py create mode 100644 weni/orgs_api/apps.py create mode 100644 weni/orgs_api/serializers.py create mode 100644 weni/orgs_api/tests.py create mode 100644 weni/orgs_api/urls.py create mode 100644 weni/orgs_api/views.py 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..cf4084c9f --- /dev/null +++ b/weni/orgs_api/tests.py @@ -0,0 +1,136 @@ +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.config, {}) + + org = Org.objects.get(name="Tembinha") + + self.assertEqual(org.config.get("is_suspended"), is_suspended) + + def test_block_org_with_config(self): + org = Org.objects.get(name="Tembinha") + org.config["another_key"] = "test" + org.save() + + is_suspended = bool(randint(0, 1)) + + self.request_patch(uuid=org.uuid, data={"is_suspended": is_suspended}) + + org = Org.objects.get(name="Tembinha") + self.assertEqual(org.config.get("is_suspended"), is_suspended) + self.assertEqual(org.config.get("another_key"), "test") + + 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..61e1fb5b9 --- /dev/null +++ b/weni/orgs_api/views.py @@ -0,0 +1,37 @@ +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.config["is_suspended"] = bool(request.data.get("is_suspended")) + org.save() + + return JsonResponse(dict(is_suspended=org.config.get("is_suspended", False))) + + @action(detail=True, methods=["POST"]) + def suspend_flag(self, request, uuid=None): + org = get_object_or_404(Org, uuid=uuid) + + 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) From ed2d76632df35737721088e72be526f260d8878e Mon Sep 17 00:00:00 2001 From: Paulo Abreu Date: Mon, 7 Nov 2022 18:08:04 -0300 Subject: [PATCH 04/10] Update CHANGELOG.md (#175) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 519bb8347..59538c5b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## [Unreleased] +## [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 From 27d7b709bbab689ce9a28eafcd079e02f34983e4 Mon Sep 17 00:00:00 2001 From: Paulo Abreu Date: Tue, 8 Nov 2022 12:05:02 -0300 Subject: [PATCH 05/10] fix: Add option to delete suspend flag to a org (#176) --- weni/orgs_api/views.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/weni/orgs_api/views.py b/weni/orgs_api/views.py index 61e1fb5b9..ad160b737 100644 --- a/weni/orgs_api/views.py +++ b/weni/orgs_api/views.py @@ -22,10 +22,21 @@ def is_suspended(self, request, uuid=None): return JsonResponse(dict(is_suspended=org.config.get("is_suspended", False))) - @action(detail=True, methods=["POST"]) + @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) From 482fc4d057ca8ad201de2d3f229e6ef5739b504d Mon Sep 17 00:00:00 2001 From: Paulo Abreu Date: Tue, 8 Nov 2022 15:57:13 -0300 Subject: [PATCH 06/10] fix: move is_suspended from config to var (#177) --- weni/orgs_api/tests.py | 17 ++--------------- weni/orgs_api/views.py | 4 ++-- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/weni/orgs_api/tests.py b/weni/orgs_api/tests.py index cf4084c9f..07275ca39 100644 --- a/weni/orgs_api/tests.py +++ b/weni/orgs_api/tests.py @@ -63,24 +63,11 @@ def test_block_org_without_config(self): self.request_patch(uuid=org.uuid, data={"is_suspended": is_suspended}) - self.assertEqual(org.config, {}) + self.assertEqual(org.is_suspended, False) org = Org.objects.get(name="Tembinha") - self.assertEqual(org.config.get("is_suspended"), is_suspended) - - def test_block_org_with_config(self): - org = Org.objects.get(name="Tembinha") - org.config["another_key"] = "test" - org.save() - - is_suspended = bool(randint(0, 1)) - - self.request_patch(uuid=org.uuid, data={"is_suspended": is_suspended}) - - org = Org.objects.get(name="Tembinha") - self.assertEqual(org.config.get("is_suspended"), is_suspended) - self.assertEqual(org.config.get("another_key"), "test") + self.assertEqual(org.is_suspended, is_suspended) def get_url_namespace(self): return "org-is-suspended" diff --git a/weni/orgs_api/views.py b/weni/orgs_api/views.py index ad160b737..9aa55995a 100644 --- a/weni/orgs_api/views.py +++ b/weni/orgs_api/views.py @@ -17,10 +17,10 @@ class OrgViewSet(GenericViewSet): def is_suspended(self, request, uuid=None): org = get_object_or_404(Org, uuid=uuid) - org.config["is_suspended"] = bool(request.data.get("is_suspended")) + org.is_suspended = bool(request.data.get("is_suspended")) org.save() - return JsonResponse(dict(is_suspended=org.config.get("is_suspended", False))) + return JsonResponse(dict(is_suspended=org.is_suspended)) @action(detail=True, methods=["POST", "DELETE"]) def suspend_flag(self, request, uuid=None): From 3429fc720b222f3a804cb9377f85456c9b82b485 Mon Sep 17 00:00:00 2001 From: Paulo Abreu Date: Tue, 8 Nov 2022 16:14:28 -0300 Subject: [PATCH 07/10] update: 1.0.31 (#178) --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59538c5b1..ea9c46680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## [Unreleased] +## [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 From f88d3699e60d657723a514cb1dbb535cbb686e82 Mon Sep 17 00:00:00 2001 From: Paulo Abreu Date: Thu, 17 Nov 2022 11:38:37 -0300 Subject: [PATCH 08/10] fix: pyproject.toml version (#180) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cdc7210cb..e14e873c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "weni-rp-apps" -version = "1.0.29" +version = "1.0.31" description = "Weni apps for Rapidpro Platform" authors = ["jcbalmeida"] license = "AGPL-3.0" From ebafa6f34841a0253d57b6ba9f6be91b8b15566f Mon Sep 17 00:00:00 2001 From: Paulo Bernardo Date: Thu, 17 Nov 2022 11:43:52 -0300 Subject: [PATCH 09/10] feat: use internal_tickter false on TemplateOrg creation (#171) --- weni/internal/orgs/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 7beec69061a18d7c7a7af2d3c2a81a43161d2f06 Mon Sep 17 00:00:00 2001 From: Paulo Bernardo Date: Thu, 17 Nov 2022 11:51:12 -0300 Subject: [PATCH 10/10] Update 1.0.32 (#181) --- CHANGELOG.md | 3 +++ pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea9c46680..755fca6ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## [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 diff --git a/pyproject.toml b/pyproject.toml index e14e873c5..9ed2b3f73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "weni-rp-apps" -version = "1.0.31" +version = "1.0.32" description = "Weni apps for Rapidpro Platform" authors = ["jcbalmeida"] license = "AGPL-3.0"