Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-django-push-notifications for 
openSUSE:Factory checked in at 2023-11-30 22:00:42
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-push-notifications (Old)
 and      
/work/SRC/openSUSE:Factory/.python-django-push-notifications.new.25432 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-django-push-notifications"

Thu Nov 30 22:00:42 2023 rev:5 rq:1129794 version:3.0.2

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-django-push-notifications/python-django-push-notifications.changes
        2023-01-06 17:06:38.652548151 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-django-push-notifications.new.25432/python-django-push-notifications.changes
     2023-11-30 22:01:31.313503262 +0100
@@ -1,0 +2,12 @@
+Wed Nov 29 12:14:57 UTC 2023 - Dirk Müller <dmuel...@suse.com>
+
+- update to 3.0.2:
+  * LegacyConfig.get_apns_use_alternative_port always return None
+  * Add django 4.1, remove master branch
+  * Update topic send_message docs in README
+  * Fix: HexadecimalField accepts non-hex values
+  * Expanded documentation for Web Push
+  * Allow APNS tokens of variable length.
+  * Add WebPush support for Safari
+
+-------------------------------------------------------------------

Old:
----
  django-push-notifications-3.0.0.tar.gz

New:
----
  django-push-notifications-3.0.2.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-django-push-notifications.spec ++++++
--- /var/tmp/diff_new_pack.V1bYh5/_old  2023-11-30 22:01:31.933526103 +0100
+++ /var/tmp/diff_new_pack.V1bYh5/_new  2023-11-30 22:01:31.933526103 +0100
@@ -16,9 +16,9 @@
 #
 
 
-%{?!python_module:%define python_module() python-%{**} python3-%{**}}
+%{?sle15_python_module_pythons}
 Name:           python-django-push-notifications
-Version:        3.0.0
+Version:        3.0.2
 Release:        0
 Summary:        Django package to send push notifications to mobile devices
 License:        MIT

++++++ django-push-notifications-3.0.0.tar.gz -> 
django-push-notifications-3.0.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/.github/workflows/release.yml 
new/django-push-notifications-3.0.2/.github/workflows/release.yml
--- old/django-push-notifications-3.0.0/.github/workflows/release.yml   
2022-02-14 03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/.github/workflows/release.yml   
2023-10-29 17:59:32.000000000 +0100
@@ -8,7 +8,7 @@
 jobs:
   build:
     if: github.repository == 'jazzband/django-push-notifications'
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04
 
     steps:
       - uses: actions/checkout@v2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/.github/workflows/test.yml 
new/django-push-notifications-3.0.2/.github/workflows/test.yml
--- old/django-push-notifications-3.0.0/.github/workflows/test.yml      
2022-02-14 03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/.github/workflows/test.yml      
2023-10-29 17:59:32.000000000 +0100
@@ -5,7 +5,7 @@
 jobs:
   build:
     name: build (Python ${{ matrix.python-version }}, Django ${{ 
matrix.django-version }})
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-20.04
     strategy:
       fail-fast: false
       matrix:
@@ -37,6 +37,7 @@
       run: |
         python -m pip install --upgrade pip
         python -m pip install --upgrade tox tox-gh-actions
+        python -m pip install setuptools-scm==6.4.2
 
     - name: Tox tests
       run: |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/.pre-commit-config.yaml 
new/django-push-notifications-3.0.2/.pre-commit-config.yaml
--- old/django-push-notifications-3.0.0/.pre-commit-config.yaml 2022-02-14 
03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/.pre-commit-config.yaml 2023-10-29 
17:59:32.000000000 +0100
@@ -2,13 +2,13 @@
 # See https://pre-commit.com/hooks.html for more hooks
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.1.0
+    rev: v4.5.0
     hooks:
     -   id: trailing-whitespace
     -   id: end-of-file-fixer
     -   id: check-yaml
     -   id: check-added-large-files
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v2.31.0
+    rev: v3.15.0
     hooks:
     -   id: pyupgrade
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-push-notifications-3.0.0/README.rst 
new/django-push-notifications-3.0.2/README.rst
--- old/django-push-notifications-3.0.0/README.rst      2022-02-14 
03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/README.rst      2023-10-29 
17:59:32.000000000 +0100
@@ -64,7 +64,7 @@
                "WNS_PACKAGE_SECURITY_ID": "[your package security id, e.g: 
'ms-app://e-3-4-6234...']",
                "WNS_SECRET_KEY": "[your app secret key, e.g.: 
'KDiejnLKDUWodsjmewuSZkk']",
                "WP_PRIVATE_KEY": "/path/to/your/private.pem",
-               "WP_CLAIMS": {'sub': "mailto: developm...@example.com"}
+               "WP_CLAIMS": {'sub': "mailto:developm...@example.com"}
        }
 
 .. note::
@@ -122,238 +122,11 @@
 
 **WP settings**
 
-- Install:
-
-.. code-block:: python
-
-       pip install pywebpush
-       pip install py-vapid  (Only for generating key)
-
-- Getting keys:
-
-       - Create file (claim.json) like this:
-
-.. code-block:: bash
-
-       {
-               "sub": "mailto: developm...@example.com",
-               "aud": "https://android.googleapis.com";
-       }
-
-       - Generate public and private keys:
-
-.. code-block:: bash
-
-       vapid --sign claim.json
-
-       No private_key.pem file found.
-       Do you want me to create one for you? (Y/n)Y
-       Do you want me to create one for you? (Y/n)Y
-       Generating private_key.pem
-       Generating public_key.pem
-       Include the following headers in your request:
-
-       Crypto-Key: 
p256ecdsa=BEFuGfKKEFp-kEBMxAIw7ng8HeH_QwnH5_h55ijKD4FRvgdJU1GVlDo8K5U5ak4cMZdQTUJlkA34llWF0xHya70
-
-       Authorization: WebPush 
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL2FuZHJvaWQuZ29vZ2xlYXBpcy5jb20iLCJleHAiOiIxNTA4NDkwODM2Iiwic3ViIjoibWFpbHRvOiBkZXZlbG9wbWVudEBleGFtcGxlLmNvbSJ9.r5CYMs86X3JZ4AEs76pXY5PxsnEhIFJ-0ckbibmFHZuyzfIpf1ZGIJbSI7knA4ufu7Hm8RFfEg5wWN1Yf-dR2A
-
-       - Generate client public key (applicationServerKey)
-
-.. code-block:: bash
-
-       vapid --applicationServerKey
-
-       Application Server Key = 
BEFuGfKKEFp-kEBMxAIw7ng8HeH_QwnH5_h55ijKD4FRvgdJU1GVlDo8K5U5ak4cMZdQTUJlkA34llWF0xHya70
-
-
-- Configure settings:
-
 - ``WP_PRIVATE_KEY``: Absolute path to your private certificate file: 
