From 86b33b577af8cfad60371d5a391adb9c48400897 Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 14:09:23 +0100 Subject: [PATCH 01/25] rm garbage --- .../41871fd4-9005-11ed-aa4f-0242ac110002.pkpass | Bin 2809 -> 0 bytes .../7de54576-9016-11ed-9ce6-0242ac110002.pkpass | Bin 2811 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 passes/41871fd4-9005-11ed-aa4f-0242ac110002.pkpass delete mode 100644 passes/7de54576-9016-11ed-9ce6-0242ac110002.pkpass diff --git a/passes/41871fd4-9005-11ed-aa4f-0242ac110002.pkpass b/passes/41871fd4-9005-11ed-aa4f-0242ac110002.pkpass deleted file mode 100644 index 403dbd376ee4a40404c30a592fd3f18e15942fe8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2809 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 diff --git a/passes/7de54576-9016-11ed-9ce6-0242ac110002.pkpass b/passes/7de54576-9016-11ed-9ce6-0242ac110002.pkpass deleted file mode 100644 index 57f8313c8b223ec5419f72862a415bd258a7cfb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2811 zcmZ{mcQhPY6TnxuAxg4HNC-AqZ}rs)@m5(Iok+-&h+cLDi$qxoR*Mxio-RrVLi7@% zBzo^wi1PFrR#`RQ%6Z>A=jD9g+%q%x-kCXb&zV2&PajD|4Fa5tlqKZu&&}V4lI%sr z^#6|2|BR0~ylGV=-^-KrB3V(EXa`#xcZ{f`y9>JDuCNwVbhJyjx6i25P+t;;D1+%B zwWYMBWnhvpSzT#uX@rE7Ua6Lh_8lFVOqsYe41qvOn&i^}{@o?vF}B^40sv4YKLJSY z^2E~J{bvVMbQk)oIE%W!`b<8}wU632RZ1hc*gO`#l&pX(BJ;w!LkH=>H`{L4bO%xkY~)@iQ95S>?PbI8?HDfLqTJ)Z)93{Ru*O3O1fZ@<;xOi7EtR_ z;2gEQ@Wz~|GK~KE2kGf^4(LHHYH%acC&WBx)&JEj$AhgP0+WXz&1Tg*r|a)g-96TB zC0q>o0)qM%-ZRL!a@ubUqzarQy9*CApiio`<oEa-Cp7;aPF2FUv}QU0K!ZG)^WnHV*r6>k9&R@ACO%4WE8OW` zqK*$ZIi)lb8DEc{CSHvbFpD-v*A!x+#1mb)U^Zb9rg*#oqTDbfR_^A2VT?e62LvMN z;2QG*J)@F7yjqQ$*xY zU2%#Cx*^OY)c%Nyw9j%Gr=fO*I?7x?jpn;B_<6K4Ab2(U-WONt*Frk{5m24ORH_by zVMLxA;J3gXwXT_H3vOc%)z`G8J8-T=UR%;F(d6H^t!Yd6| zz0NX!+sa!PjXSe#>WUxYYebRUa^ff=sl6sYOGcN7=vZw_fObr zCm!_;hjBI??Zmyp2OCuhP#H6!|0Leo4(2YU9;ivF@PRw{}t8k$wPp~IOJIgTcV zTbM1x3_fEN<`bjT+F0kxsra>RwoiA*+wNAxPP&>JhPWkD`IP92X!tDaMB;5LMvBqG z>3IYj#mzMFebFv z#*|@ZPnY)arSzn(*b2W|b!1BQ*qG}Yw%9*rDuntp zt+sQ6(z&9^8w#`BJD5nK7aO(qer00E-o5-_*Riu?-|>E-*lgqw>2sxS&AL(Fnr!tc z1{t~Z$+2<3a6RUL@@#}L{o8h9#n+Msn>~31HQdum9LaamjcrN7C3Su#mI05eM4dCrMzmldi^@8&JZjMPt;Phssq|zXWF!Qu~@M->! z4%nX z5?Z1UyxZEH`r+j}2V5o}O!KuUR_3;o4m@x5Y^LF=0J;kJkPLMgZ3dVpLzJexAFMhL zSyQq7nkpak2T26_c{zFRs^U`i$XTP7w!Wqd^@M7HYL;rMo~>(*fq4Wz@NqMKAK@}0 z6nfe68b51dG&QADwr|QVvwhh7p{jv~=gM3_fK3z&CuHC#J*Qz;yV~>4(J*IS0ln<4 zL(X(d+L6j&PVs~c~*h7%zGn9PlmCX4sNe)_MlT#f%>-ZkB9*F z`@IH&9_BiW4!OvDClmg<>6R^BA?%Ng z=$U#AGh)aVZEV+ST1xGcg$c~z$)blmuPVwkx>!!(;wRR~Siw9aOR(8o0DagWl&ue? zbH$w9aYO9jC5l0wT)eu0se+ikd0LacE3%k^?N1|z#uW4?x+`LRneUS9!#C=iiGu7O z8H^qrZED!tm({Akbhdhw@|CdgQ-qrVo)6Fkl*?!DCuLpIa(SlpfSa%30|Shamzp0I z(|^$YUSVK9KGeGj7#DIDHkoYgwOGA($K!taX2oV^)p4@YfpZ>^0Ttn=b)BTJ<(%+g zEw^HCo<*(6T0^UKp>EO&1uS;$Er3pH#zvF)>%PcNkMt$RY?RIrK^A52?9aZO#8&Hx zI)Vc)@Hb2>swOoMkd;Gm%SR308ji4Qbe1RmQ^o-;MZm;4j&ubH+)&S9njH7+5=NvU zC`{K&q}*x%_q-baiipy@=*O@rEdMs#q<4IgTbXISS)j6{3g+u>^|-(K^L4wy81IFQ zWu30$JaizDL%p8Sm+uf<-!c02vz&l|C^#SOCk7c2f+`YZC0^M6YAi(s<<2LOL9 h`xQq1t|E8jC$r-}Xrhm#p*;^#lNW*P884il{sZv$27CYj From 5ba65917b9cfafb15ad9e741b42f2949d134ca73 Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 14:38:03 +0100 Subject: [PATCH 02/25] linter, test 1 --- django_walletpass/models.py | 60 ++++++++++++++++------------------- django_walletpass/settings.py | 25 +++++++++------ setup.py | 4 ++- 3 files changed, 47 insertions(+), 42 deletions(-) diff --git a/django_walletpass/models.py b/django_walletpass/models.py index d1b78ba..4c5dacf 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -8,10 +8,8 @@ from glob import glob from django.core.exceptions import ValidationError from django.utils.module_loading import import_string -from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ -from django.utils import timezone from django_walletpass import crypto from django_walletpass.storage import WalletPassStorage from django_walletpass.files import WalletpassContentFile @@ -54,16 +52,16 @@ def _copy_dir_files(self, tmp_pass_dir): continue if not os.path.isfile(absolute_filepath): continue - filecontent = open(absolute_filepath, 'rb').read() - # Add files to manifest - self.manifest_dict[relative_file_path] = hashlib.sha1(filecontent).hexdigest() - dest_abs_filepath = os.path.join(tmp_pass_dir, relative_file_path) - dest_abs_dirpath = os.path.dirname(dest_abs_filepath) - if not os.path.exists(dest_abs_dirpath): - os.makedirs(dest_abs_dirpath) - ff = open(dest_abs_filepath, 'wb') - ff.write(filecontent) - ff.close() + with open(absolute_filepath, 'rb') as file: + filecontent = file.read() + # Add files to manifest + self.manifest_dict[relative_file_path] = hashlib.sha1(filecontent).hexdigest() + dest_abs_filepath = os.path.join(tmp_pass_dir, relative_file_path) + dest_abs_dirpath = os.path.dirname(dest_abs_filepath) + if not os.path.exists(dest_abs_dirpath): + os.makedirs(dest_abs_dirpath) + with open(dest_abs_filepath, 'wb') as ffile: + ffile.write(filecontent) def _write_extra_files(self, tmp_pass_dir): """Write extra files contained in self.extra_files into tmp dir @@ -78,9 +76,8 @@ def _write_extra_files(self, tmp_pass_dir): dest_abs_dirpath = os.path.dirname(dest_abs_filepath) if not os.path.exists(dest_abs_dirpath): os.makedirs(dest_abs_dirpath) - ff = open(dest_abs_filepath, 'wb') - ff.write(filecontent) - ff.close() + with open(dest_abs_filepath, 'wb') as ffile: + ffile.write(filecontent) def _write_pass_json(self, tmp_pass_dir): """Write content of self.pass_data to pass.json (in JSON format) @@ -92,9 +89,8 @@ def _write_pass_json(self, tmp_pass_dir): pass_json_bytes = bytes(pass_json, 'utf8') # Add pass.json to manifest self.manifest_dict['pass.json'] = hashlib.sha1(pass_json_bytes).hexdigest() - ff = open(os.path.join(tmp_pass_dir, 'pass.json'), 'wb') - ff.write(pass_json_bytes) - ff.close() + with open(os.path.join(tmp_pass_dir, 'pass.json'), 'wb') as ffile: + ffile.write(pass_json_bytes) def _write_manifest_json_and_signature(self, tmp_pass_dir): """Write the content of self.manifest_dict into manifest.json @@ -104,9 +100,8 @@ def _write_manifest_json_and_signature(self, tmp_pass_dir): """ manifest_json = json.dumps(self.manifest_dict) manifest_json_bytes = bytes(manifest_json, 'utf8') - ff = open(os.path.join(tmp_pass_dir, 'manifest.json'), 'wb') - ff.write(manifest_json_bytes) - ff.close() + with open(os.path.join(tmp_pass_dir, 'manifest.json'), 'wb') as ffile: + ffile.write(manifest_json_bytes) signature_content = crypto.pkcs7_sign( certcontent=WALLETPASS_CONF['CERT_CONTENT'], keycontent=WALLETPASS_CONF['KEY_CONTENT'], @@ -114,9 +109,8 @@ def _write_manifest_json_and_signature(self, tmp_pass_dir): data=manifest_json_bytes, key_password=WALLETPASS_CONF['KEY_PASSWORD'], ) - ff = open(os.path.join(tmp_pass_dir, 'signature'), 'wb') - ff.write(signature_content) - ff.close() + with open(os.path.join(tmp_pass_dir, 'signature'), 'wb') as ffile: + ffile.write(signature_content) def _zip_all(self, directory): zip_file_path = os.path.join(directory, '..', 'walletcard.pkpass') @@ -125,7 +119,8 @@ def _zip_all(self, directory): relative_file_path = os.path.relpath(filepath, directory) zip_pkpass.write(filepath, arcname=relative_file_path) zip_pkpass.close() - return open(zip_file_path, 'rb').read() + with open(zip_file_path, 'rb') as ffile: + return ffile.read() def _load_pass_json_file_if_exists(self, directory): """Call self.load_pass_json_file if pass.json exist @@ -162,8 +157,9 @@ def load_pass_json_file(self, dir): Args: dir (str): path where resides the pass.json """ - json_data = open(os.path.join(dir, 'pass.json'), 'r').read() - self.pass_data = json.loads(json_data) + with open(os.path.join(dir, 'pass.json'), 'r') as ffile: + json_data = ffile.read() + self.pass_data = json.loads(json_data) def pre_build_pass_data(self): """Update self.pass_data with self.pass_data_required content @@ -255,9 +251,8 @@ def get_pass_builder(self): tmp_pass_dir = os.path.join(tmpdirname, 'data.pass') # Put zip file into tmp dir zip_path = os.path.join(tmpdirname, 'walletcard.pkpass') - zip_pkpass = open(zip_path, 'wb') - zip_pkpass.write(self.data.read()) - zip_pkpass.close() + with open(zip_path, 'wb') as zip_pkpass: + zip_pkpass.write(self.data.read()) # Extract zip file to tmp dir with zipfile.ZipFile(zip_path, "r") as zip_ref: zip_ref.extractall(tmp_pass_dir) @@ -272,7 +267,8 @@ def get_pass_builder(self): continue if not os.path.isfile(filepath): continue - builder.add_file(relative_file_path, open(filepath, 'rb').read()) + with open(filepath, 'rb') as ffile: + builder.add_file(relative_file_path, ffile.read()) # Load of these fields due to that those fields are ignored # on pass.json loading builder.pass_data_required.update({ @@ -290,7 +286,7 @@ class Meta: unique_together = ( 'pass_type_identifier', 'serial_number', - ), + ) class Registration(models.Model): diff --git a/django_walletpass/settings.py b/django_walletpass/settings.py index 00b45c6..657ad30 100644 --- a/django_walletpass/settings.py +++ b/django_walletpass/settings.py @@ -4,6 +4,13 @@ from django.test.signals import setting_changed FULL_BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +with open(os.path.join(FULL_BASE_DIR, 'certs', 'AppleWWDRCA.pem'), 'rb') as ffile: + wwdrca_pem_content = ffile.read() + +with open(os.path.join(FULL_BASE_DIR, 'certs', 'AppleWWDRCA.cer'), 'rb') as ffile: + wwdrca_content = ffile.read() + DEFAULTS = { 'PUSH_AUTH_STRATEGY': 'legacy', # legacy or token 'TOKEN_AUTH_KEY_PATH': None, @@ -14,13 +21,9 @@ 'KEY_PATH': None, 'KEY_PASSWORD': None, 'APPLE_WWDRCA_CERT_PATH': os.path.join(FULL_BASE_DIR, 'certs', 'AppleWWDRCA.cer'), - 'WWDRCA_CONTENT': open( - os.path.join(FULL_BASE_DIR, 'certs', 'AppleWWDRCA.cer'), 'rb' - ).read(), + 'WWDRCA_CONTENT': wwdrca_content, 'APPLE_WWDRCA_PEM_PATH': os.path.join(FULL_BASE_DIR, 'certs', 'AppleWWDRCA.pem'), - 'WWDRCA_PEM_CONTENT': open( - os.path.join(FULL_BASE_DIR, 'certs', 'AppleWWDRCA.pem'), 'rb' - ).read(), + 'WWDRCA_PEM_CONTENT': wwdrca_pem_content, 'PASS_TYPE_ID': None, 'TEAM_ID': None, 'SERVICE_URL': None, @@ -33,15 +36,19 @@ class ConfigManager(UserDict): + def update_conf(self): self.data = DEFAULTS new = django_settings.WALLETPASS if 'CERT_PATH' in new: - new['CERT_CONTENT'] = open(new['CERT_PATH'], 'rb').read() + with open(new['CERT_PATH'], 'rb') as ffile: + new['CERT_CONTENT'] = ffile.read() if 'KEY_PATH' in new: - new['KEY_CONTENT'] = open(new['KEY_PATH'], 'rb').read() + with open(new['KEY_PATH'], 'rb') as ffile: + new['KEY_CONTENT'] = ffile.read() if 'APPLE_WWDRCA_PEM_PATH' in new: - new['WWDRCA_PEM_CONTENT'] = open(new['APPLE_WWDRCA_PEM_PATH'], 'rb').read() + with open(new['APPLE_WWDRCA_PEM_PATH'], 'rb') as ffile: + new['WWDRCA_PEM_CONTENT'] = ffile.read() self.data.update(new) diff --git a/setup.py b/setup.py index c09ddbd..689506f 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,9 @@ from setuptools import find_packages, setup here = os.path.abspath(os.path.dirname(__file__)) -README = open(os.path.join(here, 'README.md')).read() + +with open(os.path.join(here, 'README.md')) as ffile: + README = ffile.read() setup( name='django-walletpass', From 11b56a53aec072928a7756998af5a823ac99de23 Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 14:46:57 +0100 Subject: [PATCH 03/25] linter, test 2 --- django_walletpass/classviews.py | 6 +++--- django_walletpass/models.py | 17 ++++++++--------- django_walletpass/settings.py | 10 +++++----- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/django_walletpass/classviews.py b/django_walletpass/classviews.py index 5adcdc5..da2872b 100644 --- a/django_walletpass/classviews.py +++ b/django_walletpass/classviews.py @@ -61,7 +61,7 @@ class RegisterPassViewSet(viewsets.ViewSet): def create(self, request, device_library_id, pass_type_id, serial_number): pass_ = get_pass(pass_type_id, serial_number) - if request.META.get('HTTP_AUTHORIZATION') != 'ApplePass %s' % pass_.authentication_token: + if request.META.get('HTTP_AUTHORIZATION') != f"ApplePass {pass_.authentication_token}": return Response({}, status=status.HTTP_401_UNAUTHORIZED) registration = Registration.objects.filter( device_library_identifier=device_library_id, @@ -81,7 +81,7 @@ def create(self, request, device_library_id, pass_type_id, serial_number): def destroy(self, request, device_library_id, pass_type_id, serial_number): pass_ = get_pass(pass_type_id, serial_number) - if request.META.get('HTTP_AUTHORIZATION') != 'ApplePass %s' % pass_.authentication_token: + if request.META.get('HTTP_AUTHORIZATION') != f"ApplePass {pass_.authentication_token}": return Response({}, status=status.HTTP_401_UNAUTHORIZED) registration = Registration.objects.filter( device_library_identifier=device_library_id, @@ -103,7 +103,7 @@ class LatestVersionViewSet(viewsets.ViewSet): def retrieve(self, request, pass_type_id, serial_number): pass_ = get_pass(pass_type_id, serial_number) - if request.META.get('HTTP_AUTHORIZATION') != 'ApplePass %s' % pass_.authentication_token: + if request.META.get('HTTP_AUTHORIZATION') != f"ApplePass {pass_.authentication_token}": return Response({}, status=status.HTTP_401_UNAUTHORIZED) if WALLETPASS_CONF['STORAGE_HTTP_REDIRECT']: diff --git a/django_walletpass/models.py b/django_walletpass/models.py index 4c5dacf..fecf242 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -114,13 +114,12 @@ def _write_manifest_json_and_signature(self, tmp_pass_dir): def _zip_all(self, directory): zip_file_path = os.path.join(directory, '..', 'walletcard.pkpass') - zip_pkpass = zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) - for filepath in glob(os.path.join(directory, '**'), recursive=True): - relative_file_path = os.path.relpath(filepath, directory) - zip_pkpass.write(filepath, arcname=relative_file_path) - zip_pkpass.close() - with open(zip_file_path, 'rb') as ffile: - return ffile.read() + with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zip_pkpass: + for filepath in glob(os.path.join(directory, '**'), recursive=True): + relative_file_path = os.path.relpath(filepath, directory) + zip_pkpass.write(filepath, arcname=relative_file_path) + with open(zip_file_path, 'rb') as ffile: + return ffile.read() def _load_pass_json_file_if_exists(self, directory): """Call self.load_pass_json_file if pass.json exist @@ -151,13 +150,13 @@ def clean(self): self._clean_builded_pass_content() self.validate() - def load_pass_json_file(self, dir): + def load_pass_json_file(self, directory): """Load json file without test if exists. Args: dir (str): path where resides the pass.json """ - with open(os.path.join(dir, 'pass.json'), 'r') as ffile: + with open(os.path.join(directory, 'pass.json'), 'r') as ffile: json_data = ffile.read() self.pass_data = json.loads(json_data) diff --git a/django_walletpass/settings.py b/django_walletpass/settings.py index 657ad30..a1fa0c5 100644 --- a/django_walletpass/settings.py +++ b/django_walletpass/settings.py @@ -5,11 +5,11 @@ FULL_BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -with open(os.path.join(FULL_BASE_DIR, 'certs', 'AppleWWDRCA.pem'), 'rb') as ffile: - wwdrca_pem_content = ffile.read() +with open(os.path.join(FULL_BASE_DIR, 'certs', 'AppleWWDRCA.pem'), 'rb') as _ffile: + wwdrca_pem_content = _ffile.read() -with open(os.path.join(FULL_BASE_DIR, 'certs', 'AppleWWDRCA.cer'), 'rb') as ffile: - wwdrca_content = ffile.read() +with open(os.path.join(FULL_BASE_DIR, 'certs', 'AppleWWDRCA.cer'), 'rb') as _ffile: + wwdrca_content = _ffile.read() DEFAULTS = { 'PUSH_AUTH_STRATEGY': 'legacy', # legacy or token @@ -57,7 +57,7 @@ def update_conf(self): dwpconfig.update_conf() -def update_conf(*args, **kwargs): +def update_conf(*args, **kwargs): # pylint: disable=unused-argument if kwargs['setting'] == 'WALLETPASS': dwpconfig.update_conf() From b3a61e74f0d7a227099bdf5133a8a63a8c47f49c Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 14:52:55 +0100 Subject: [PATCH 04/25] linter, test 3 --- django_walletpass/models.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django_walletpass/models.py b/django_walletpass/models.py index fecf242..04ab395 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -156,7 +156,7 @@ def load_pass_json_file(self, directory): Args: dir (str): path where resides the pass.json """ - with open(os.path.join(directory, 'pass.json'), 'r') as ffile: + with open(os.path.join(directory, 'pass.json'), 'rb') as ffile: json_data = ffile.read() self.pass_data = json.loads(json_data) diff --git a/setup.py b/setup.py index 689506f..293b968 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ here = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(here, 'README.md')) as ffile: +with open(os.path.join(here, 'README.md'), 'r', encoding='utf-8') as ffile: README = ffile.read() setup( From ef75947a2c31fd9c61463090a3b9255f97a4df5c Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 15:17:31 +0100 Subject: [PATCH 05/25] linter, test 4 --- .pylintrc | 6 +++--- django_walletpass/apps.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pylintrc b/.pylintrc index d8bd739..21038f4 100644 --- a/.pylintrc +++ b/.pylintrc @@ -5,9 +5,9 @@ # paths. ignore=migrations,docs -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= +# Add files or directories matching the regex patterns to the ignore-list. + # The regex matches against paths. +ignore-paths=example, django_walletpass/migrations jobs=0 diff --git a/django_walletpass/apps.py b/django_walletpass/apps.py index 0687203..a715e0f 100644 --- a/django_walletpass/apps.py +++ b/django_walletpass/apps.py @@ -5,4 +5,4 @@ class DjangoWalletpassConfig(AppConfig): name = 'django_walletpass' def ready(self): - from django_walletpass import signals as _signals + from django_walletpass import signals as _signals # pylint: disable=import-outside-toplevel From 55446e39e94f2787f92761a4d6ccdd25d8668fcd Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 15:23:23 +0100 Subject: [PATCH 06/25] unit test workflow, aproach 1 --- .github/workflows/django.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/django.yml diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml new file mode 100644 index 0000000..ff4e198 --- /dev/null +++ b/.github/workflows/django.yml @@ -0,0 +1,30 @@ +name: Django CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master", "develop" ] + +jobs: + tests: + + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: [3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + python setup.py install + - name: Run Tests + run: | + python ./example/manage.py test django_walletpass \ No newline at end of file From 026c7d4825f9c77b97e73b37f19ea61f6a08826d Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 15:24:38 +0100 Subject: [PATCH 07/25] unit test workflow, aproach 2 --- .github/workflows/django.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index ff4e198..bd26ab7 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -2,7 +2,7 @@ name: Django CI on: push: - branches: [ "master" ] + branches: [ "master", "develop" ] pull_request: branches: [ "master", "develop" ] From c6e039808294ecfce4ca9729930f3bef4abfc71f Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 17:30:51 +0100 Subject: [PATCH 08/25] fix doc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e840c54..1bfde54 100644 --- a/README.md +++ b/README.md @@ -284,5 +284,5 @@ builder.save_to_db(pass_instance) Checkout source and run from source root directory ``` -docker run -it --rm -v "$(pwd):/app" python:3.8 bash -c "cd /app; python setup.py install; ./example/manage.py test django_walletpass +docker run -it --rm -v "$(pwd):/app" python:3.8 bash -c "cd /app; python setup.py install; ./example/manage.py test django_walletpass" ``` From 85055e784b9cab083abfe5742993592dbfc469db Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 17:31:05 +0100 Subject: [PATCH 09/25] rm package from example --- example/django_walletpass | 1 - 1 file changed, 1 deletion(-) delete mode 120000 example/django_walletpass diff --git a/example/django_walletpass b/example/django_walletpass deleted file mode 120000 index b553199..0000000 --- a/example/django_walletpass +++ /dev/null @@ -1 +0,0 @@ -../django_walletpass \ No newline at end of file From 55388c0adb2e5efc5d493f9da5aeab897498a9e6 Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 17:59:57 +0100 Subject: [PATCH 10/25] ignore passes --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d125db3..ecf651d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ nosetests.xml .pydevproject /example/db.sqlite3 /example/passes +/passes \ No newline at end of file From c66e4171c224d0ed6bb5a58bac2bff79546039c6 Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 18:00:26 +0100 Subject: [PATCH 11/25] fixes unit test workflow --- django_walletpass/models.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/django_walletpass/models.py b/django_walletpass/models.py index 04ab395..ee3af0a 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -118,8 +118,8 @@ def _zip_all(self, directory): for filepath in glob(os.path.join(directory, '**'), recursive=True): relative_file_path = os.path.relpath(filepath, directory) zip_pkpass.write(filepath, arcname=relative_file_path) - with open(zip_file_path, 'rb') as ffile: - return ffile.read() + with open(zip_file_path, 'rb') as ffile: + return ffile.read() def _load_pass_json_file_if_exists(self, directory): """Call self.load_pass_json_file if pass.json exist @@ -250,8 +250,10 @@ def get_pass_builder(self): tmp_pass_dir = os.path.join(tmpdirname, 'data.pass') # Put zip file into tmp dir zip_path = os.path.join(tmpdirname, 'walletcard.pkpass') - with open(zip_path, 'wb') as zip_pkpass: - zip_pkpass.write(self.data.read()) + with open(zip_path, 'wb') as ffile: + self.data.seek(0) + ffile.write(self.data.read()) + # Extract zip file to tmp dir with zipfile.ZipFile(zip_path, "r") as zip_ref: zip_ref.extractall(tmp_pass_dir) From 3e1e0dc2a53038286fa966ae57665322fdfb1fff Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 18:05:47 +0100 Subject: [PATCH 12/25] python >= 3.9 for lint and testing --- .github/workflows/django.yml | 2 +- .github/workflows/pylint.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index bd26ab7..ec6f80f 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.7, 3.8, 3.9] + python-version: [3.9, 3.10] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 6da5857..26a95c0 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.9", "3.10"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} From 6011c3d471665d7481681f2f76f40568bb994e2d Mon Sep 17 00:00:00 2001 From: Nache Date: Mon, 9 Jan 2023 18:07:56 +0100 Subject: [PATCH 13/25] workflow, string version to handle .10 --- .github/workflows/django.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index ec6f80f..bce48e6 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -13,7 +13,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.9, 3.10] + python-version: ["3.9", "3.10"] steps: - uses: actions/checkout@v3 From 4de65ef6629c66168d1d24f2367c9308a34dc6c9 Mon Sep 17 00:00:00 2001 From: Jirka Schaefer Date: Tue, 10 Jan 2023 21:05:59 +0100 Subject: [PATCH 14/25] doc re signals must match code --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1bfde54..3d09979 100644 --- a/README.md +++ b/README.md @@ -90,13 +90,13 @@ django-walletpass signals certain events that might come handy in your application. ``` -from django_walletpass.views import pass_registered, pass_unregistered +from django_walletpass.classviews import PASS_REGISTERED, PASS_UNREGISTERED -@receiver(pass_registered) +@receiver(PASS_REGISTERED) def pass_registered(sender, **kwargs): pass -@receiver(pass_unregistered) +@receiver(PASS_UNREGISTERED) def pass_unregistered(sender, **kwargs): pass ``` From 88dba00d44ce61e063d7c44bc6654c1615672ac4 Mon Sep 17 00:00:00 2001 From: Jirka Schaefer Date: Tue, 10 Jan 2023 21:39:46 +0100 Subject: [PATCH 15/25] maintain timezone offset through apple update requests --- django_walletpass/classviews.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_walletpass/classviews.py b/django_walletpass/classviews.py index da2872b..aec1cbf 100644 --- a/django_walletpass/classviews.py +++ b/django_walletpass/classviews.py @@ -13,7 +13,7 @@ from django_walletpass.models import Pass, Registration, Log from django_walletpass.settings import dwpconfig as WALLETPASS_CONF -FORMAT = '%Y-%m-%d %H:%M:%S' +FORMAT = '%Y-%m-%d %H:%M:%S%z' PASS_REGISTERED = django.dispatch.Signal() PASS_UNREGISTERED = django.dispatch.Signal() From 32e0e93f18075de5f27ba2993eeb3cba12f8961c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jirka=20Sch=C3=A4fer?= Date: Thu, 25 May 2023 15:58:04 +0200 Subject: [PATCH 16/25] docs must reflect settings change (#17) --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3d09979..bee37d3 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ $ pip install django-walletpass Add 'django_walletpass' to you installed apps in the settings.py file. -Load the content of your cert.pem and key.pem in your settings.py file. +Load the content of your cert.pem and key.pem in your settings.py file. This is required +for signing the .pkpass file. ``` @@ -65,7 +66,7 @@ WALLETPASS = { } ``` -If you plan to use token JWT auth instead key/cert, use: +Add token JWT config data to allow APNs push: ``` WALLETPASS = { From 11e718a9d682e7222a2d543daacbc8d52376f7aa Mon Sep 17 00:00:00 2001 From: Nache Date: Thu, 25 May 2023 16:06:56 +0200 Subject: [PATCH 17/25] readme --- README.md | 56 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index bee37d3..e65e5b3 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ This application implements the creation of **signed .pkpass** files and ## Requirements -- Django 2.* -- Python >= 3.5 +- Django 2.*, 3.*, 4.* +- Python >= 3.6 - pyca/cryptography (for .pkpass SMIME sign) - djangorestframework >= 3.8 @@ -39,8 +39,8 @@ Add 'django_walletpass' to you installed apps in the settings.py file. Load the content of your cert.pem and key.pem in your settings.py file. This is required for signing the .pkpass file. -``` +```python WALLETPASS = { 'CERT_PATH': 'path/to/your/cert.pem', 'KEY_PATH': 'path/to/your/key.pem', @@ -52,7 +52,7 @@ WALLETPASS = { Add extra needed conf to your settings.py file. -``` +```python WALLETPASS = { 'CERT_PATH': 'path/to/your/cert.pem', 'KEY_PATH': 'path/to/your/key.pem', @@ -68,7 +68,7 @@ WALLETPASS = { Add token JWT config data to allow APNs push: -``` +```python WALLETPASS = { 'PUSH_AUTH_STRATEGY': 'token', 'TOKEN_AUTH_KEY_PATH': 'path/to/your/key.p8', @@ -82,7 +82,7 @@ WALLETPASS = { You should also import the urls into your site urls. -``` +```python urlpatterns = [ url(r'^api/passes/', include('django_walletpass.urls')), ``` @@ -90,7 +90,7 @@ urlpatterns = [ django-walletpass signals certain events that might come handy in your application. -``` +```python from django_walletpass.classviews import PASS_REGISTERED, PASS_UNREGISTERED @receiver(PASS_REGISTERED) @@ -107,7 +107,7 @@ def pass_unregistered(sender, **kwargs): Default: DEFAULT_FILE_STORAGE -``` +```python WALLETPASS_CONF = { # Defaults to DEFAULT_FILE_STORAGE 'STORAGE_CLASS': 'my.custom.storageclass, @@ -119,7 +119,7 @@ WALLETPASS_CONF = { Default: False -``` +```python WALLETPASS_CONF = { 'PUSH_SANDBOX': False, } @@ -127,7 +127,7 @@ WALLETPASS_CONF = { ### CA certificates path (optional) -``` +```python WALLETPASS_CONF = { # Cert in pem format. 'APPLE_WWDRCA_PEM_PATH': 'path/to/cert.pem', @@ -140,7 +140,7 @@ files from `s3`. Default: False -``` +```python WALLETPASS_CONF = { STORAGE_HTTP_REDIRECT: True, } @@ -153,15 +153,15 @@ WALLETPASS_CONF = { Init empty builder -``` -from django_walletpass.models import PassBuilder +```python +from django_walletpass.models import PassBuilder builder = PassBuilder() ``` Init builder usign a directory as base -``` -from django_walletpass.models import PassBuilder +```python +from django_walletpass.models import PassBuilder builder = PassBuilder(directory='/path/to/your.pass/') ``` @@ -169,7 +169,7 @@ If the base directory contains a `pass.json` it will be loaded, but remember that required attributes of `pass.json` will be overwritten during build process using this values: -``` +```python { "passTypeIdentifier": WALLETPASS_CONF['PASS_TYPE_ID'], "serialNumber": secrets.token_urlsafe(20), @@ -187,7 +187,7 @@ can manage it like a normal python dictionary. Update some attrs: -``` +```python builder.pass_data.update({ "barcode": { "message": "123456789", @@ -201,13 +201,13 @@ builder.pass_data.update({ Update one attr: -``` +```python builder.pass_data['description'] = "Organic Produce Loyalty Card" ``` ### Overwrite automatically generated required attribute values -``` +```python builder.pass_data_required.update({ "passTypeIdentifier": "customvalue", "serialNumber": "customvalue", @@ -220,7 +220,7 @@ builder.pass_data_required.update({ you can overwrite individual attributes: -``` +```python builder.pass_data_required.update({ "serialNumber": "customvalue", }) @@ -229,14 +229,14 @@ builder.pass_data_required['serialNumber] = 'cutomvalue' ### Add extra files -``` +```python file_content = open('myfile', 'rb').read() builder.add_file('image.png', file_content) ``` You can also add files to directories: -``` +```python file_content = open('myfile', 'rb').read() builder.add_file('en.lproj/pass.strings', file_content) ``` @@ -246,34 +246,34 @@ builder.add_file('en.lproj/pass.strings', file_content) Build the content of .pkpass -``` +```python pkpass_content = builder.build() ``` Write to file: -``` +```python pkpass_file = open('mypass.pkpass', 'rb') pkpass_file.write(pkpass_content) ``` Save to new record in DB: -``` +```python pass_instance = builder.write_to_model() pass_instance.save() ``` Save to existent record in DB: -``` +```python builder.write_to_model(pass_instance) pass_instance.save() ``` ### Load .pkpass from DB and update -``` +```python builder = pass_instance.get_pass_builder() builder.pass_data.update({'field': 'value'}) builder.build() @@ -284,6 +284,6 @@ builder.save_to_db(pass_instance) Checkout source and run from source root directory -``` +```bash docker run -it --rm -v "$(pwd):/app" python:3.8 bash -c "cd /app; python setup.py install; ./example/manage.py test django_walletpass" ``` From 95a6c9720427039ed741818cd247128efaa43e86 Mon Sep 17 00:00:00 2001 From: Jirka Schaefer Date: Thu, 25 May 2023 17:03:28 +0200 Subject: [PATCH 18/25] get linter some love --- django_walletpass/classviews.py | 28 +++++++++++++++------------- django_walletpass/models.py | 2 +- django_walletpass/services.py | 2 +- django_walletpass/tests/main.py | 13 +++++++------ 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/django_walletpass/classviews.py b/django_walletpass/classviews.py index 3897f03..069fa56 100644 --- a/django_walletpass/classviews.py +++ b/django_walletpass/classviews.py @@ -1,18 +1,20 @@ import json from calendar import timegm + +from pytz.exceptions import NonExistentTimeError + +import django.dispatch +from dateutil.parser import parse +from django.db.models import Max from django.http import HttpResponse -from django.utils.http import http_date from django.middleware.http import ConditionalGetMiddleware -from django.db.models import Max -import django.dispatch from django.shortcuts import get_object_or_404 -from rest_framework import viewsets, status -from rest_framework.response import Response -from rest_framework.permissions import AllowAny -from django_walletpass.models import Pass, Registration, Log +from django.utils.http import http_date +from django_walletpass.models import Log, Pass, Registration from django_walletpass.settings import dwpconfig as WALLETPASS_CONF -from pytz.exceptions import NonExistentTimeError -from dateutil.parser import parse +from rest_framework import status, viewsets +from rest_framework.permissions import AllowAny +from rest_framework.response import Response # legacy constant, remove when it can be assumed no timestamps of this format are out # there anymore @@ -43,11 +45,11 @@ def list(self, request, device_library_id, pass_type_id): try: # must be able to read UTC isoformat with TZ and FORMAT datetime string # as well - dt = parse(request.GET['passesUpdatedSince']) - passes = passes.filter(updated_at__gt=dt) + date = parse(request.GET['passesUpdatedSince']) + passes = passes.filter(updated_at__gt=date) except NonExistentTimeError: - dt = dt.replace(hour=0, minute=0) - passes = passes.filter(updated_at__gt=dt) + date = date.replace(hour=0, minute=0) + passes = passes.filter(updated_at__gt=date) if passes: last_updated = passes.aggregate(Max('updated_at'))['updated_at__max'] diff --git a/django_walletpass/models.py b/django_walletpass/models.py index 00b1036..1157d35 100644 --- a/django_walletpass/models.py +++ b/django_walletpass/models.py @@ -229,7 +229,7 @@ class Pass(models.Model): updated_at = models.DateTimeField(auto_now=True) def get_registrations(self): - return self.registrations.all() + return self.registrations.all() def push_notification(self): klass = import_string(WALLETPASS_CONF['WALLETPASS_PUSH_CLASS']) diff --git a/django_walletpass/services.py b/django_walletpass/services.py index 124f1de..092f222 100644 --- a/django_walletpass/services.py +++ b/django_walletpass/services.py @@ -3,7 +3,7 @@ from ssl import SSLError from aioapns import APNs, NotificationRequest -from aioapns.exceptions import ConnectionClosed, ConnectionError +from aioapns.exceptions import ConnectionClosed from django_walletpass.models import Registration from django_walletpass.settings import dwpconfig as WALLETPASS_CONF diff --git a/django_walletpass/tests/main.py b/django_walletpass/tests/main.py index 3837dcc..c7d34e1 100644 --- a/django_walletpass/tests/main.py +++ b/django_walletpass/tests/main.py @@ -1,20 +1,21 @@ from unittest import mock + +from dateutil.parser import parse from django.test import TestCase +from django.utils import timezone from django_walletpass import crypto +from django_walletpass.classviews import FORMAT from django_walletpass.models import Pass, PassBuilder, Registration from django_walletpass.settings import dwpconfig as WALLETPASS_CONF -from dateutil.parser import parse -from django.utils import timezone -from django_walletpass.classviews import FORMAT class ClassViewsTestCase(TestCase): def test_format_parse(self): """ ensure dateutil reads FORMAT properly """ - d = timezone.now() - s = d.strftime(FORMAT) - self.assertEqual(parse(s), timezone.make_naive(d).replace(microsecond=0)) + now = timezone.now() + now_string = d.strftime(FORMAT) + self.assertEqual(parse(now_string), timezone.make_naive(now).replace(microsecond=0)) class CryptoTestCase(TestCase): From c137a96f7f006ff4df88a9f6f9dd240eb749ab56 Mon Sep 17 00:00:00 2001 From: Jirka Schaefer Date: Thu, 25 May 2023 17:07:06 +0200 Subject: [PATCH 19/25] linter love test fix 2 --- django_walletpass/tests/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_walletpass/tests/main.py b/django_walletpass/tests/main.py index c7d34e1..b774f78 100644 --- a/django_walletpass/tests/main.py +++ b/django_walletpass/tests/main.py @@ -14,7 +14,7 @@ class ClassViewsTestCase(TestCase): def test_format_parse(self): """ ensure dateutil reads FORMAT properly """ now = timezone.now() - now_string = d.strftime(FORMAT) + now_string = now.strftime(FORMAT) self.assertEqual(parse(now_string), timezone.make_naive(now).replace(microsecond=0)) From 9bb042ba4b9435b2e296877acf8d6ea8d13a35b4 Mon Sep 17 00:00:00 2001 From: Jirka Schaefer Date: Thu, 25 May 2023 17:13:51 +0200 Subject: [PATCH 20/25] linter love 3 --- django_walletpass/classviews.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django_walletpass/classviews.py b/django_walletpass/classviews.py index 069fa56..7c094a2 100644 --- a/django_walletpass/classviews.py +++ b/django_walletpass/classviews.py @@ -10,11 +10,11 @@ from django.middleware.http import ConditionalGetMiddleware from django.shortcuts import get_object_or_404 from django.utils.http import http_date -from django_walletpass.models import Log, Pass, Registration -from django_walletpass.settings import dwpconfig as WALLETPASS_CONF from rest_framework import status, viewsets from rest_framework.permissions import AllowAny from rest_framework.response import Response +from django_walletpass.models import Log, Pass, Registration +from django_walletpass.settings import dwpconfig as WALLETPASS_CONF # legacy constant, remove when it can be assumed no timestamps of this format are out # there anymore @@ -126,7 +126,7 @@ def retrieve(self, request, pass_type_id, serial_number): response['Last-Modified'] = http_date(timegm(pass_.updated_at.utctimetuple())) - def _get_response(request): + def _get_response(request): #noqa: W0613 return response return ConditionalGetMiddleware(get_response=_get_response)(request) From 5afda04f8da33f8c9a6d0d707a14e5396e785501 Mon Sep 17 00:00:00 2001 From: Jirka Schaefer Date: Thu, 25 May 2023 17:17:30 +0200 Subject: [PATCH 21/25] linter love 4 fix pylint comment syntax --- django_walletpass/classviews.py | 78 +++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/django_walletpass/classviews.py b/django_walletpass/classviews.py index 7c094a2..dae0efc 100644 --- a/django_walletpass/classviews.py +++ b/django_walletpass/classviews.py @@ -10,28 +10,31 @@ from django.middleware.http import ConditionalGetMiddleware from django.shortcuts import get_object_or_404 from django.utils.http import http_date +from django_walletpass.models import Log, Pass, Registration +from django_walletpass.settings import dwpconfig as WALLETPASS_CONF from rest_framework import status, viewsets from rest_framework.permissions import AllowAny from rest_framework.response import Response -from django_walletpass.models import Log, Pass, Registration -from django_walletpass.settings import dwpconfig as WALLETPASS_CONF # legacy constant, remove when it can be assumed no timestamps of this format are out # there anymore -FORMAT = '%Y-%m-%d %H:%M:%S' +FORMAT = "%Y-%m-%d %H:%M:%S" PASS_REGISTERED = django.dispatch.Signal() PASS_UNREGISTERED = django.dispatch.Signal() def get_pass(pass_type_id, serial_number): - return get_object_or_404(Pass, pass_type_identifier=pass_type_id, serial_number=serial_number) + return get_object_or_404( + Pass, pass_type_identifier=pass_type_id, serial_number=serial_number + ) class RegistrationsViewSet(viewsets.ViewSet): """ Gets the Serial Numbers for Passes Associated with a Device """ - permission_classes = (AllowAny, ) + + permission_classes = (AllowAny,) def list(self, request, device_library_id, pass_type_id): passes = Pass.objects.filter( @@ -41,20 +44,25 @@ def list(self, request, device_library_id, pass_type_id): if passes.count() == 0: return Response({}, status=status.HTTP_400_BAD_REQUEST) - if 'passesUpdatedSince' in request.GET: + if "passesUpdatedSince" in request.GET: try: # must be able to read UTC isoformat with TZ and FORMAT datetime string # as well - date = parse(request.GET['passesUpdatedSince']) + date = parse(request.GET["passesUpdatedSince"]) passes = passes.filter(updated_at__gt=date) except NonExistentTimeError: date = date.replace(hour=0, minute=0) passes = passes.filter(updated_at__gt=date) if passes: - last_updated = passes.aggregate(Max('updated_at'))['updated_at__max'] - serial_numbers = [p.serial_number for p in passes.filter(updated_at=last_updated).all()] - response_data = {'lastUpdated': last_updated.isoformat(), 'serialNumbers': serial_numbers} + last_updated = passes.aggregate(Max("updated_at"))["updated_at__max"] + serial_numbers = [ + p.serial_number for p in passes.filter(updated_at=last_updated).all() + ] + response_data = { + "lastUpdated": last_updated.isoformat(), + "serialNumbers": serial_numbers, + } return Response(response_data) return Response({}, status=status.HTTP_204_NO_CONTENT) @@ -68,23 +76,26 @@ class RegisterPassViewSet(viewsets.ViewSet): get: Registers a Device to Receive Push Notifications for a Pass destroy: Unregister a Device """ - permission_classes = (AllowAny, ) + + permission_classes = (AllowAny,) def create(self, request, device_library_id, pass_type_id, serial_number): pass_ = get_pass(pass_type_id, serial_number) - if request.META.get('HTTP_AUTHORIZATION') != f"ApplePass {pass_.authentication_token}": + if ( + request.META.get("HTTP_AUTHORIZATION") + != f"ApplePass {pass_.authentication_token}" + ): return Response({}, status=status.HTTP_401_UNAUTHORIZED) registration = Registration.objects.filter( - device_library_identifier=device_library_id, - pazz=pass_, + device_library_identifier=device_library_id, pazz=pass_, ) if registration: return Response({}, status=status.HTTP_200_OK) body = json.loads(request.body) new_registration = Registration( device_library_identifier=device_library_id, - push_token=body['pushToken'], + push_token=body["pushToken"], pazz=pass_, ) new_registration.save() @@ -93,11 +104,13 @@ def create(self, request, device_library_id, pass_type_id, serial_number): def destroy(self, request, device_library_id, pass_type_id, serial_number): pass_ = get_pass(pass_type_id, serial_number) - if request.META.get('HTTP_AUTHORIZATION') != f"ApplePass {pass_.authentication_token}": + if ( + request.META.get("HTTP_AUTHORIZATION") + != f"ApplePass {pass_.authentication_token}" + ): return Response({}, status=status.HTTP_401_UNAUTHORIZED) registration = Registration.objects.filter( - device_library_identifier=device_library_id, - pazz=pass_, + device_library_identifier=device_library_id, pazz=pass_, ) if registration: return Response({}, status=status.HTTP_200_OK) @@ -110,23 +123,31 @@ class LatestVersionViewSet(viewsets.ViewSet): """ get: Gets the latest version of a Pass """ - permission_classes = (AllowAny, ) + + permission_classes = (AllowAny,) def retrieve(self, request, pass_type_id, serial_number): pass_ = get_pass(pass_type_id, serial_number) - if request.META.get('HTTP_AUTHORIZATION') != f"ApplePass {pass_.authentication_token}": + if ( + request.META.get("HTTP_AUTHORIZATION") + != f"ApplePass {pass_.authentication_token}" + ): return Response({}, status=status.HTTP_401_UNAUTHORIZED) - if WALLETPASS_CONF['STORAGE_HTTP_REDIRECT']: - return Response({}, status=status.HTTP_302_FOUND, headers={'Location': pass_.data.url}) + if WALLETPASS_CONF["STORAGE_HTTP_REDIRECT"]: + return Response( + {}, status=status.HTTP_302_FOUND, headers={"Location": pass_.data.url} + ) - response = HttpResponse(pass_.data.read(), content_type='application/vnd.apple.pkpass') - response['Content-Disposition'] = 'attachment; filename=pass.pkpass' + response = HttpResponse( + pass_.data.read(), content_type="application/vnd.apple.pkpass" + ) + response["Content-Disposition"] = "attachment; filename=pass.pkpass" - response['Last-Modified'] = http_date(timegm(pass_.updated_at.utctimetuple())) + response["Last-Modified"] = http_date(timegm(pass_.updated_at.utctimetuple())) - def _get_response(request): #noqa: W0613 + def _get_response(request): # noqa: W0613 return response return ConditionalGetMiddleware(get_response=_get_response)(request) @@ -137,10 +158,11 @@ class LogViewSet(viewsets.ViewSet): """ Logs messages from devices """ - permission_classes = (AllowAny, ) + + permission_classes = (AllowAny,) def create(self, request): json_body = json.loads(request.body) - for message in json_body['logs']: + for message in json_body["logs"]: Log(message=message).save() return Response({}, status=status.HTTP_200_OK) From 6ea752ba69129ff82b699db898cfa9ef6e7f3dbe Mon Sep 17 00:00:00 2001 From: Jirka Schaefer Date: Thu, 25 May 2023 17:19:14 +0200 Subject: [PATCH 22/25] Revert "linter love 4" This reverts commit 5afda04f8da33f8c9a6d0d707a14e5396e785501. --- django_walletpass/classviews.py | 78 ++++++++++++--------------------- 1 file changed, 28 insertions(+), 50 deletions(-) diff --git a/django_walletpass/classviews.py b/django_walletpass/classviews.py index dae0efc..7c094a2 100644 --- a/django_walletpass/classviews.py +++ b/django_walletpass/classviews.py @@ -10,31 +10,28 @@ from django.middleware.http import ConditionalGetMiddleware from django.shortcuts import get_object_or_404 from django.utils.http import http_date -from django_walletpass.models import Log, Pass, Registration -from django_walletpass.settings import dwpconfig as WALLETPASS_CONF from rest_framework import status, viewsets from rest_framework.permissions import AllowAny from rest_framework.response import Response +from django_walletpass.models import Log, Pass, Registration +from django_walletpass.settings import dwpconfig as WALLETPASS_CONF # legacy constant, remove when it can be assumed no timestamps of this format are out # there anymore -FORMAT = "%Y-%m-%d %H:%M:%S" +FORMAT = '%Y-%m-%d %H:%M:%S' PASS_REGISTERED = django.dispatch.Signal() PASS_UNREGISTERED = django.dispatch.Signal() def get_pass(pass_type_id, serial_number): - return get_object_or_404( - Pass, pass_type_identifier=pass_type_id, serial_number=serial_number - ) + return get_object_or_404(Pass, pass_type_identifier=pass_type_id, serial_number=serial_number) class RegistrationsViewSet(viewsets.ViewSet): """ Gets the Serial Numbers for Passes Associated with a Device """ - - permission_classes = (AllowAny,) + permission_classes = (AllowAny, ) def list(self, request, device_library_id, pass_type_id): passes = Pass.objects.filter( @@ -44,25 +41,20 @@ def list(self, request, device_library_id, pass_type_id): if passes.count() == 0: return Response({}, status=status.HTTP_400_BAD_REQUEST) - if "passesUpdatedSince" in request.GET: + if 'passesUpdatedSince' in request.GET: try: # must be able to read UTC isoformat with TZ and FORMAT datetime string # as well - date = parse(request.GET["passesUpdatedSince"]) + date = parse(request.GET['passesUpdatedSince']) passes = passes.filter(updated_at__gt=date) except NonExistentTimeError: date = date.replace(hour=0, minute=0) passes = passes.filter(updated_at__gt=date) if passes: - last_updated = passes.aggregate(Max("updated_at"))["updated_at__max"] - serial_numbers = [ - p.serial_number for p in passes.filter(updated_at=last_updated).all() - ] - response_data = { - "lastUpdated": last_updated.isoformat(), - "serialNumbers": serial_numbers, - } + last_updated = passes.aggregate(Max('updated_at'))['updated_at__max'] + serial_numbers = [p.serial_number for p in passes.filter(updated_at=last_updated).all()] + response_data = {'lastUpdated': last_updated.isoformat(), 'serialNumbers': serial_numbers} return Response(response_data) return Response({}, status=status.HTTP_204_NO_CONTENT) @@ -76,26 +68,23 @@ class RegisterPassViewSet(viewsets.ViewSet): get: Registers a Device to Receive Push Notifications for a Pass destroy: Unregister a Device """ - - permission_classes = (AllowAny,) + permission_classes = (AllowAny, ) def create(self, request, device_library_id, pass_type_id, serial_number): pass_ = get_pass(pass_type_id, serial_number) - if ( - request.META.get("HTTP_AUTHORIZATION") - != f"ApplePass {pass_.authentication_token}" - ): + if request.META.get('HTTP_AUTHORIZATION') != f"ApplePass {pass_.authentication_token}": return Response({}, status=status.HTTP_401_UNAUTHORIZED) registration = Registration.objects.filter( - device_library_identifier=device_library_id, pazz=pass_, + device_library_identifier=device_library_id, + pazz=pass_, ) if registration: return Response({}, status=status.HTTP_200_OK) body = json.loads(request.body) new_registration = Registration( device_library_identifier=device_library_id, - push_token=body["pushToken"], + push_token=body['pushToken'], pazz=pass_, ) new_registration.save() @@ -104,13 +93,11 @@ def create(self, request, device_library_id, pass_type_id, serial_number): def destroy(self, request, device_library_id, pass_type_id, serial_number): pass_ = get_pass(pass_type_id, serial_number) - if ( - request.META.get("HTTP_AUTHORIZATION") - != f"ApplePass {pass_.authentication_token}" - ): + if request.META.get('HTTP_AUTHORIZATION') != f"ApplePass {pass_.authentication_token}": return Response({}, status=status.HTTP_401_UNAUTHORIZED) registration = Registration.objects.filter( - device_library_identifier=device_library_id, pazz=pass_, + device_library_identifier=device_library_id, + pazz=pass_, ) if registration: return Response({}, status=status.HTTP_200_OK) @@ -123,31 +110,23 @@ class LatestVersionViewSet(viewsets.ViewSet): """ get: Gets the latest version of a Pass """ - - permission_classes = (AllowAny,) + permission_classes = (AllowAny, ) def retrieve(self, request, pass_type_id, serial_number): pass_ = get_pass(pass_type_id, serial_number) - if ( - request.META.get("HTTP_AUTHORIZATION") - != f"ApplePass {pass_.authentication_token}" - ): + if request.META.get('HTTP_AUTHORIZATION') != f"ApplePass {pass_.authentication_token}": return Response({}, status=status.HTTP_401_UNAUTHORIZED) - if WALLETPASS_CONF["STORAGE_HTTP_REDIRECT"]: - return Response( - {}, status=status.HTTP_302_FOUND, headers={"Location": pass_.data.url} - ) + if WALLETPASS_CONF['STORAGE_HTTP_REDIRECT']: + return Response({}, status=status.HTTP_302_FOUND, headers={'Location': pass_.data.url}) - response = HttpResponse( - pass_.data.read(), content_type="application/vnd.apple.pkpass" - ) - response["Content-Disposition"] = "attachment; filename=pass.pkpass" + response = HttpResponse(pass_.data.read(), content_type='application/vnd.apple.pkpass') + response['Content-Disposition'] = 'attachment; filename=pass.pkpass' - response["Last-Modified"] = http_date(timegm(pass_.updated_at.utctimetuple())) + response['Last-Modified'] = http_date(timegm(pass_.updated_at.utctimetuple())) - def _get_response(request): # noqa: W0613 + def _get_response(request): #noqa: W0613 return response return ConditionalGetMiddleware(get_response=_get_response)(request) @@ -158,11 +137,10 @@ class LogViewSet(viewsets.ViewSet): """ Logs messages from devices """ - - permission_classes = (AllowAny,) + permission_classes = (AllowAny, ) def create(self, request): json_body = json.loads(request.body) - for message in json_body["logs"]: + for message in json_body['logs']: Log(message=message).save() return Response({}, status=status.HTTP_200_OK) From a3f6961fec4dfcb0bfaf4858d01170720186c0a0 Mon Sep 17 00:00:00 2001 From: Jirka Schaefer Date: Thu, 25 May 2023 17:20:02 +0200 Subject: [PATCH 23/25] linter love 5 --- django_walletpass/classviews.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_walletpass/classviews.py b/django_walletpass/classviews.py index 7c094a2..5077e39 100644 --- a/django_walletpass/classviews.py +++ b/django_walletpass/classviews.py @@ -126,7 +126,7 @@ def retrieve(self, request, pass_type_id, serial_number): response['Last-Modified'] = http_date(timegm(pass_.updated_at.utctimetuple())) - def _get_response(request): #noqa: W0613 + def _get_response(request): # noqa: W0613 return response return ConditionalGetMiddleware(get_response=_get_response)(request) From 61b9b411813b7cc126f0df3596ec9125fc6e4e9d Mon Sep 17 00:00:00 2001 From: Jirka Schaefer Date: Thu, 25 May 2023 17:22:45 +0200 Subject: [PATCH 24/25] linter love 6 --- django_walletpass/classviews.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_walletpass/classviews.py b/django_walletpass/classviews.py index 5077e39..d7813a3 100644 --- a/django_walletpass/classviews.py +++ b/django_walletpass/classviews.py @@ -126,7 +126,7 @@ def retrieve(self, request, pass_type_id, serial_number): response['Last-Modified'] = http_date(timegm(pass_.updated_at.utctimetuple())) - def _get_response(request): # noqa: W0613 + def _get_response(_request): return response return ConditionalGetMiddleware(get_response=_get_response)(request) From fb5311991d501e362b168efa109578ec03038f1d Mon Sep 17 00:00:00 2001 From: Nache Date: Thu, 25 May 2023 17:32:55 +0200 Subject: [PATCH 25/25] new release :) --- CHANGELOG.md | 6 +++++- setup.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55f3a36..7cbb2d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog -## [3.0] + +## [3.0] - 2023-05-25 ## Breaking Changes @@ -11,6 +12,9 @@ - replace `pyAPNS2` with `aioapns` to enable Django>=4.0 compatibility and resolve unmaintained `hyper` dependency - increase minimum Python version to 3.6 +### Fixed + +- sumer time switch bugfix ## [2.0] - 2023-01-09 diff --git a/setup.py b/setup.py index a1e4046..d5080c3 100644 --- a/setup.py +++ b/setup.py @@ -40,9 +40,9 @@ 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Software Development :: Internationalization', 'Topic :: Software Development :: Localization',