From a00b0df99436a154da0e671c6ff6eb0b3fbf3e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABrs=20Cirsis?= Date: Thu, 6 Jul 2023 16:56:48 +0300 Subject: [PATCH 01/18] Create and set up an event loop, if code is executing in non-main thread. if code is executing in a non-main thread: By default, the main thread in Python has an event loop set up automatically. However, if the code is running in a separate thread, we need to manually create and set up an event loop for that thread. --- django_walletpass/services.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/django_walletpass/services.py b/django_walletpass/services.py index 092f222..59b323f 100644 --- a/django_walletpass/services.py +++ b/django_walletpass/services.py @@ -12,7 +12,11 @@ class PushBackend: def __init__(self): - self.loop = asyncio.get_event_loop() + try: + self.loop = asyncio.get_event_loop() + except RuntimeError: + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) async def push_notification(self, client, token): From 1ad7809d247de0ddb6c62d6932a3f6f754d81f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Sun, 9 Jul 2023 16:02:06 +0300 Subject: [PATCH 02/18] ADD Human-readable name for the application --- django_walletpass/apps.py | 1 + 1 file changed, 1 insertion(+) diff --git a/django_walletpass/apps.py b/django_walletpass/apps.py index a715e0f..10d6127 100644 --- a/django_walletpass/apps.py +++ b/django_walletpass/apps.py @@ -3,6 +3,7 @@ class DjangoWalletpassConfig(AppConfig): name = 'django_walletpass' + verbose_name = 'Django walletpass' def ready(self): from django_walletpass import signals as _signals # pylint: disable=import-outside-toplevel From 605a03b0472c608a83a45eab2d4a61fc97c0cd3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Sun, 9 Jul 2023 16:17:43 +0300 Subject: [PATCH 03/18] string representation Starting from Python 3, the __str__ method was introduced as a replacement for __unicode__ to provide a string representation of an object. --- django_walletpass/models.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/django_walletpass/models.py b/django_walletpass/models.py index 1157d35..35b261f 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -285,6 +285,9 @@ def get_pass_builder(self): def __unicode__(self): return self.serial_number + def __str__(self): + return self.serial_number + class Meta: verbose_name_plural = "passes" unique_together = ( @@ -308,6 +311,9 @@ class Registration(models.Model): def __unicode__(self): return self.device_library_identifier + def __str__(self): + return self.device_library_identifier + class Log(models.Model): """ @@ -317,3 +323,6 @@ class Log(models.Model): def __unicode__(self): return self.message + + def __str__(self): + return self.message From 3b33657fbf038f488d31d8d3518c8f0c5b83f1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Sun, 9 Jul 2023 19:18:53 +0300 Subject: [PATCH 04/18] Django Admin tweaks for Models - searchable, filterable --- django_walletpass/admin.py | 53 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/django_walletpass/admin.py b/django_walletpass/admin.py index a720e15..5a27b52 100644 --- a/django_walletpass/admin.py +++ b/django_walletpass/admin.py @@ -1,6 +1,55 @@ from django.contrib import admin +from django.urls import reverse +from django.utils.html import format_html +from django.utils.safestring import mark_safe + from django_walletpass.models import Pass, Registration, Log -admin.site.register(Pass) -admin.site.register(Registration) admin.site.register(Log) + + +@admin.register(Pass) +class PassAdmin(admin.ModelAdmin): + list_display = ("serial_number", "updated_at", "pass_type_identifier", "wallet_pass_") + search_fields = ("serial_number", "pass_type_identifier", "authentication_token", "data") + list_filter = ("pass_type_identifier", "updated_at") + date_hierarchy = "updated_at" + readonly_fields = ("wallet_pass_", "updated_at") + + def wallet_pass_(self, obj: Pass): + if obj.data: + svg_icon = '' \ + '' \ + '' + return format_html( + "{icon}", + url=obj.data.url, + title=obj.data.name, + icon=mark_safe(svg_icon), + ) + return + + wallet_pass_.short_description = "Pass" + + +@admin.register(Registration) +class PassAdmin(admin.ModelAdmin): + list_display = ("device_library_identifier", "push_token", "pass_") + search_fields = ("device_library_identifier", "push_token", "pazz") + raw_id_fields = ("pazz",) + readonly_fields = ("pass_",) + + def pass_(self, obj: Registration): + if obj.pazz_id: + url = reverse( + "admin:%s_%s_change" % (obj.pazz._meta.app_label, obj.pazz._meta.model_name), + args=[obj.pazz_id], + ) + return format_html( + "{title}", + url=url, + title=obj.pazz.serial_number, + ) + return + + pass_.short_description = "Pass" From 6d6d7b2e41c9a4f2655973b838e803c590869d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Sun, 9 Jul 2023 22:00:40 +0300 Subject: [PATCH 05/18] Add fields to the Log model --- .../migrations/0009_auto_20230709_2143.py | 66 +++++++++++++++++++ django_walletpass/models.py | 15 +++++ 2 files changed, 81 insertions(+) create mode 100644 django_walletpass/migrations/0009_auto_20230709_2143.py diff --git a/django_walletpass/migrations/0009_auto_20230709_2143.py b/django_walletpass/migrations/0009_auto_20230709_2143.py new file mode 100644 index 0000000..6d21b0e --- /dev/null +++ b/django_walletpass/migrations/0009_auto_20230709_2143.py @@ -0,0 +1,66 @@ +# Generated by Django 3.2.14 on 2023-07-09 18:43 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import django_walletpass.storage + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_walletpass', '0008_alter_pass_data'), + ] + + operations = [ + migrations.AddField( + model_name='log', + name='created_at', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AddField( + model_name='log', + name='device_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='log', + name='msg', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='log', + name='pass_type_identifier', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='log', + name='pazz', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='logs', to='django_walletpass.pass'), + ), + migrations.AddField( + model_name='log', + name='serial_number', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='log', + name='status', + field=models.CharField(blank=True, max_length=100, null=True), + ), + migrations.AddField( + model_name='log', + name='task_type', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='log', + name='web_service_url', + field=models.URLField(blank=True, null=True), + ), + migrations.AlterField( + model_name='pass', + name='data', + field=models.FileField(storage=django_walletpass.storage.WalletPassStorage(), upload_to='passes'), + ), + ] diff --git a/django_walletpass/models.py b/django_walletpass/models.py index 1157d35..df67906 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -1,4 +1,6 @@ +import datetime import os +import re import uuid import hashlib import json @@ -7,6 +9,7 @@ import zipfile from glob import glob from django.core.exceptions import ValidationError +from django.utils import timezone from django.utils.module_loading import import_string from django.db import models from django.utils.translation import gettext_lazy as _ @@ -313,7 +316,19 @@ class Log(models.Model): """ Log message sent by a device """ + created_at = models.DateTimeField(default=timezone.now) + status = models.CharField(max_length=100, null=True, blank=True) + task_type = models.CharField(max_length=255, null=True, blank=True) + pass_type_identifier = models.CharField(max_length=255, null=True, blank=True) + serial_number = models.CharField(max_length=255, null=True, blank=True) + pazz = models.ForeignKey(Pass, null=True, blank=True, on_delete=models.CASCADE, related_name='logs') + web_service_url = models.URLField(null=True, blank=True) + device_id = models.CharField(max_length=255, null=True, blank=True) + msg = models.TextField(null=True, blank=True) message = models.TextField() def __unicode__(self): return self.message + + def __str__(self): + return self.message From ed0c197b38a15036ad88d2e780d026ff34dc92d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Sun, 9 Jul 2023 22:02:37 +0300 Subject: [PATCH 06/18] Add LogAdmin --- django_walletpass/admin.py | 39 +++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/django_walletpass/admin.py b/django_walletpass/admin.py index a720e15..31d2802 100644 --- a/django_walletpass/admin.py +++ b/django_walletpass/admin.py @@ -1,6 +1,43 @@ from django.contrib import admin +from django.urls import reverse +from django.utils.html import format_html + from django_walletpass.models import Pass, Registration, Log admin.site.register(Pass) admin.site.register(Registration) -admin.site.register(Log) + + +@admin.register(Log) +class LogAdmin(admin.ModelAdmin): + list_display = ( + "created_at", + "status", + "task_type", + # "pass_type_identifier", + # "serial_number", + "pass_", + # "web_service_url", + "device_id", + "msg", + ) + list_filter = ("status", "task_type", "pass_type_identifier") + search_fields = ("pass_type_identifier", "serial_number", "device_id", "msg", "message") + readonly_fields = ("created_at", "pass_") + raw_id_fields = ("pazz",) + list_select_related = ("pazz",) + + def pass_(self, obj: Log): + if obj.pazz_id: + url = reverse( + "admin:%s_%s_change" % (obj.pazz._meta.app_label, obj.pazz._meta.model_name), + args=[obj.pazz_id], + ) + return format_html( + "{title}", + url=url, + title=obj.serial_number, + ) + return obj.serial_number + + pass_.short_description = "Pass" From 020428ce4782ce69baabe36b1e1bf08c0ffd004e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Sun, 9 Jul 2023 22:03:14 +0300 Subject: [PATCH 07/18] parse_log Class method --- django_walletpass/models.py | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/django_walletpass/models.py b/django_walletpass/models.py index df67906..25c3b09 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -332,3 +332,53 @@ def __unicode__(self): def __str__(self): return self.message + + @classmethod + def parse_log(cls, log, message): + pattern_register = r"\[(.*?)\]\s(.*?)\s\(for device (.*?), pass type (.*?), serial number (.*?); with web service url (.*?)\)\s(.*?): (.*$)" + pattern_get = r"\[(.*?)\]\s(.*?)\s\(pass type (.*?), serial number (.*?), if-modified-since \(.*?\); with web service url (.*?)\) (.*?): (.*$)" + pattern_web_service_error = r"\[(.*?)\]\s(.*?)\sfor (.*?)\s\((.*?)\):\s(.*)" + + match_register = re.match(pattern_register, message) + match_get = re.match(pattern_get, message) + match_web_service_error = re.match(pattern_web_service_error, message) + + if match_register: + timestamp_str, task_type, device_id, pass_type_identifier, serial_number, web_service_url, status, msg = match_register.groups() + elif match_get: + timestamp_str, task_type, pass_type_identifier, serial_number, web_service_url, status, msg = match_get.groups() + device_id = None # 'Get pass task' entries don't include device_id + elif match_web_service_error: + timestamp_str, task_type, pass_type_identifier, web_service_url, msg = match_web_service_error.groups() + serial_number = None + device_id = None + status = "error" + else: + log.status = 'unknown' + log.message = message + log.save() + return # Log entry didn't match any known pattern + + if 'error' in status: + status = 'error' + elif 'warning' in status: + status = 'warning' + + log.created_at = datetime.datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S %z") + log.status = status + log.task_type = task_type + log.device_id = device_id + log.pass_type_identifier = pass_type_identifier + log.serial_number = serial_number + log.web_service_url = web_service_url + log.msg = msg + log.message = message + + if serial_number: + try: + pazz = Pass.objects.get(serial_number=serial_number) + log.pazz = pazz + except Pass.DoesNotExist: + pass + + log.save() From 203f19cac1579f71c691ca5af6a25d92ad0ade56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Sun, 9 Jul 2023 22:03:43 +0300 Subject: [PATCH 08/18] parse_log when log data is received --- django_walletpass/classviews.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django_walletpass/classviews.py b/django_walletpass/classviews.py index d7813a3..3bffc6b 100644 --- a/django_walletpass/classviews.py +++ b/django_walletpass/classviews.py @@ -142,5 +142,6 @@ class LogViewSet(viewsets.ViewSet): def create(self, request): json_body = json.loads(request.body) for message in json_body['logs']: - Log(message=message).save() + log = Log(message=message) + Log.parse_log(log, message) return Response({}, status=status.HTTP_200_OK) From 7efc96f57f311efa5731f2161eb46a439980158d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Sun, 9 Jul 2023 22:50:01 +0300 Subject: [PATCH 09/18] add web_service_root_view to prevent error log web service url https://xxxx/passes/) encountered error: Unexpected response code 404 --- django_walletpass/classviews.py | 4 ++++ django_walletpass/urls.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/django_walletpass/classviews.py b/django_walletpass/classviews.py index 3bffc6b..af5dd1a 100644 --- a/django_walletpass/classviews.py +++ b/django_walletpass/classviews.py @@ -27,6 +27,10 @@ def get_pass(pass_type_id, serial_number): return get_object_or_404(Pass, pass_type_identifier=pass_type_id, serial_number=serial_number) +def web_service_root_view(request): + return HttpResponse(status=200) + + class RegistrationsViewSet(viewsets.ViewSet): """ Gets the Serial Numbers for Passes Associated with a Device diff --git a/django_walletpass/urls.py b/django_walletpass/urls.py index 53028e4..e02431b 100644 --- a/django_walletpass/urls.py +++ b/django_walletpass/urls.py @@ -1,7 +1,8 @@ -from django.urls import re_path +from django.urls import re_path, path from . import classviews urlpatterns = [ + path('', classviews.web_service_root_view, name='walletpass_web_service_url'), re_path( r'^v1/devices/(?P.+)/registrations/(?P.+)/(?P.+)$', classviews.RegisterPassViewSet.as_view({ From aa5e3d2aa8ff5a35c46b015a2307fd48b054a7cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Mon, 10 Jul 2023 13:17:10 +0300 Subject: [PATCH 10/18] Fix typo and search_fields --- django_walletpass/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django_walletpass/admin.py b/django_walletpass/admin.py index 9151576..36dbb36 100644 --- a/django_walletpass/admin.py +++ b/django_walletpass/admin.py @@ -66,9 +66,9 @@ def wallet_pass_(self, obj: Pass): @admin.register(Registration) -class PassAdmin(admin.ModelAdmin): +class RegistrationAdmin(admin.ModelAdmin): list_display = ("device_library_identifier", "push_token", "pass_") - search_fields = ("device_library_identifier", "push_token", "pazz") + search_fields = ("device_library_identifier", "push_token", "pazz__serial_number") raw_id_fields = ("pazz",) readonly_fields = ("pass_",) From 88d14e1f6240549ddbcf1f28d163c9ade27baec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Mon, 10 Jul 2023 13:17:46 +0300 Subject: [PATCH 11/18] Remove web_service_root_view --- django_walletpass/classviews.py | 4 ---- django_walletpass/urls.py | 1 - 2 files changed, 5 deletions(-) diff --git a/django_walletpass/classviews.py b/django_walletpass/classviews.py index af5dd1a..3bffc6b 100644 --- a/django_walletpass/classviews.py +++ b/django_walletpass/classviews.py @@ -27,10 +27,6 @@ def get_pass(pass_type_id, serial_number): return get_object_or_404(Pass, pass_type_identifier=pass_type_id, serial_number=serial_number) -def web_service_root_view(request): - return HttpResponse(status=200) - - class RegistrationsViewSet(viewsets.ViewSet): """ Gets the Serial Numbers for Passes Associated with a Device diff --git a/django_walletpass/urls.py b/django_walletpass/urls.py index e02431b..883bbfa 100644 --- a/django_walletpass/urls.py +++ b/django_walletpass/urls.py @@ -2,7 +2,6 @@ from . import classviews urlpatterns = [ - path('', classviews.web_service_root_view, name='walletpass_web_service_url'), re_path( r'^v1/devices/(?P.+)/registrations/(?P.+)/(?P.+)$', classviews.RegisterPassViewSet.as_view({ From 3c3178e0792ee9e6d5ec585934849dd531158def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Mon, 10 Jul 2023 14:09:46 +0300 Subject: [PATCH 12/18] Log representation - creation date --- django_walletpass/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_walletpass/models.py b/django_walletpass/models.py index 0f14d01..cf6b65f 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -337,7 +337,7 @@ def __unicode__(self): return self.message def __str__(self): - return self.message + return self.created_at.strftime('%d/%m/%y %H:%M:%S') @classmethod def parse_log(cls, log, message): From 9e0846656c4284c9a4aad3f242b21a8321b6a200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Tue, 11 Jul 2023 18:31:38 +0300 Subject: [PATCH 13/18] Add more patterns to parse_log --- django_walletpass/models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/django_walletpass/models.py b/django_walletpass/models.py index cf6b65f..509c4d6 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -344,10 +344,12 @@ def parse_log(cls, log, message): pattern_register = r"\[(.*?)\]\s(.*?)\s\(for device (.*?), pass type (.*?), serial number (.*?); with web service url (.*?)\)\s(.*?): (.*$)" pattern_get = r"\[(.*?)\]\s(.*?)\s\(pass type (.*?), serial number (.*?), if-modified-since \(.*?\); with web service url (.*?)\) (.*?): (.*$)" pattern_web_service_error = r"\[(.*?)\]\s(.*?)\sfor (.*?)\s\((.*?)\):\s(.*)" + pattern_get_warning = r"\[(.*?)\]\s(.*?)\s\(pass type (.*?), serial number (.*?), if-modified-since \(.*?\); with web service url (.*?)\) (.*?): (.*\.)\s(.*$)" match_register = re.match(pattern_register, message) match_get = re.match(pattern_get, message) match_web_service_error = re.match(pattern_web_service_error, message) + match_get_warning = re.match(pattern_get_warning, message) if match_register: timestamp_str, task_type, device_id, pass_type_identifier, serial_number, web_service_url, status, msg = match_register.groups() @@ -359,6 +361,10 @@ def parse_log(cls, log, message): serial_number = None device_id = None status = "error" + elif match_get_warning: + timestamp_str, task_type, pass_type_identifier, serial_number, web_service_url, status, msg = match_get_warning.groups() + device_id = None + status = "warning" else: log.status = 'unknown' log.message = message From 3d4b75542e5915857ffa6d060b634b5003b65497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Tue, 11 Jul 2023 19:04:00 +0300 Subject: [PATCH 14/18] Adjust the regular expression --- django_walletpass/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_walletpass/models.py b/django_walletpass/models.py index 509c4d6..0d11a06 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -343,7 +343,7 @@ def __str__(self): def parse_log(cls, log, message): pattern_register = r"\[(.*?)\]\s(.*?)\s\(for device (.*?), pass type (.*?), serial number (.*?); with web service url (.*?)\)\s(.*?): (.*$)" pattern_get = r"\[(.*?)\]\s(.*?)\s\(pass type (.*?), serial number (.*?), if-modified-since \(.*?\); with web service url (.*?)\) (.*?): (.*$)" - pattern_web_service_error = r"\[(.*?)\]\s(.*?)\sfor (.*?)\s\((.*?)\):\s(.*)" + pattern_web_service_error = r"\[(.*?)\]\s(.*?)\sfor (.*?)\s\((.*?)\):\s(.*$)" pattern_get_warning = r"\[(.*?)\]\s(.*?)\s\(pass type (.*?), serial number (.*?), if-modified-since \(.*?\); with web service url (.*?)\) (.*?): (.*\.)\s(.*$)" match_register = re.match(pattern_register, message) From 62cd074018a083d19f0d5d4d8f707749f4d19184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Tue, 11 Jul 2023 22:25:20 +0300 Subject: [PATCH 15/18] Wrap pass file deletion try except statement - if file is deleted from server, and we are updating pass. --- django_walletpass/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/django_walletpass/models.py b/django_walletpass/models.py index 0d11a06..74715f1 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -209,7 +209,10 @@ def write_to_model(self, instance=None): if self.builded_pass_content is None: raise ValueError(_("Cannot save to model: builded_pass_content is None.")) content = WalletpassContentFile(self.builded_pass_content) - instance.data.delete() + try: + instance.data.delete() + except FileNotFoundError: + pass instance.data.save(filename, content) return instance From 4daee238098a3ef080711bc0160de1865394621f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Tue, 11 Jul 2023 22:29:46 +0300 Subject: [PATCH 16/18] Revert "Wrap pass file deletion try except statement - if file is deleted from server, and we are updating pass." This reverts commit 62cd074018a083d19f0d5d4d8f707749f4d19184. --- django_walletpass/models.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/django_walletpass/models.py b/django_walletpass/models.py index 74715f1..0d11a06 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -209,10 +209,7 @@ def write_to_model(self, instance=None): if self.builded_pass_content is None: raise ValueError(_("Cannot save to model: builded_pass_content is None.")) content = WalletpassContentFile(self.builded_pass_content) - try: - instance.data.delete() - except FileNotFoundError: - pass + instance.data.delete() instance.data.save(filename, content) return instance From 21272210106f55e9195c139f4aa893d230b2941d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artu=CC=84rs=20Cirsis?= Date: Thu, 13 Jul 2023 13:42:42 +0300 Subject: [PATCH 17/18] fix value too long for type character varying(150) - received push token with 164 chars --- .../0010_alter_registration_push_token.py | 18 ++++++++++++++++++ django_walletpass/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 django_walletpass/migrations/0010_alter_registration_push_token.py diff --git a/django_walletpass/migrations/0010_alter_registration_push_token.py b/django_walletpass/migrations/0010_alter_registration_push_token.py new file mode 100644 index 0000000..9862c75 --- /dev/null +++ b/django_walletpass/migrations/0010_alter_registration_push_token.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.14 on 2023-07-13 10:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_walletpass', '0009_auto_20230709_2143'), + ] + + operations = [ + migrations.AlterField( + model_name='registration', + name='push_token', + field=models.CharField(max_length=255), + ), + ] diff --git a/django_walletpass/models.py b/django_walletpass/models.py index 0d11a06..d9c2087 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -304,7 +304,7 @@ class Registration(models.Model): Registration of a Pass on a device """ device_library_identifier = models.CharField(max_length=150) - push_token = models.CharField(max_length=150) + push_token = models.CharField(max_length=255) pazz = models.ForeignKey( Pass, on_delete=models.CASCADE, From 160a175db940357f94c0248067fc4bd0152be294 Mon Sep 17 00:00:00 2001 From: Jirka Schaefer Date: Mon, 22 Jan 2024 17:25:22 +0100 Subject: [PATCH 18/18] svg must be in img file --- django_walletpass/admin.py | 40 +++++++++----- .../static/admin/passbook_icon.svg | 3 ++ django_walletpass/tests/main.py | 53 +++++++++++++++---- 3 files changed, 74 insertions(+), 22 deletions(-) create mode 100644 django_walletpass/static/admin/passbook_icon.svg diff --git a/django_walletpass/admin.py b/django_walletpass/admin.py index 36dbb36..f9a53e9 100644 --- a/django_walletpass/admin.py +++ b/django_walletpass/admin.py @@ -1,9 +1,9 @@ from django.contrib import admin +from django.template import Context, Template from django.urls import reverse from django.utils.html import format_html -from django.utils.safestring import mark_safe -from django_walletpass.models import Pass, Registration, Log +from django_walletpass.models import Log, Pass, Registration @admin.register(Log) @@ -20,7 +20,13 @@ class LogAdmin(admin.ModelAdmin): "msg", ) list_filter = ("status", "task_type", "pass_type_identifier") - search_fields = ("pass_type_identifier", "serial_number", "device_id", "msg", "message") + search_fields = ( + "pass_type_identifier", + "serial_number", + "device_id", + "msg", + "message", + ) readonly_fields = ("created_at", "pass_") raw_id_fields = ("pazz",) list_select_related = ("pazz",) @@ -28,7 +34,8 @@ class LogAdmin(admin.ModelAdmin): def pass_(self, obj: Log): if obj.pazz_id: url = reverse( - "admin:%s_%s_change" % (obj.pazz._meta.app_label, obj.pazz._meta.model_name), + "admin:%s_%s_change" + % (obj.pazz._meta.app_label, obj.pazz._meta.model_name), args=[obj.pazz_id], ) return format_html( @@ -43,22 +50,30 @@ def pass_(self, obj: Log): @admin.register(Pass) class PassAdmin(admin.ModelAdmin): - list_display = ("serial_number", "updated_at", "pass_type_identifier", "wallet_pass_") - search_fields = ("serial_number", "pass_type_identifier", "authentication_token", "data") + list_display = ( + "serial_number", + "updated_at", + "pass_type_identifier", + "wallet_pass_", + ) + search_fields = ( + "serial_number", + "pass_type_identifier", + "authentication_token", + "data", + ) list_filter = ("pass_type_identifier", "updated_at") date_hierarchy = "updated_at" readonly_fields = ("wallet_pass_", "updated_at") def wallet_pass_(self, obj: Pass): if obj.data: - svg_icon = '' \ - '' \ - '' return format_html( - "{icon}", + Template( + "{% load static %}" + ).render(Context({})), url=obj.data.url, title=obj.data.name, - icon=mark_safe(svg_icon), ) return @@ -75,7 +90,8 @@ class RegistrationAdmin(admin.ModelAdmin): def pass_(self, obj: Registration): if obj.pazz_id: url = reverse( - "admin:%s_%s_change" % (obj.pazz._meta.app_label, obj.pazz._meta.model_name), + "admin:%s_%s_change" + % (obj.pazz._meta.app_label, obj.pazz._meta.model_name), args=[obj.pazz_id], ) return format_html( diff --git a/django_walletpass/static/admin/passbook_icon.svg b/django_walletpass/static/admin/passbook_icon.svg new file mode 100644 index 0000000..555054e --- /dev/null +++ b/django_walletpass/static/admin/passbook_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/django_walletpass/tests/main.py b/django_walletpass/tests/main.py index b774f78..d5e659e 100644 --- a/django_walletpass/tests/main.py +++ b/django_walletpass/tests/main.py @@ -1,31 +1,64 @@ from unittest import mock from dateutil.parser import parse +from django.contrib import admin from django.test import TestCase from django.utils import timezone + from django_walletpass import crypto +from django_walletpass.admin import PassAdmin from django_walletpass.classviews import FORMAT from django_walletpass.models import Pass, PassBuilder, Registration from django_walletpass.settings import dwpconfig as WALLETPASS_CONF -class ClassViewsTestCase(TestCase): +class AdminTestCase(TestCase): + def test_wallet_pass(self): + admin_view = PassAdmin(Pass, admin.site) + builder = PassBuilder() + builder.pass_data = { + "formatVersion": 1, + "barcode": { + "message": "123456789", + "format": "PKBarcodeFormatPDF417", + "messageEncoding": "iso-8859-1", + }, + "organizationName": "Organic Produce", + "description": "Organic Produce Loyalty Card", + "logoText": "Organic Produce", + "foregroundColor": "rgb(255, 255, 255)", + "backgroundColor": "rgb(55, 117, 50)", + } + + builder.build() + + instance = builder.write_to_model() + instance.save() + + self.assertEqual( + admin_view.wallet_pass_(instance)[-48:], + "", + ) + +class ClassViewsTestCase(TestCase): def test_format_parse(self): - """ ensure dateutil reads FORMAT properly """ + """ensure dateutil reads FORMAT properly""" now = timezone.now() now_string = now.strftime(FORMAT) - self.assertEqual(parse(now_string), timezone.make_naive(now).replace(microsecond=0)) + self.assertEqual( + parse(now_string), timezone.make_naive(now).replace(microsecond=0) + ) class CryptoTestCase(TestCase): def test_smime_sign(self): crypto.pkcs7_sign( - certcontent=WALLETPASS_CONF['CERT_CONTENT'], - keycontent=WALLETPASS_CONF['KEY_CONTENT'], - wwdr_certificate=WALLETPASS_CONF['WWDRCA_PEM_CONTENT'], - data=b'data to be signed', - key_password=WALLETPASS_CONF['KEY_PASSWORD'], + certcontent=WALLETPASS_CONF["CERT_CONTENT"], + keycontent=WALLETPASS_CONF["KEY_CONTENT"], + wwdr_certificate=WALLETPASS_CONF["WWDRCA_PEM_CONTENT"], + data=b"data to be signed", + key_password=WALLETPASS_CONF["KEY_PASSWORD"], ) @@ -37,7 +70,7 @@ def test_build_pkpass(self): "barcode": { "message": "123456789", "format": "PKBarcodeFormatPDF417", - "messageEncoding": "iso-8859-1" + "messageEncoding": "iso-8859-1", }, "organizationName": "Organic Produce", "description": "Organic Produce Loyalty Card", @@ -57,7 +90,7 @@ def test_build_pkpass(self): self.assertEqual(builder.manifest_dict, builder2.manifest_dict) self.assertEqual(builder.pass_data, builder2.pass_data) - builder2.pass_data.update({"organizationName": 'test'}) + builder2.pass_data.update({"organizationName": "test"}) builder2.build() builder2.write_to_model(instance) instance.save()