os.path.join(BASE_DIR, "private_key.pem")
-- ``WP_CLAIMS``: Dictionary with the same sub info like claims file: {'sub': 
"mailto: developm...@example.com"}
+- ``WP_CLAIMS``: Dictionary with default value for the sub, (subject), sent to 
the webpush service, This would be used by the service if they needed to reach 
out to you (the sender). Could be a url or mailto e.g. {'sub': 
"mailto:developm...@example.com"}.
 - ``WP_ERROR_TIMEOUT``: The timeout on WebPush POSTs. (Optional)
-- ``WP_POST_URL``: A dictionary (key per browser supported) with the full url 
that webpush notifications will be POSTed to. (Optional)
-
-
-- Configure client (javascript):
-
-.. code-block:: javascript
-
-       // Utils functions:
-
-       function urlBase64ToUint8Array (base64String) {
-               var padding = '='.repeat((4 - base64String.length % 4) % 4)
-               var base64 = (base64String + padding)
-                       .replace(/\-/g, '+')
-                       .replace(/_/g, '/')
-
-               var rawData = window.atob(base64)
-               var outputArray = new Uint8Array(rawData.length)
-
-               for (var i = 0; i < rawData.length; ++i) {
-                       outputArray[i] = rawData.charCodeAt(i)
-               }
-               return outputArray;
-       }
-
-       function loadVersionBrowser () {
-               if ("userAgentData" in navigator) {
-                       // navigator.userAgentData is not available in
-                       // Firefox and Safari
-                       const uaData = navigator.userAgentData;
-                       // Outputs of navigator.userAgentData.brands[n].brand 
are e.g.
-                       // Chrome: 'Google Chrome'
-                       // Edge: 'Microsoft Edge'
-                       // Opera: 'Opera'
-                       let browsername;
-                       let browserversion;
-                       let chromeVersion = null;
-                       for (var i = 0; i < uaData.brands.length; i++) {
-                               let brand = uaData.brands[i].brand;
-                               browserversion = uaData.brands[i].version;
-                               if 
(brand.match(/opera|chrome|edge|safari|firefox|msie|trident/i) !== null) {
-                                       // If we have a chrome match, save the 
match, but try to find another match
-                                       // E.g. Edge can also produce a false 
Chrome match.
-                                       if (brand.match(/chrome/i) !== null) {
-                                               chromeVersion = browserversion;
-                                       }
-                                       // If this is not a chrome match return 
immediately
-                                       else {
-                                               browsername = 
brand.substr(brand.indexOf(' ')+1);
-                                               return {
-                                                       name: browsername,
-                                                       version: browserversion
-                                               }
-                                       }
-                               }
-                       }
-                       // No non-Chrome match was found. If we have a chrome 
match, return it.
-                       if (chromeVersion !== null) {
-                               return {
-                                       name: "chrome",
-                                       version: chromeVersion
-                               }
-                       }
-               }
-               // If no userAgentData is not present, or if no match via 
userAgentData was found,
-               // try to extract the browser name and version from userAgent
-               const userAgent = navigator.userAgent;
-               var ua = userAgent, tem, M = 
ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
-               if (/trident/i.test(M[1])) {
-                       tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
-                       return {name: 'IE', version: (tem[1] || '')};
-               }
-               if (M[1] === 'Chrome') {
-                       tem = ua.match(/\bOPR\/(\d+)/);
-                       if (tem != null) {
-                               return {name: 'Opera', version: tem[1]};
-                       }
-               }
-               M = M[2] ? [M[1], M[2]] : [navigator.appName, 
navigator.appVersion, '-?'];
-               if ((tem = ua.match(/version\/(\d+)/i)) != null) {
-                       M.splice(1, 1, tem[1]);
-               }
-               return {
-                       name: M[0],
-                       version: M[1]
-               };
-       };
-       var applicationServerKey = 
"BEFuGfKKEFp-kEBMxAIw7ng8HeH_QwnH5_h55ijKD4FRvgdJU1GVlDo8K5U5ak4cMZdQTUJlkA34llWF0xHya70";
-       ....
-
-       // In your ready listener
-       if ('serviceWorker' in navigator) {
-               // The service worker has to store in the root of the app
-               // 
http://stackoverflow.com/questions/29874068/navigator-serviceworker-is-never-ready
-               var browser = loadVersionBrowser();
-               
navigator.serviceWorker.register('navigatorPush.service.js?version=1.0.0').then(function
 (reg) {
-                       reg.pushManager.subscribe({
-                               userVisibleOnly: true,
-                               applicationServerKey: 
urlBase64ToUint8Array(applicationServerKey)
-                       }).then(function (sub) {
-                               var endpointParts = sub.endpoint.split('/');
-                               var registration_id = 
endpointParts[endpointParts.length - 1];
-                               var data = {
-                                       'browser': browser.name.toUpperCase(),
-                                       'p256dh': 
btoa(String.fromCharCode.apply(null, new Uint8Array(sub.getKey('p256dh')))),
-                                       'auth': 
btoa(String.fromCharCode.apply(null, new Uint8Array(sub.getKey('auth')))),
-                                       'name': 'XXXXX',
-                                       'registration_id': registration_id
-                               };
-                               requestPOSTToServer(data);
-                       })
-               }).catch(function (err) {
-                       console.log(':^(', err);
-               });
-
-
-
-
-       // Example navigatorPush.service.js file
-
-       var getTitle = function (title) {
-               if (title === "") {
-                       title = "TITLE DEFAULT";
-               }
-               return title;
-       };
-       var getNotificationOptions = function (message, message_tag) {
-               var options = {
-                       body: message,
-                       icon: '/img/icon_120.png',
-                       tag: message_tag,
-                       vibrate: [200, 100, 200, 100, 200, 100, 200]
-               };
-               return options;
-       };
-
-       self.addEventListener('install', function (event) {
-               self.skipWaiting();
-       });
-
-       self.addEventListener('push', function(event) {
-               try {
-                       // Push is a JSON
-                       var response_json = event.data.json();
-                       var title = response_json.title;
-                       var message = response_json.message;
-                       var message_tag = response_json.tag;
-               } catch (err) {
-                       // Push is a simple text
-                       var title = "";
-                       var message = event.data.text();
-                       var message_tag = "";
-               }
-               self.registration.showNotification(getTitle(title), 
getNotificationOptions(message, message_tag));
-               // Optional: Comunicating with our js application. Send a signal
-               self.clients.matchAll({includeUncontrolled: true, type: 
'window'}).then(function (clients) {
-                       clients.forEach(function (client) {
-                               client.postMessage({
-                                       "data": message_tag,
-                                       "data_title": title,
-                                       "data_body": message});
-                               });
-               });
-       });
-
-       // Optional: Added to that the browser opens when you click on the 
notification push web.
-       self.addEventListener('notificationclick', function(event) {
-               // Android doesn't close the notification when you click it
-               // See http://crbug.com/463146
-               event.notification.close();
-               // Check if there's already a tab open with this URL.
-               // If yes: focus on the tab.
-               // If no: open a tab with the URL.
-               event.waitUntil(clients.matchAll({type: 'window', 
includeUncontrolled: true}).then(function(windowClients) {
-                               for (var i = 0; i < windowClients.length; i++) {
-                                       var client = windowClients[i];
-                                       if ('focus' in client) {
-                                               return client.focus();
-                                       }
-                               }
-                       })
-               );
-       });
 
+For more information about how to configure WebPush, see `docs/WebPush 
<https://github.com/jazzband/django-push-notifications/blob/master/docs/WebPush.rst>`_.
 
 
 Sending messages
@@ -389,6 +162,37 @@
        once constructed the payload exceeds the maximum size, an 
``APNSDataOverflow`` exception will be raised before anything is sent.
        Reference: `Apple Payload Documentation 
<https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW1>`_
 
+Web Push accepts only one variable (``message``), which is passed directly to 
pywebpush. This message can be a simple string, which will be used as your 
notification's body, or it can be contain `any data supported by 
pywebpush<https://github.com/web-push-libs/pywebpush>`.
+
+Simple example:
+
+.. code-block:: python
+
+       from push_notifications.models import WebPushDevice
+
+       device = WebPushDevice.objects.get(registration_id=wp_reg_id)
+
+       device.send_message("You've got mail")
+
+.. note::
+       To customize the notification title using this method, edit the 
``"TITLE DEFAULT"`` string in your ``navigatorPush.service.js`` file.
+
+JSON example:
+
+.. code-block:: python
+
+       import json
+       from push_notifications.models import WebPushDevice
+
+       device = WebPushDevice.objects.get(registration_id=wp_reg_id)
+
+       title = "Message Received"
+       message = "You've got mail"
+       data = json.dumps({"title": title, "message": message})
+
+       device.send_message(data)
+
+
 Sending messages in bulk
 ------------------------
 .. code-block:: python
@@ -463,7 +267,7 @@
        from push_notifications.gcm import send_message
 
         # First param is "None" because no Registration_id is needed, the 
message will be sent to all devices subscribed to the topic.
-        send_message(None, {"body": "Hello members of my_topic!"}, 
to="/topics/my_topic")
+        send_message(None, {"body": "Hello members of my_topic!"}, 
cloud_type="FCM", to="/topics/my_topic")
 
 Reference: `FCM Documentation 
<https://firebase.google.com/docs/cloud-messaging/android/topic-messaging>`_
 
@@ -495,6 +299,7 @@
 Routes can be added one of two ways:
 
 - Routers_ (include all views)
+
 .. _Routers: 
http://www.django-rest-framework.org/tutorial/6-viewsets-and-routers#using-routers
 
 ::
@@ -513,6 +318,7 @@
        )
 
 - Using as_view_ (specify which views to include)
