From 9798849aab1349f40a86049e77e7926231062bd9 Mon Sep 17 00:00:00 2001 From: Klaus-Uwe Mitterer Date: Mon, 13 Apr 2020 12:08:59 +0200 Subject: [PATCH] Implement base SMS provider PlaySMS integration --- core/classes/sms.py | 33 ++++++++++++++++++++++++ core/helpers/__init__.py | 0 core/helpers/files.py | 5 ++++ core/models.py | 4 --- core/models/__init__.py | 1 + core/models/files.py | 13 ++++++++++ core/modules/sms.py | 39 ++++++++++++++++++++++++++++ core/modules/urls.py | 18 +++++++++++++ core/urls.py | 19 ++------------ dbsettings | 2 +- demomodule/urls.py | 2 +- module_dependencies.md | 4 +++ playsms/__init__.py | 0 playsms/apps.py | 5 ++++ playsms/models.py | 55 ++++++++++++++++++++++++++++++++++++++++ playsms/sms.py | 0 playsms/urls.py | 2 ++ playsms/views.py | 3 +++ requirements.txt | 1 + 19 files changed, 183 insertions(+), 23 deletions(-) create mode 100644 core/classes/sms.py create mode 100644 core/helpers/__init__.py create mode 100644 core/helpers/files.py delete mode 100644 core/models.py create mode 100644 core/models/__init__.py create mode 100644 core/models/files.py create mode 100644 core/modules/sms.py create mode 100644 core/modules/urls.py create mode 100644 module_dependencies.md create mode 100644 playsms/__init__.py create mode 100644 playsms/apps.py create mode 100644 playsms/models.py create mode 100644 playsms/sms.py create mode 100644 playsms/urls.py create mode 100644 playsms/views.py diff --git a/core/classes/sms.py b/core/classes/sms.py new file mode 100644 index 0000000..eafc872 --- /dev/null +++ b/core/classes/sms.py @@ -0,0 +1,33 @@ +from typing import Union + +class SMSNotSent(Exception): + '''SMS providers must raise SMSNotSent if an SMS was reported as not sent by the gateway.''' + +class BaseSMSProvider: + '''SMS providers must be subclasses of BaseSMSProvider and implement at least is_active and sendSMS().''' + + @property + def get_name(self): + return "Base SMS Provider" + + @property + def get_logo(self): + return "" + + @property + def is_active(self): + '''Returns True if the provider is properly configured and ready to use.''' + raise NotImplementedError(f"{type(self)} does not implement is_active!") + + @property + def get_edit_url(self): + return + + def sendSMS(self, recipients: Union[str, list], message: str): + '''Send an SMS message to one or more recipients + + :param recipients: Recipient phone number as a string, or a list of multiple phone number strings + :param message: Message to be sent as a string + ''' + + raise NotImplementedError(f"{type(self)} does not implement sendSMS()!") \ No newline at end of file diff --git a/core/helpers/__init__.py b/core/helpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/helpers/files.py b/core/helpers/files.py new file mode 100644 index 0000000..16d9137 --- /dev/null +++ b/core/helpers/files.py @@ -0,0 +1,5 @@ +import uuid +import os.path + +def generate_storage_filename(): + return uuid.uuid4() \ No newline at end of file diff --git a/core/models.py b/core/models.py deleted file mode 100644 index b096caa..0000000 --- a/core/models.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.db import models - -# Create your models here. - diff --git a/core/models/__init__.py b/core/models/__init__.py new file mode 100644 index 0000000..f7552b8 --- /dev/null +++ b/core/models/__init__.py @@ -0,0 +1 @@ +from core.models.files import * \ No newline at end of file diff --git a/core/models/files.py b/core/models/files.py new file mode 100644 index 0000000..03fab66 --- /dev/null +++ b/core/models/files.py @@ -0,0 +1,13 @@ +from django.db.models import Model, CharField, ImageField, FileField + +from core.helpers.files import generate_storage_filename + +# Create your models here. + +class ImageFile(Model): + filename = CharField(max_length=255) + file = ImageField(upload_to=generate_storage_filename) + +class File(Model): + filename = CharField(max_length=255) + file = FileField(upload_to=generate_storage_filename) \ No newline at end of file diff --git a/core/modules/sms.py b/core/modules/sms.py new file mode 100644 index 0000000..b9ae1dd --- /dev/null +++ b/core/modules/sms.py @@ -0,0 +1,39 @@ +import importlib + +from django.conf import settings + +from core.classes.navigation import NavItem, NavSection, Navigation + +from dbsettings import getValue + +providers = [] + +modules_available = [] + +for module in settings.EXPEPHALON_MODULES: + try: + mos = importlib.import_module(f"{module}.sms") + for provider in mos.SMSPROVIDERS: + providers.append(provider) + if mos.CREATE: + modules_available = mos.CREATE + except (AttributeError, ModuleNotFoundError): + continue + +def get_sms_provider_by_name(name): + for provider in providers: + if provider.name == name: + return provider + +def get_default_sms_provider_name(): + return getValue("core.sms.default", False) + +def get_default_sms_provider(): + name = get_default_sms_provider_name() + if name: + for provider in providers: + if provider.name == name and provider.is_active: + return provider + for provider in providers: + if provider.is_active: + return provider \ No newline at end of file diff --git a/core/modules/urls.py b/core/modules/urls.py new file mode 100644 index 0000000..78dc60f --- /dev/null +++ b/core/modules/urls.py @@ -0,0 +1,18 @@ +import importlib + +from django.conf import settings +from django.urls import path + +URLPATTERNS = [] + +for module in settings.EXPEPHALON_MODULES: + try: + mou = importlib.import_module(f"{module}.urls") + for url, action, name in mou.ADMIN_URLS: + URLPATTERNS.append(path(f'admin/modules/{module}/{url}', action, name=f"{module}_{name}")) + except (AttributeError, ModuleNotFoundError): + pass + +if "dbsettings" in settings.INSTALLED_APPS: + from core.views import DBSettingsListView + URLPATTERNS.append(path("admin/dbsettings/", DBSettingsListView.as_view(), name="dbsettings")) \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index f041f58..b7c5ec5 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,25 +1,10 @@ - -from django.contrib import admin from django.urls import path, include -from django.conf import settings from core.views import DashboardView -from core.navigation import Navigation +from core.modules.urls import URLPATTERNS import importlib urlpatterns = [ path('admin/', DashboardView.as_view(), name="backend"), -] - -for module in settings.EXPEPHALON_MODULES: - try: - mou = importlib.import_module(f"{module}.urls") - for url, action, name in mou.ADMIN_URLS: - urlpatterns.append(path(f'admin/modules/{module}/{url}', action, name=f"{module}_{name}")) - except (AttributeError, ModuleNotFoundError): - pass - -if "dbsettings" in settings.INSTALLED_APPS: - from core.views import DBSettingsListView - urlpatterns.append(path("admin/dbsettings/", DBSettingsListView.as_view(), name="dbsettings")) \ No newline at end of file +] + URLPATTERNS diff --git a/dbsettings b/dbsettings index a1bf7c0..06e4fcd 160000 --- a/dbsettings +++ b/dbsettings @@ -1 +1 @@ -Subproject commit a1bf7c0dbd874e1ebb941b545eb8e0fd00dd776a +Subproject commit 06e4fcd801e3961de05f98bc5bf88a5a6a334408 diff --git a/demomodule/urls.py b/demomodule/urls.py index dd6c513..04a48ed 100644 --- a/demomodule/urls.py +++ b/demomodule/urls.py @@ -1,5 +1,5 @@ from core.views import DashboardView ADMIN_URLS = [ - ("", DashboardView.as_view(), "backend"), + ("", DashboardView.as_view(), "demomodule_backend"), ] \ No newline at end of file diff --git a/module_dependencies.md b/module_dependencies.md new file mode 100644 index 0000000..5bc96b3 --- /dev/null +++ b/module_dependencies.md @@ -0,0 +1,4 @@ +### playsms + +* core + diff --git a/playsms/__init__.py b/playsms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/playsms/apps.py b/playsms/apps.py new file mode 100644 index 0000000..6141e81 --- /dev/null +++ b/playsms/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class PlaysmsConfig(AppConfig): + name = 'playsms' diff --git a/playsms/models.py b/playsms/models.py new file mode 100644 index 0000000..c4d7d45 --- /dev/null +++ b/playsms/models.py @@ -0,0 +1,55 @@ +from django.db.models import Model, CharField, BooleanField + +from core.classes.sms import BaseSMSProvider, SMSNotSent + +from urllib.parse import quote_plus +from urllib.request import urlopen +from typing import Union + + +import json + +# Create your models here. + +class PlaySMSServer(Model, BaseSMSProvider): + name = CharField(max_length=255) + logo = CharField(max_length=255) + https = BooleanField(default=True) + host = CharField(max_length=255) + username = CharField(max_length=255) + token = CharField(max_length=255) + + @property + def is_active(self): + return True + + @staticmethod + def getError(status): + if status["error_message"]: + return status["error_message"] + try: + if status["data"]["error"]: + return status["data"]["error"] + finally: + return + + def sendSMS(self, recipients: Union[str, list], message: str): + '''Send an SMS message to one or more recipients + + :param recipients: Recipient phone number as a string, or a list of multiple phone number strings + :param message: Message to be sent as a string + ''' + + if isinstance(recipients, list): + recipients = ",".join(recipients) + + url = 'http%s://%s/index.php?app=ws&u="%s"&h="%s"&op=pv&to=%s&msg=%s' % ("s" if self.https else "", self.host, self.username, self.token, recipients, quote_plus(message)) + + response = urlopen(url) + + status = json.loads(response.read().decode()) + + error = PlaySMSServer.getError(status) + + if error: + raise SMSNotSent(f'An error occurred trying to send the SMS: {error}') \ No newline at end of file diff --git a/playsms/sms.py b/playsms/sms.py new file mode 100644 index 0000000..e69de29 diff --git a/playsms/urls.py b/playsms/urls.py new file mode 100644 index 0000000..ff15a35 --- /dev/null +++ b/playsms/urls.py @@ -0,0 +1,2 @@ +ADMIN_URLS = [ +] \ No newline at end of file diff --git a/playsms/views.py b/playsms/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/playsms/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/requirements.txt b/requirements.txt index a703f2c..9b77834 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ django[argon2] mysqlclient django-storages boto3 +Pillow