+
 .. _as_view: 
http://www.django-rest-framework.org/tutorial/6-viewsets-and-routers#binding-viewsets-to-urls-explicitly
 
 ::
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-push-notifications-3.0.0/docs/WebPush.rst 
new/django-push-notifications-3.0.2/docs/WebPush.rst
--- old/django-push-notifications-3.0.0/docs/WebPush.rst        1970-01-01 
01:00:00.000000000 +0100
+++ new/django-push-notifications-3.0.2/docs/WebPush.rst        2023-10-29 
17:59:32.000000000 +0100
@@ -0,0 +1,225 @@
+At a high-level, the key steps for implementing web push notifications after 
installing django-push-notifications[WP] are:
+ - Configure the VAPID keys, a private and public key for signing your push 
requests.
+ - Add client side logic to ask the user for permission to send push 
notifications and then sending returned client identifier information to a 
django view to create a WebPushDevice.
+ - Use a service worker to receive messages that have been pushed to the 
device and displaying them as notifications.
+
+These are in addition to the instalation steps for 
django-push-notifications[WP]
+
+Configure the VAPID keys
+------------------------------
+- Install:
+
+.. code-block:: python
+
+       pip install py-vapid  (Only for generating key)
+
+- Generate public and private keys:
+
+.. code-block:: bash
+
+       vapid --gen
+
+       Generating private_key.pem
+       Generating public_key.pem
+
+
+The private key generated is the file to use with the setting 
``WP_PRIVATE_KEY``
+The public key will be used in your client side javascript, but first it must 
be formated as an Application Server Key
+
+- Generate client public key (applicationServerKey)
+
+.. code-block:: bash
+
+       vapid --applicationServerKey
+
+       Application Server Key = <Your Public Key>
+
+
+
+Client Side logic to ask user for permission and subscribe to WebPush
+------------------------------
+The example subscribeUser function is best called in response to a user 
action, such as a button click. Some browsers will deny the request otherwise.
+
+.. code-block:: javascript
+
+       // Utils functions:
+
+       function urlBase64ToUint8Array (base64String) {
+         var padding = '='.repeat((4 - base64String.length % 4) % 4)
+         var base64 = (base64String + padding)
+           .replace(/\-/g, '+')
+           .replace(/_/g, '/')
+
+         var rawData = window.atob(base64)
+         var outputArray = new Uint8Array(rawData.length)
+
+         for (var i = 0; i < rawData.length; ++i) {
+           outputArray[i] = rawData.charCodeAt(i)
+         }
+         return outputArray;
+       }
+
+       var applicationServerKey = '<Your Public Key>';
+
+       function subscribeUser() {
+         if ('Notification' in window && 'serviceWorker' in navigator) {
+           navigator.serviceWorker.ready.then(function (reg) {
+             reg.pushManager
+               .subscribe({
+                 userVisibleOnly: true,
+                 applicationServerKey: urlBase64ToUint8Array(
+                   applicationServerKey
+                 ),
+               })
+               .then(function (sub) {
+                 var registration_id = sub.endpoint;
+                 var data = {
+                   p256dh: btoa(
+                     String.fromCharCode.apply(
+                       null,
+                       new Uint8Array(sub.getKey('p256dh'))
+                     )
+                   ),
+                   auth: btoa(
+                     String.fromCharCode.apply(
+                       null,
+                       new Uint8Array(sub.getKey('auth'))
+                     )
+                   ),
+                   registration_id: registration_id,
+                 }
+                 requestPOSTToServer(data)
+               })
+               .catch(function (e) {
+                 if (Notification.permission === 'denied') {
+                   console.warn('Permission for notifications was denied')
+                 } else {
+                   console.error('Unable to subscribe to push', e)
+                 }
+               })
+           })
+         }
+       }
+
+       // Send the subscription data to your server
+       function requestPOSTToServer (data) {
+         const headers = new Headers();
+         headers.set('Content-Type', 'application/json');
+         const requestOptions = {
+           method: 'POST',
+           headers,
+           body: JSON.stringify(data),
+         };
+
+         return (
+           fetch(
+             '<your endpoint url>',
+             requestOptions
+           )
+         ).then((response) => response.json())
+       }
+
+Server Side logic to create webpush
+------------------------------
+Is is up to you how to add a view in your django application that can handle a 
POST of p256dh, auth, registration_id and create a WebPushDevice with those 
values assoicated with the appropriate user.
+For example you could use rest_framework
+
+.. code-block:: python
+
+       from rest_framework.routers import SimpleRouter
+       from push_notifications.api.rest_framework import WebPushDeviceViewSet
+       ....
+       api_router = SimpleRouter()
+       api_router.register(r'push/web', WebPushDeviceViewSet, 
basename='web_push')
+       ...
+       urlpatterns += [
+               # Api
+               re_path('api/v1/', include(api_router.urls)),
+               ...
+       ]
+
+Or a generic function view (add your own boilerplate for errors and 
protections)
+
+.. code-block:: python
+
+       import json
+       from push_notifications.models import WebPushDevice
+       def register_webpush(request):
+               data = json.loads(request.body)
+               WebPushDevice.objects.create(
+                       user=request.user,
+                       **data
+               )
+
+
+Service Worker to show messages
+------------------------------
+You will need a service worker registered with your web app that can handle 
the notfications, for example
+
+.. code-block:: javascript
+
+       // Example navigatorPush.service.js file
+
+       var getTitle = function (title) {
+         if (title === "") {
+           title = "TITLE DEFAULT";
+         }
+         return title;
+       };
+       var getNotificationOptions = function (message, message_tag) {
+         var options = {
+           body: message,
+           icon: '/img/icon_120.png',
+           tag: message_tag,
+           vibrate: [200, 100, 200, 100, 200, 100, 200]
+         };
+         return options;
+       };
+
+       self.addEventListener('install', function (event) {
+         self.skipWaiting();
+       });
+
+       self.addEventListener('push', function(event) {
+         try {
+           // Push is a JSON
+           var response_json = event.data.json();
+           var title = response_json.title;
+           var message = response_json.message;
+           var message_tag = response_json.tag;
+         } catch (err) {
+           // Push is a simple text
+           var title = "";
+           var message = event.data.text();
+           var message_tag = "";
+         }
+         self.registration.showNotification(getTitle(title), 
getNotificationOptions(message, message_tag));
+         // Optional: Comunicating with our js application. Send a signal
+         self.clients.matchAll({includeUncontrolled: true, type: 
'window'}).then(function (clients) {
+           clients.forEach(function (client) {
+             client.postMessage({
+               "data": message_tag,
+               "data_title": title,
+               "data_body": message});
+             });
+         });
+       });
+
+       // Optional: Added to that the browser opens when you click on the 
notification push web.
+       self.addEventListener('notificationclick', function(event) {
+         // Android doesn't close the notification when you click it
+         // See http://crbug.com/463146
+         event.notification.close();
+         // Check if there's already a tab open with this URL.
+         // If yes: focus on the tab.
+         // If no: open a tab with the URL.
+         event.waitUntil(clients.matchAll({type: 'window', 
includeUncontrolled: true}).then(function(windowClients) {
+             for (var i = 0; i < windowClients.length; i++) {
+               var client = windowClients[i];
+               if ('focus' in client) {
+                 return client.focus();
+               }
+             }
+           })
+         );
+       });
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/api/rest_framework.py 
new/django-push-notifications-3.0.2/push_notifications/api/rest_framework.py
--- 
old/django-push-notifications-3.0.0/push_notifications/api/rest_framework.py    
    2022-02-14 03:43:49.000000000 +0100
+++ 
new/django-push-notifications-3.0.2/push_notifications/api/rest_framework.py    
    2023-10-29 17:59:32.000000000 +0100
@@ -22,7 +22,7 @@
                        data = int(data, 16) if type(data) != int else data
                except ValueError:
                        raise ValidationError("Device ID is not a valid hex 
number")
-               return super(HexIntegerField, self).to_internal_value(data)
+               return super().to_internal_value(data)
 
        def to_representation(self, value):
                return value
@@ -46,10 +46,10 @@
                model = APNSDevice
 
        def validate_registration_id(self, value):
-               # iOS device tokens are 256-bit hexadecimal (64 characters). In 
2016 Apple is increasing
-               # iOS device tokens to 100 bytes hexadecimal (200 characters).
 
-               if hex_re.match(value) is None or len(value) not in (64, 200):
+               # 
https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622958-application
+               # As of 02/2023 APNS tokens (registration_id) "are of variable 
length. Do not hard-code their size."
+               if hex_re.match(value) is None:
                        raise ValidationError("Registration ID (device token) 
is invalid")
 
                return value
@@ -160,12 +160,12 @@
        def perform_create(self, serializer):
                if self.request.user.is_authenticated:
                        serializer.save(user=self.request.user)
-               return super(DeviceViewSetMixin, 
self).perform_create(serializer)
+               return super().perform_create(serializer)
 
        def perform_update(self, serializer):
                if self.request.user.is_authenticated:
                        serializer.save(user=self.request.user)
-               return super(DeviceViewSetMixin, 
self).perform_update(serializer)
+               return super().perform_update(serializer)
 
 
 class AuthorizedMixin:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/conf/app.py 
new/django-push-notifications-3.0.2/push_notifications/conf/app.py
--- old/django-push-notifications-3.0.0/push_notifications/conf/app.py  
2022-02-14 03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/push_notifications/conf/app.py  
2023-10-29 17:59:32.000000000 +0100
@@ -171,7 +171,7 @@
                """Validate the APNS certificate at startup."""
 
                try:
-                       with open(certfile, "r") as f:
+                       with open(certfile) as f:
                                content = f.read()
                                check_apns_certificate(content)
                except Exception as e:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/conf/legacy.py 
new/django-push-notifications-3.0.2/push_notifications/conf/legacy.py
--- old/django-push-notifications-3.0.0/push_notifications/conf/legacy.py       
2022-02-14 03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/push_notifications/conf/legacy.py       
2023-10-29 17:59:32.000000000 +0100
@@ -119,8 +119,7 @@
                return self._get_application_settings(application_id, 
"APNS_USE_SANDBOX", self.msg)
 
        def get_apns_use_alternative_port(self, application_id=None):
-               return
-               self._get_application_settings(application_id, 
"APNS_USE_ALTERNATIVE_PORT", self.msg)
+               return self._get_application_settings(application_id, 
"APNS_USE_ALTERNATIVE_PORT", self.msg)
 
        def get_apns_topic(self, application_id=None):
                return self._get_application_settings(application_id, 
"APNS_TOPIC", self.msg)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/fields.py 
new/django-push-notifications-3.0.2/push_notifications/fields.py
--- old/django-push-notifications-3.0.0/push_notifications/fields.py    
2022-02-14 03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/push_notifications/fields.py    
2023-10-29 17:59:32.000000000 +0100
@@ -13,7 +13,7 @@
 UNSIGNED_64BIT_INT_MAX_VALUE = 2 ** 64 - 1
 
 
-hex_re = re.compile(r"^(([0-9A-f])|(0x[0-9A-f]))+$")
+hex_re = re.compile(r"^(0x)?([0-9a-f])+$", re.I)
 signed_integer_vendors = [
        "postgresql",
        "sqlite",
@@ -48,7 +48,7 @@
                self.default_validators = [
                        RegexValidator(hex_re, _("Enter a valid hexadecimal 
number"), "invalid")
                ]
-               super(HexadecimalField, self).__init__(*args, **kwargs)
+               super().__init__(*args, **kwargs)
 
        def prepare_value(self, value):
                # converts bigint from db to hex before it is displayed in admin
@@ -82,7 +82,7 @@
                elif "sqlite" == connection.vendor:
                        return "UNSIGNED BIG INT"
                else:
-                       return super(HexIntegerField, 
self).db_type(connection=connection)
+                       return super().db_type(connection=connection)
 
        def get_prep_value(self, value):
                """ Return the integer value to be stored from the hex string 
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/migrations/0001_initial.py
 
new/django-push-notifications-3.0.2/push_notifications/migrations/0001_initial.py
--- 
old/django-push-notifications-3.0.0/push_notifications/migrations/0001_initial.py
   2022-02-14 03:43:49.000000000 +0100
+++ 
new/django-push-notifications-3.0.2/push_notifications/migrations/0001_initial.py
   2023-10-29 17:59:32.000000000 +0100
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django.conf import settings
 from django.db import migrations, models
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/migrations/0002_auto_20160106_0850.py
 
new/django-push-notifications-3.0.2/push_notifications/migrations/0002_auto_20160106_0850.py
--- 
old/django-push-notifications-3.0.0/push_notifications/migrations/0002_auto_20160106_0850.py
        2022-02-14 03:43:49.000000000 +0100
+++ 
new/django-push-notifications-3.0.2/push_notifications/migrations/0002_auto_20160106_0850.py
        2023-10-29 17:59:32.000000000 +0100
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.9.1 on 2016-01-06 08:50
 from django.db import migrations, models
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/migrations/0003_wnsdevice.py
 
new/django-push-notifications-3.0.2/push_notifications/migrations/0003_wnsdevice.py
--- 
old/django-push-notifications-3.0.0/push_notifications/migrations/0003_wnsdevice.py
 2022-02-14 03:43:49.000000000 +0100
+++ 
new/django-push-notifications-3.0.2/push_notifications/migrations/0003_wnsdevice.py
 2023-10-29 17:59:32.000000000 +0100
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.9.6 on 2016-06-13 20:46
 import django.db.models.deletion
 from django.conf import settings
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/migrations/0004_fcm.py 
new/django-push-notifications-3.0.2/push_notifications/migrations/0004_fcm.py
--- 
old/django-push-notifications-3.0.0/push_notifications/migrations/0004_fcm.py   
    2022-02-14 03:43:49.000000000 +0100
+++ 
new/django-push-notifications-3.0.2/push_notifications/migrations/0004_fcm.py   
    2023-10-29 17:59:32.000000000 +0100
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # Generated by Django 1.9.6 on 2016-06-13 20:46
 from django.conf import settings
 from django.db import migrations, models
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/migrations/0005_applicationid.py
 
new/django-push-notifications-3.0.2/push_notifications/migrations/0005_applicationid.py
--- 
old/django-push-notifications-3.0.0/push_notifications/migrations/0005_applicationid.py
     2022-02-14 03:43:49.000000000 +0100
+++ 
new/django-push-notifications-3.0.2/push_notifications/migrations/0005_applicationid.py
     2023-10-29 17:59:32.000000000 +0100
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django.conf import settings
 from django.db import migrations, models
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/migrations/0006_webpushdevice.py
 
new/django-push-notifications-3.0.2/push_notifications/migrations/0006_webpushdevice.py
--- 
old/django-push-notifications-3.0.0/push_notifications/migrations/0006_webpushdevice.py
     2022-02-14 03:43:49.000000000 +0100
+++ 
new/django-push-notifications-3.0.2/push_notifications/migrations/0006_webpushdevice.py
     2023-10-29 17:59:32.000000000 +0100
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from django.conf import settings
 from django.db import migrations, models
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/models.py 
new/django-push-notifications-3.0.2/push_notifications/models.py
--- old/django-push-notifications-3.0.0/push_notifications/models.py    
2022-02-14 03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/push_notifications/models.py    
2023-10-29 17:59:32.000000000 +0100
@@ -255,6 +255,4 @@
        def send_message(self, message, **kwargs):
                from .webpush import webpush_send_message
 
-               return webpush_send_message(
-                       uri=self.registration_id, message=message, 
browser=self.browser,
-                       auth=self.auth, p256dh=self.p256dh, 
application_id=self.application_id, **kwargs)
+               return webpush_send_message(self, message, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/push_notifications/webpush.py 
new/django-push-notifications-3.0.2/push_notifications/webpush.py
--- old/django-push-notifications-3.0.0/push_notifications/webpush.py   
2022-02-14 03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/push_notifications/webpush.py   
2023-10-29 17:59:32.000000000 +0100
@@ -1,3 +1,5 @@
+import warnings
+
 from pywebpush import WebPushException, webpush
 
 from .conf import get_manager
@@ -5,9 +7,18 @@
 
 
 def get_subscription_info(application_id, uri, browser, auth, p256dh):
-       url = get_manager().get_wp_post_url(application_id, browser)
+       if uri.startswith("https://";):
+               endpoint = uri
+       else:
+               url = get_manager().get_wp_post_url(application_id, browser)
+               endpoint = "{}/{}".format(url, uri)
+               warnings.warn(
+                       "registration_id should be the full endpoint returned 
from pushManager.subscribe",
+                       DeprecationWarning,
+                       stacklevel=2,
+               )
        return {
-               "endpoint": "{}/{}".format(url, uri),
+               "endpoint": endpoint,
                "keys": {
                        "auth": auth,
                        "p256dh": p256dh,
@@ -15,25 +26,30 @@
        }
 
 
-def webpush_send_message(
-       uri, message, browser, auth, p256dh, application_id=None, **kwargs
-):
-       subscription_info = get_subscription_info(application_id, uri, browser, 
auth, p256dh)
-
+def webpush_send_message(device, message, **kwargs):
+       subscription_info = get_subscription_info(
+               device.application_id, device.registration_id,
+               device.browser, device.auth, device.p256dh)
        try:
+               results = {"results": [{"original_registration_id": 
device.registration_id}]}
                response = webpush(
                        subscription_info=subscription_info,
                        data=message,
-                       
vapid_private_key=get_manager().get_wp_private_key(application_id),
-                       
vapid_claims=get_manager().get_wp_claims(application_id).copy(),
+                       
vapid_private_key=get_manager().get_wp_private_key(device.application_id),
+                       
vapid_claims=get_manager().get_wp_claims(device.application_id).copy(),
                        **kwargs
                )
-               results = {"results": [{}]}
-               if not response.ok:
-                       results["results"][0]["error"] = response.content
-                       results["results"][0]["original_registration_id"] = 
response.content
-               else:
+               if response.ok:
                        results["success"] = 1
+               else:
+                       results["failure"] = 1
+                       results["results"][0]["error"] = response.content
                return results
        except WebPushException as e:
+               if e.response is not None and e.response.status_code in [404, 
410]:
+                       results["failure"] = 1
+                       results["results"][0]["error"] = e.message
+                       device.active = False
+                       device.save()
+                       return results
                raise WebPushError(e.message)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-push-notifications-3.0.0/setup.cfg 
new/django-push-notifications-3.0.2/setup.cfg
--- old/django-push-notifications-3.0.0/setup.cfg       2022-02-14 
03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/setup.cfg       2023-10-29 
17:59:32.000000000 +0100
@@ -38,7 +38,6 @@
 APNS =
        apns2>=0.3.0
        importlib-metadata;python_version < "3.8"
-       pywebpush>=1.3.0
        Django>=2.2
 
 WP = pywebpush>=1.3.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-push-notifications-3.0.0/setup.py 
new/django-push-notifications-3.0.2/setup.py
--- old/django-push-notifications-3.0.0/setup.py        2022-02-14 
03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/setup.py        2023-10-29 
17:59:32.000000000 +0100
@@ -1,4 +1,11 @@
 #!/usr/bin/env python
 from setuptools import setup
+from pathlib import Path
 
-setup(use_scm_version={"version_scheme": "post-release"})
+this_directory = Path(__file__).parent
+long_description = (this_directory / "README.rst").read_text()
+setup(
+    long_description=long_description,
+    long_description_content_type='text/x-rst',
+    use_scm_version={"version_scheme": "post-release"}
+)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-push-notifications-3.0.0/tests/settings.py 
new/django-push-notifications-3.0.2/tests/settings.py
--- old/django-push-notifications-3.0.0/tests/settings.py       2022-02-14 
03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/tests/settings.py       2023-10-29 
17:59:32.000000000 +0100
@@ -26,5 +26,5 @@
 SECRET_KEY = "foobar"
 
 PUSH_NOTIFICATIONS_SETTINGS = {
-       "WP_CLAIMS": {"sub": "mailto: jazzb...@example.com"}
+       "WP_CLAIMS": {"sub": "mailto:jazzb...@example.com"}
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/tests/settings_unique.py 
new/django-push-notifications-3.0.2/tests/settings_unique.py
--- old/django-push-notifications-3.0.0/tests/settings_unique.py        
2022-02-14 03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/tests/settings_unique.py        
2023-10-29 17:59:32.000000000 +0100
@@ -26,6 +26,6 @@
 SECRET_KEY = "foobar"
 
 PUSH_NOTIFICATIONS_SETTINGS = {
-       "WP_CLAIMS": {"sub": "mailto: jazzb...@example.com"},
+       "WP_CLAIMS": {"sub": "mailto:jazzb...@example.com"},
        "UNIQUE_REG_ID": True
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-push-notifications-3.0.0/tests/test_fields.py 
new/django-push-notifications-3.0.2/tests/test_fields.py
--- old/django-push-notifications-3.0.0/tests/test_fields.py    1970-01-01 
01:00:00.000000000 +0100
+++ new/django-push-notifications-3.0.2/tests/test_fields.py    2023-10-29 
17:59:32.000000000 +0100
@@ -0,0 +1,40 @@
+from django.core.exceptions import ValidationError
+from django.test import SimpleTestCase
+
+from push_notifications.fields import HexadecimalField
+
+
+class HexadecimalFieldTestCase(SimpleTestCase):
+       _INVALID_HEX_VALUES = [
+               "foobar",
+               "GLUTEN",
+               "HeLLo WoRLd",
+               "international",
+               "°!#€%&/()[]{}=?",
+               "0x",
+       ]
+
+       _VALID_HEX_VALUES = {
+               "babe": "babe",
+               "BEEF": "BEEF",
+               " \nfeed \t": "feed",
+               "0x012345789abcdef": "0x012345789abcdef",
+               "012345789aBcDeF": "012345789aBcDeF",
+       }
+
+       def test_clean_invalid_values(self):
+               """Passing invalid values raises ValidationError."""
+               f = HexadecimalField()
+               for invalid in self._INVALID_HEX_VALUES:
+                       self.assertRaisesMessage(
+                               ValidationError,
+                               "'Enter a valid hexadecimal number'",
+                               f.clean,
+                               invalid,
+                       )
+
+       def test_clean_valid_values(self):
+               """Passing valid values returns the expected output."""
+               f = HexadecimalField()
+               for valid, expected in self._VALID_HEX_VALUES.items():
+                       self.assertEqual(expected, f.clean(valid))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/tests/test_legacy_config.py 
new/django-push-notifications-3.0.2/tests/test_legacy_config.py
--- old/django-push-notifications-3.0.0/tests/test_legacy_config.py     
2022-02-14 03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/tests/test_legacy_config.py     
2023-10-29 17:59:32.000000000 +0100
@@ -1,3 +1,5 @@
+from unittest import mock
+
 from django.core.exceptions import ImproperlyConfigured
 from django.test import TestCase
 
@@ -38,9 +40,17 @@
                )
 
        def test_immutable_wp_claims(self):
+               self.endpoint = 
"https://updates.push.services.mozilla.com/wpush/v2/token";
+               self.mock_device = mock.Mock()
+               self.mock_device.application_id = None
+               self.mock_device.registration_id = self.endpoint
+               self.mock_device.auth = "authtest"
+               self.mock_device.p256dh = "p256dhtest"
+               self.mock_device.active = True
+               self.mock_device.save.return_value = True
                vapid_claims_pre = get_manager().get_wp_claims(None).copy()
                try:
-                       webpush_send_message("", {}, "CHROME", "", "")
+                       webpush_send_message(self.mock_device, "message")
                except WebPushError:
                        pass
                vapid_claims_after = get_manager().get_wp_claims(None)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-push-notifications-3.0.0/tests/test_models.py 
new/django-push-notifications-3.0.2/tests/test_models.py
--- old/django-push-notifications-3.0.0/tests/test_models.py    2022-02-14 
03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/tests/test_models.py    2023-10-29 
17:59:32.000000000 +0100
@@ -276,7 +276,7 @@
                        reg_ids = [obj.registration_id for obj in 
GCMDevice.objects.all()]
                        send_bulk_message(reg_ids, {"message": "Hello World"}, 
"GCM")
                        p.assert_called_once_with(
-                               [u"abc", u"abc1"], {"message": "Hello World"}, 
cloud_type="GCM", application_id=None
+                               ["abc", "abc1"], {"message": "Hello World"}, 
cloud_type="GCM", application_id=None
                        )
 
        def test_fcm_send_message(self):
@@ -506,7 +506,7 @@
                        reg_ids = [obj.registration_id for obj in 
GCMDevice.objects.all()]
                        send_bulk_message(reg_ids, {"message": "Hello World"}, 
"FCM")
                        p.assert_called_once_with(
-                               [u"abc", u"abc1"], {"message": "Hello World"}, 
cloud_type="FCM",
+                               ["abc", "abc1"], {"message": "Hello World"}, 
cloud_type="FCM",
                                application_id=None
                        )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/tests/test_rest_framework.py 
new/django-push-notifications-3.0.2/tests/test_rest_framework.py
--- old/django-push-notifications-3.0.0/tests/test_rest_framework.py    
2022-02-14 03:43:49.000000000 +0100
+++ new/django-push-notifications-3.0.2/tests/test_rest_framework.py    
2023-10-29 17:59:32.000000000 +0100
@@ -5,13 +5,13 @@
 )
 
 
-GCM_DRF_INVALID_HEX_ERROR = {"device_id": [u"Device ID is not a valid hex 
number"]}
-GCM_DRF_OUT_OF_RANGE_ERROR = {"device_id": [u"Device ID is out of range"]}
+GCM_DRF_INVALID_HEX_ERROR = {"device_id": ["Device ID is not a valid hex 
number"]}
+GCM_DRF_OUT_OF_RANGE_ERROR = {"device_id": ["Device ID is out of range"]}
 
 
 class APNSDeviceSerializerTestCase(TestCase):
        def test_validation(self):
-               # valid data - 32 bytes upper case
+               # valid data - 64 bytes upper case
                serializer = APNSDeviceSerializer(data={
                        "registration_id": 
"AEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAE",
                        "name": "Apple iPhone 6+",
@@ -20,7 +20,7 @@
                })
                self.assertTrue(serializer.is_valid())
 
-               # valid data - 32 bytes lower case
+               # valid data - 64 bytes lower case
                serializer = APNSDeviceSerializer(data={
                        "registration_id": 
"aeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeae",
                        "name": "Apple iPhone 6+",
@@ -31,7 +31,7 @@
 
                # valid data - 100 bytes upper case
                serializer = APNSDeviceSerializer(data={
-                       "registration_id": "AE" * 100,
+                       "registration_id": "AE" * 50,
                        "name": "Apple iPhone 6+",
                        "device_id": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
                })
@@ -39,7 +39,15 @@
 
                # valid data - 100 bytes lower case
                serializer = APNSDeviceSerializer(data={
-                       "registration_id": "ae" * 100,
+                       "registration_id": "ae" * 50,
+                       "name": "Apple iPhone 6+",
+                       "device_id": "ffffffffffffffffffffffffffffffff",
+               })
+               self.assertTrue(serializer.is_valid())
+
+               # valid data - 200 bytes mixed case
+               serializer = APNSDeviceSerializer(data={
+                       "registration_id": "aE" * 100,
                        "name": "Apple iPhone 6+",
                        "device_id": "ffffffffffffffffffffffffffffffff",
                })
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django-push-notifications-3.0.0/tests/test_webpush.py 
new/django-push-notifications-3.0.2/tests/test_webpush.py
--- old/django-push-notifications-3.0.0/tests/test_webpush.py   1970-01-01 
01:00:00.000000000 +0100
+++ new/django-push-notifications-3.0.2/tests/test_webpush.py   2023-10-29 
17:59:32.000000000 +0100
@@ -0,0 +1,93 @@
+from unittest import mock
+
+from django.test import TestCase
+from pywebpush import WebPushException
+
+from push_notifications.exceptions import WebPushError
+from push_notifications.webpush import (
+       get_subscription_info, webpush_send_message
+)
+
+# Mock Responses
+mock_success_response = mock.MagicMock(status_code=200, ok=True)
+mock_fail_resposne = mock.MagicMock(status_code=400, ok=False, content="Test 
Error")
+mock_unsubscribe_response = mock.MagicMock(
+    status_code=410, ok=False, content="Unsubscribe")
+mock_unsubscribe_response_404 = mock.MagicMock(
+    status_code=404, ok=False, content="Unsubscribe")
+
+
+class WebPushSendMessageTestCase(TestCase):
+       def setUp(self):
+               self.endpoint = 
"https://updates.push.services.mozilla.com/wpush/v2/token";
+               self.mock_device = mock.Mock()
+               self.mock_device.application_id = None
+               self.mock_device.registration_id = self.endpoint
+               self.mock_device.auth = "authtest"
+               self.mock_device.p256dh = "p256dhtest"
+               self.mock_device.active = True
+               self.mock_device.save.return_value = True
+
+       def test_get_subscription_info(self):
+               keys = {"auth": "authtest", "p256dh": "p256dhtest"}
+               endpoint = self.endpoint
+               original = get_subscription_info(
+                       None, "token", "FIREFOX", keys["auth"], keys["p256dh"]
+               )
+
+               self.assertEqual(
+                       original,
+                       {
+                               "endpoint": endpoint,
+                               "keys": keys,
+                       },
+               )
+
+               patched = get_subscription_info(
+                       None,
+                       endpoint,
+                       "",
+                       keys["auth"],
+                       keys["p256dh"],
+               )
+
+               self.assertEqual(
+                       patched,
+                       {
+                               "endpoint": endpoint,
+                               "keys": keys,
+                       },
+               )
+
+       @mock.patch("push_notifications.webpush.webpush", 
return_value=mock_success_response)
+       def test_webpush_send_message(self, webpush_mock):
+               results = webpush_send_message(self.mock_device, "message")
+               self.assertEqual(results["success"], 1)
+
+       @mock.patch("push_notifications.webpush.webpush", 
return_value=mock_fail_resposne)
+       def test_webpush_send_message_failure(self, webpush_mock):
+               results = webpush_send_message(self.mock_device, "message")
+               self.assertEqual(results["failure"], 1)
+
+       @mock.patch(
+        "push_notifications.webpush.webpush",
+        side_effect=WebPushException("Unsubscribe",
+        response=mock_unsubscribe_response))
+       def test_webpush_send_message_unsubscribe(self, webpush_mock):
+               results = webpush_send_message(self.mock_device, "message")
+               self.assertEqual(results["failure"], 1)
+
+       @mock.patch(
+        "push_notifications.webpush.webpush",
+        side_effect=WebPushException("Unsubscribe",
+        response=mock_unsubscribe_response_404))
+       def test_webpush_send_message_404(self, webpush_mock):
+               results = webpush_send_message(self.mock_device, "message")
+               self.assertEqual(results["failure"], 1)
+
+       @mock.patch(
+        "push_notifications.webpush.webpush",
+        side_effect=WebPushException("Error"))
+       def test_webpush_send_message_exception(self, webpush_mock):
+               with self.assertRaises(WebPushError):
+                       webpush_send_message(self.mock_device, "message")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django-push-notifications-3.0.0/tox.ini 
new/django-push-notifications-3.0.2/tox.ini
--- old/django-push-notifications-3.0.0/tox.ini 2022-02-14 03:43:49.000000000 
+0100
+++ new/django-push-notifications-3.0.2/tox.ini 2023-10-29 17:59:32.000000000 
+0100
@@ -1,9 +1,9 @@
 [tox]
-skipsdist = True
+skipsdist = False
 usedevelop = true
 envlist =
     py{36,37,38,39}-dj{22,32}
-    py{38,39}-dj{40,main}
+    py{38,39}-dj{40,405}
     flake8
 
 [gh-actions]
@@ -18,7 +18,7 @@
     2.2: dj22
     3.2: dj32
     4.0: dj40
-    main: djmain
+       4.0.5: dj405
 
 [testenv]
 usedevelop = true
@@ -38,8 +38,8 @@
     djangorestframework
     dj22: Django>=2.2,<3.0
     dj32: Django>=3.2,<3.3
-    dj40: Django>=4.0,<4.1
-    djmain: https://github.com/django/django/archive/main.tar.gz
+    dj40: Django>=4.0,<4.0.5
+       dj405: Django>=4.0.5,<4.1
 
 [testenv:flake8]
 commands = flake8 --exit-zero

Reply via email to