django-esi 6.0.0__tar.gz → 7.0.0b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-esi might be problematic. Click here for more details.

Files changed (78) hide show
  1. {django_esi-6.0.0 → django_esi-7.0.0b1}/PKG-INFO +21 -19
  2. {django_esi-6.0.0 → django_esi-7.0.0b1}/README.md +13 -13
  3. django_esi-7.0.0b1/esi/__init__.py +5 -0
  4. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/admin.py +0 -1
  5. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/apps.py +1 -1
  6. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/checks.py +23 -0
  7. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/clients.py +62 -32
  8. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/decorators.py +2 -1
  9. django_esi-7.0.0b1/esi/migrations/0013_squashed_0012_fix_token_type_choices.py +57 -0
  10. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/models.py +8 -7
  11. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tasks.py +29 -2
  12. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/templates/esi/select_token.html +9 -9
  13. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/factories.py +1 -1
  14. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_clients.py +122 -98
  15. django_esi-7.0.0b1/esi/tests/test_tasks.py +116 -0
  16. {django_esi-6.0.0 → django_esi-7.0.0b1}/pyproject.toml +10 -5
  17. django_esi-6.0.0/esi/__init__.py +0 -5
  18. django_esi-6.0.0/esi/tests/test_tasks.py +0 -61
  19. {django_esi-6.0.0 → django_esi-7.0.0b1}/LICENSE +0 -0
  20. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/app_settings.py +0 -0
  21. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/errors.py +0 -0
  22. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/de/LC_MESSAGES/django.mo +0 -0
  23. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/de/LC_MESSAGES/django.po +0 -0
  24. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/en/LC_MESSAGES/django.mo +0 -0
  25. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/en/LC_MESSAGES/django.po +0 -0
  26. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/es/LC_MESSAGES/django.mo +0 -0
  27. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/es/LC_MESSAGES/django.po +0 -0
  28. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  29. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/fr_FR/LC_MESSAGES/django.po +0 -0
  30. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  31. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/it_IT/LC_MESSAGES/django.po +0 -0
  32. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ja/LC_MESSAGES/django.mo +0 -0
  33. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ja/LC_MESSAGES/django.po +0 -0
  34. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  35. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ko_KR/LC_MESSAGES/django.po +0 -0
  36. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ru/LC_MESSAGES/django.mo +0 -0
  37. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ru/LC_MESSAGES/django.po +0 -0
  38. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  39. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/zh_Hans/LC_MESSAGES/django.po +0 -0
  40. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/management/commands/__init__.py +0 -0
  41. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/management/commands/migrate_to_ssov2.py +0 -0
  42. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/managers.py +0 -0
  43. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0001_initial.py +0 -0
  44. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0002_scopes_20161208.py +0 -0
  45. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0003_hide_tokens_from_admin_site.py +0 -0
  46. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0004_remove_unique_access_token.py +0 -0
  47. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0005_remove_token_length_limit.py +0 -0
  48. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0006_remove_url_length_limit.py +0 -0
  49. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0007_fix_mysql_8_migration.py +0 -0
  50. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0008_nullable_refresh_token.py +0 -0
  51. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0009_set_old_tokens_to_sso_v1.py +0 -0
  52. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0010_set_new_tokens_to_sso_v2.py +0 -0
  53. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0011_add_token_indices.py +0 -0
  54. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0012_fix_token_type_choices.py +0 -0
  55. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/__init__.py +0 -0
  56. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/static/esi/img/EVE_SSO_Login_Buttons_Large_Black.png +0 -0
  57. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/static/esi/img/EVE_SSO_Login_Buttons_Large_White.png +0 -0
  58. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/static/esi/img/EVE_SSO_Login_Buttons_Small_Black.png +0 -0
  59. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/static/esi/img/EVE_SSO_Login_Buttons_Small_White.png +0 -0
  60. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/templatetags/__init__.py +0 -0
  61. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/templatetags/scope_tags.py +0 -0
  62. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/__init__.py +0 -0
  63. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/client_authed_pilot.py +0 -0
  64. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/client_public_pilot.py +0 -0
  65. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/factories_2.py +0 -0
  66. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/jwt_factory.py +0 -0
  67. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_checks.py +0 -0
  68. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_decorators.py +0 -0
  69. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_management_command.py +0 -0
  70. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_managers.py +0 -0
  71. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_models.py +0 -0
  72. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_swagger.json +0 -0
  73. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_swagger_full.json +0 -0
  74. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_templatetags.py +0 -0
  75. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_views.py +0 -0
  76. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/threading_pilot.py +0 -0
  77. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/urls.py +0 -0
  78. {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/views.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: django-esi
3
- Version: 6.0.0
3
+ Version: 7.0.0b1
4
4
  Summary: Django app for accessing the EVE Swagger Interface (ESI).
5
5
  Author-email: Alliance Auth <adarnof@gmail.com>
6
6
  Requires-Python: >=3.8
@@ -8,8 +8,8 @@ Description-Content-Type: text/markdown
8
8
  Classifier: Environment :: Web Environment
9
9
  Classifier: Framework :: Django
10
10
  Classifier: Framework :: Django :: 4.2
11
- Classifier: Framework :: Django :: 5.0
12
11
  Classifier: Framework :: Django :: 5.1
12
+ Classifier: Framework :: Django :: 5.2
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
15
15
  Classifier: Operating System :: OS Independent
@@ -20,12 +20,14 @@ Classifier: Programming Language :: Python :: 3.9
20
20
  Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
22
  Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
23
24
  Classifier: Topic :: Internet :: WWW/HTTP
24
25
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
25
- Requires-Dist: bravado>=10.6,<11
26
+ License-File: LICENSE
27
+ Requires-Dist: bravado>=10.6,<12
26
28
  Requires-Dist: brotli
27
29
  Requires-Dist: celery>=4.0.2
28
- Requires-Dist: django>=4.2,<5.2
30
+ Requires-Dist: django>=4.2,<6
29
31
  Requires-Dist: jsonschema<4
30
32
  Requires-Dist: python-jose>=3.3
31
33
  Requires-Dist: requests>=2.26
@@ -34,7 +36,7 @@ Requires-Dist: tqdm>=4.62.3
34
36
  Requires-Dist: myst-parser ; extra == "docs"
35
37
  Requires-Dist: sphinx ; extra == "docs"
36
38
  Requires-Dist: sphinx-copybutton ; extra == "docs"
37
- Requires-Dist: sphinx-rtd-theme<3,>=2 ; extra == "docs"
39
+ Requires-Dist: sphinx-rtd-theme>=3,<4 ; extra == "docs"
38
40
  Requires-Dist: sphinx-tabs ; extra == "docs"
39
41
  Requires-Dist: sphinxcontrib-django ; extra == "docs"
40
42
  Requires-Dist: coverage ; extra == "test"
@@ -47,27 +49,27 @@ Project-URL: Tracker, https://gitlab.com/allianceauth/django-esi/-/issues
47
49
  Provides-Extra: docs
48
50
  Provides-Extra: test
49
51
 
50
- # django-esi
52
+ # Django-ESI
51
53
 
52
54
  Django app for easy access to the EVE Swagger Interface (ESI)
53
55
 
54
- [![version](https://img.shields.io/pypi/v/django-esi)](https://pypi.org/project/django-esi/)
55
- [![python](https://img.shields.io/pypi/pyversions/django-esi)](https://pypi.org/project/django-esi/)
56
- [![django](https://img.shields.io/pypi/djversions/django-esi)](https://pypi.org/project/django-esi/)
57
- [![license](https://img.shields.io/badge/license-GPLv3-green)](https://pypi.org/project/django-esi/)
58
- [![pipeline-status](https://gitlab.com/allianceauth/django-esi/badges/master/pipeline.svg)](https://gitlab.com/allianceauth/django-esi/pipelines)
59
- [![coverage](https://gitlab.com/allianceauth/django-esi/badges/master/coverage.svg)](https://gitlab.com/allianceauth/django-esi/pipelines)
56
+ [![Version](https://img.shields.io/pypi/v/django-esi)](https://pypi.org/project/django-esi/)
57
+ [![Python Versions](https://img.shields.io/pypi/pyversions/django-esi)](https://pypi.org/project/django-esi/)
58
+ [![Django Versions](https://img.shields.io/pypi/djversions/django-esi)](https://pypi.org/project/django-esi/)
59
+ [![License](https://img.shields.io/badge/license-GPLv3-green)](https://pypi.org/project/django-esi/)
60
+ [![Pipeline Status](https://gitlab.com/allianceauth/django-esi/badges/master/pipeline.svg)](https://gitlab.com/allianceauth/django-esi/pipelines)
61
+ [![Coverage](https://gitlab.com/allianceauth/django-esi/badges/master/coverage.svg)](https://gitlab.com/allianceauth/django-esi/pipelines)
60
62
  [![Documentation Status](https://readthedocs.org/projects/django-esi/badge/?version=latest)](https://django-esi.readthedocs.io/en/latest/?badge=latest)
61
63
  [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
62
64
  [![Chat on Discord](https://img.shields.io/discord/399006117012832262.svg)](https://discord.gg/fjnHAmk)
63
65
 
64
66
  ## Overview
65
67
 
66
- Django-esi is a Django app that provides an interface for easy access to the EVE Swagger Interface (ESI), the official API for the game [EVE Online](https://www.eveonline.com/).
68
+ Django-ESI is a Django app that provides an interface for easy access to the EVE Swagger Interface (ESI), the official API for the game [EVE Online](https://www.eveonline.com/).
67
69
 
68
- It is build upon [Bravado](https://github.com/Yelp/bravado) - a python client library for Swagger 2.0 services.
70
+ It is built upon [Bravado](https://github.com/Yelp/bravado) - a python client library for Swagger 2.0 services.
69
71
 
70
- Django-esi adds the following main functionalities to a Django site:
72
+ Django-ESI adds the following main functionalities to a Django site:
71
73
 
72
74
  - Dynamically generated client for interacting with public and private ESI endpoints
73
75
  - Support for adding EVE SSO to authenticate characters and retrieve tokens
@@ -75,8 +77,8 @@ Django-esi adds the following main functionalities to a Django site:
75
77
 
76
78
  ## Python Support
77
79
 
78
- Django-esi follows the Django Python support schedule, The supported version of Python will differ based on the version of Django used.
79
- <https://docs.djangoproject.com/en/5.0/faq/install/#what-python-version-can-i-use-with-django>
80
+ Django-ESI follows the Django Python support schedule, The supported version of Python will differ based on the version of Django used.
81
+ <https://docs.djangoproject.com/en/5.2/faq/install/#what-python-version-can-i-use-with-django>
80
82
 
81
83
  ## History of this app
82
84
 
@@ -84,5 +86,5 @@ This app is a fork from [adarnauth-esi](https://gitlab.com/Adarnof/adarnauth-esi
84
86
 
85
87
  ## Documentation
86
88
 
87
- For all details on how to install and use django-esi please see the [documentation](https://django-esi.readthedocs.io/en/latest/).
89
+ For all details on how to install and use Django-ESI please see the [Documentation](https://django-esi.readthedocs.io/en/latest/).
88
90
 
@@ -1,24 +1,24 @@
1
- # django-esi
1
+ # Django-ESI
2
2
 
3
3
  Django app for easy access to the EVE Swagger Interface (ESI)
4
4
 
5
- [![version](https://img.shields.io/pypi/v/django-esi)](https://pypi.org/project/django-esi/)
6
- [![python](https://img.shields.io/pypi/pyversions/django-esi)](https://pypi.org/project/django-esi/)
7
- [![django](https://img.shields.io/pypi/djversions/django-esi)](https://pypi.org/project/django-esi/)
8
- [![license](https://img.shields.io/badge/license-GPLv3-green)](https://pypi.org/project/django-esi/)
9
- [![pipeline-status](https://gitlab.com/allianceauth/django-esi/badges/master/pipeline.svg)](https://gitlab.com/allianceauth/django-esi/pipelines)
10
- [![coverage](https://gitlab.com/allianceauth/django-esi/badges/master/coverage.svg)](https://gitlab.com/allianceauth/django-esi/pipelines)
5
+ [![Version](https://img.shields.io/pypi/v/django-esi)](https://pypi.org/project/django-esi/)
6
+ [![Python Versions](https://img.shields.io/pypi/pyversions/django-esi)](https://pypi.org/project/django-esi/)
7
+ [![Django Versions](https://img.shields.io/pypi/djversions/django-esi)](https://pypi.org/project/django-esi/)
8
+ [![License](https://img.shields.io/badge/license-GPLv3-green)](https://pypi.org/project/django-esi/)
9
+ [![Pipeline Status](https://gitlab.com/allianceauth/django-esi/badges/master/pipeline.svg)](https://gitlab.com/allianceauth/django-esi/pipelines)
10
+ [![Coverage](https://gitlab.com/allianceauth/django-esi/badges/master/coverage.svg)](https://gitlab.com/allianceauth/django-esi/pipelines)
11
11
  [![Documentation Status](https://readthedocs.org/projects/django-esi/badge/?version=latest)](https://django-esi.readthedocs.io/en/latest/?badge=latest)
12
12
  [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
13
13
  [![Chat on Discord](https://img.shields.io/discord/399006117012832262.svg)](https://discord.gg/fjnHAmk)
14
14
 
15
15
  ## Overview
16
16
 
17
- Django-esi is a Django app that provides an interface for easy access to the EVE Swagger Interface (ESI), the official API for the game [EVE Online](https://www.eveonline.com/).
17
+ Django-ESI is a Django app that provides an interface for easy access to the EVE Swagger Interface (ESI), the official API for the game [EVE Online](https://www.eveonline.com/).
18
18
 
19
- It is build upon [Bravado](https://github.com/Yelp/bravado) - a python client library for Swagger 2.0 services.
19
+ It is built upon [Bravado](https://github.com/Yelp/bravado) - a python client library for Swagger 2.0 services.
20
20
 
21
- Django-esi adds the following main functionalities to a Django site:
21
+ Django-ESI adds the following main functionalities to a Django site:
22
22
 
23
23
  - Dynamically generated client for interacting with public and private ESI endpoints
24
24
  - Support for adding EVE SSO to authenticate characters and retrieve tokens
@@ -26,8 +26,8 @@ Django-esi adds the following main functionalities to a Django site:
26
26
 
27
27
  ## Python Support
28
28
 
29
- Django-esi follows the Django Python support schedule, The supported version of Python will differ based on the version of Django used.
30
- <https://docs.djangoproject.com/en/5.0/faq/install/#what-python-version-can-i-use-with-django>
29
+ Django-ESI follows the Django Python support schedule, The supported version of Python will differ based on the version of Django used.
30
+ <https://docs.djangoproject.com/en/5.2/faq/install/#what-python-version-can-i-use-with-django>
31
31
 
32
32
  ## History of this app
33
33
 
@@ -35,4 +35,4 @@ This app is a fork from [adarnauth-esi](https://gitlab.com/Adarnof/adarnauth-esi
35
35
 
36
36
  ## Documentation
37
37
 
38
- For all details on how to install and use django-esi please see the [documentation](https://django-esi.readthedocs.io/en/latest/).
38
+ For all details on how to install and use Django-ESI please see the [Documentation](https://django-esi.readthedocs.io/en/latest/).
@@ -0,0 +1,5 @@
1
+ """Django app for accessing the EVE Swagger Interface (ESI)."""
2
+
3
+ __version__ = '7.0.0b1'
4
+ __title__ = 'Django-ESI'
5
+ __url__ = 'https://gitlab.com/allianceauth/django-esi'
@@ -23,7 +23,6 @@ class TokenAdmin(admin.ModelAdmin):
23
23
  def get_scopes(self, obj):
24
24
  return ", ".join([x.name for x in obj.scopes.all()])
25
25
 
26
-
27
26
  User = get_user_model()
28
27
  list_display = ('user', 'character_name', 'get_scopes')
29
28
  search_fields = ['user__%s' % User.USERNAME_FIELD, 'character_name', 'scopes__name']
@@ -6,6 +6,6 @@ class EsiConfig(AppConfig):
6
6
  name = 'esi'
7
7
  verbose_name = 'EVE Swagger Interface (SSO v2)'
8
8
 
9
- def ready(self):
9
+ def ready(self) -> None:
10
10
  super().ready()
11
11
  from esi import checks # noqa
@@ -4,7 +4,9 @@ from django.conf import settings
4
4
 
5
5
  @register(Tags.security)
6
6
  def check_sso_application_settings(*args, **kwargs):
7
+
7
8
  errors = []
9
+
8
10
  if (
9
11
  not hasattr(settings, "ESI_SSO_CLIENT_ID")
10
12
  or not hasattr(settings, "ESI_SSO_CLIENT_SECRET")
@@ -30,4 +32,25 @@ def check_sso_application_settings(*args, **kwargs):
30
32
  id='esi.E001'
31
33
  )
32
34
  )
35
+
36
+ # Check for ESI_USER_CONTACT_EMAIL
37
+ if hasattr(settings, "ESI_USER_CONTACT_EMAIL"):
38
+ # Check if ESI_USER_CONTACT_EMAIL is empty
39
+ if settings.ESI_USER_CONTACT_EMAIL == "":
40
+ errors.append(
41
+ Error(
42
+ msg="'ESI_USER_CONTACT_EMAIL' is empty. A valid email is required as maintainer contact for CCP.",
43
+ hint="",
44
+ id="esi.E002",
45
+ )
46
+ )
47
+ # ESI_USER_CONTACT_EMAIL not found
48
+ else:
49
+ errors.append(
50
+ Error(
51
+ msg="No 'ESI_USER_CONTACT_EMAIL' found is settings. A valid email is required as maintainer contact for CCP.",
52
+ hint="",
53
+ id="esi.E003",
54
+ )
55
+ )
33
56
  return errors
@@ -5,6 +5,7 @@ import logging
5
5
  from time import sleep
6
6
  from urllib import parse as urlparse
7
7
  from typing import Any, Union, Tuple
8
+ import warnings
8
9
 
9
10
  from bravado.client import SwaggerClient
10
11
  from bravado import requests_client
@@ -20,7 +21,7 @@ from requests.adapters import HTTPAdapter
20
21
  from django.core.cache import cache
21
22
 
22
23
  from .errors import TokenExpiredError
23
- from . import app_settings, __version__, __title__
24
+ from . import app_settings, __version__, __title__, __url__
24
25
 
25
26
 
26
27
  logger = logging.getLogger(__name__)
@@ -438,13 +439,9 @@ def read_spec(path, http_client=None):
438
439
 
439
440
 
440
441
  def esi_client_factory(
441
- token=None,
442
- datasource: str = None,
443
- spec_file: str = None,
444
- version: str = None,
445
- app_info_text: str = None,
446
- **kwargs
447
- ) -> SwaggerClient:
442
+ token=None, datasource: str = None, spec_file: str = None, version: str = None,
443
+ app_info_text: str = None, # Deprecate in favour of the following variables
444
+ ua_appname: str = None, ua_version: str = None, ua_url: str = None, **kwargs) -> SwaggerClient:
448
445
  """Generate a new ESI client.
449
446
 
450
447
  Args:
@@ -452,10 +449,9 @@ def esi_client_factory(
452
449
  datasource: Name of the ESI datasource to access.
453
450
  spec_file: Absolute path to a swagger spec file to load.
454
451
  version: Base ESI API version. Accepted values are 'legacy', 'latest',
455
- app_info_text: Text identifying the application using ESI which will be \
456
- included in the User-Agent header. Should contain name and version of the \
457
- application using ESI. e.g. `"my-app v1.0.0"`. \
458
- Note that spaces are used as delimiter.
452
+ ua_appname: Name of the App for generating a User-Agent,
453
+ ua_version: Version of the App for generating a User-Agent,
454
+ ua_url: (optional) URL To the Source Code or Documentation for generating a User-Agent,
459
455
  kwargs: Explicit resource versions to build, in the form Character='v4'. \
460
456
  Same values accepted as version.
461
457
 
@@ -466,15 +462,40 @@ def esi_client_factory(
466
462
  Returns:
467
463
  New ESI client
468
464
  """
465
+
466
+ if app_info_text is not None:
467
+ warnings.warn(
468
+ "The 'app_info_text' parameter is deprecated and will be removed in a future release. "
469
+ "Use 'ua_appname', 'ua_version', and `ua_url` to dynamically build a User-Agent instead",
470
+ DeprecationWarning,
471
+ stacklevel=2
472
+ )
473
+
474
+ if ua_appname is None or ua_version is None:
475
+ warnings.warn(
476
+ "Applications must define their own 'ua_appname' and 'ua_version' to generate a User-Agent",
477
+ DeprecationWarning,
478
+ stacklevel=2
479
+ )
480
+
469
481
  if app_settings.ESI_INFO_LOGGING_ENABLED:
470
482
  logger.info('Generating an ESI client...')
471
483
 
472
484
  client = RequestsClientPlus()
473
- user_agent = (
474
- str(app_info_text) if app_info_text else f"{__title__} v{__version__}"
475
- )
476
- if app_settings.ESI_USER_CONTACT_EMAIL:
477
- user_agent += f" {app_settings.ESI_USER_CONTACT_EMAIL}"
485
+
486
+ if app_info_text:
487
+ # app_info_text (email@example) Django-ESI/1.2.3 (+https://gitlab.com/allianceauth/django-esi)
488
+ # Deprecated
489
+ user_agent = f"{app_info_text} ({app_settings.ESI_USER_CONTACT_EMAIL}) {__title__}/{__version__} (+{__url__})"
490
+ elif ua_appname is None or ua_version is None:
491
+ # Django-ESI/1.2.3 () (email@example; +https://gitlab.com/allianceauth/django-esi)
492
+ # Deprecated
493
+ user_agent = f"{__title__}/{__version__} ({app_settings.ESI_USER_CONTACT_EMAIL}; +{__url__})"
494
+ else:
495
+ # AppName/1.2.3 (email@example.com) Django-ESI/1.2.3 (+https://gitlab.com/allianceauth/django-esi)
496
+ # or AppName/1.2.3 (email@example.com; +https://gitlab.com/) Django-ESI/1.2.3 (+https://gitlab.com/allianceauth/django-esi) (+https://gitlab.com/allianceauth/django-esi)
497
+ # Preferred
498
+ user_agent = f"{ua_appname}/{ua_version} ({app_settings.ESI_USER_CONTACT_EMAIL}{f'; +{ua_url})' if ua_url else ')'} {__title__}/{__version__} (+{__url__})"
478
499
 
479
500
  client.user_agent = user_agent
480
501
 
@@ -535,10 +556,9 @@ class EsiClientProvider:
535
556
  datasource: Name of the ESI datasource to access.
536
557
  spec_file: Absolute path to a swagger spec file to load.
537
558
  version: Base ESI API version. Accepted values are 'legacy', 'latest',
538
- app_info_text: Text identifying the application using ESI which will be \
539
- included in the User-Agent header. Should contain name and version of \
540
- the application using ESI. e.g. `"my-app v1.0.0"`. \
541
- Note that spaces are used as delimiter.
559
+ ua_appname: Name of the App for generating a User-Agent,
560
+ ua_version: Version of the App for generating a User-Agent,
561
+ ua_url: (optional) URL To the Source Code or Documentation for generating a User-Agent,
542
562
  kwargs: Explicit resource versions to build, in the form Character='v4'. \
543
563
  Same values accepted as version.
544
564
 
@@ -550,30 +570,40 @@ class EsiClientProvider:
550
570
  _client = None
551
571
 
552
572
  def __init__(
553
- self,
554
- datasource=None,
555
- spec_file=None,
556
- version=None,
557
- app_info_text=None,
558
- **kwargs
559
- ):
573
+ self, datasource=None, spec_file=None, version=None,
574
+ app_info_text=None, # Deprecate in favour of the following variables
575
+ ua_appname: str = None, ua_version: str = None, ua_url: str = None, **kwargs) -> None:
560
576
  self._datasource = datasource
561
577
  self._spec_file = spec_file
562
578
  self._version = version
563
- self._app_text = app_info_text
579
+ self._app_text = app_info_text # Deprecate in favour of the following variables
580
+ self._ua_appname = ua_appname
581
+ self._ua_version = ua_version
582
+ self._ua_url = ua_url
564
583
  self._kwargs = kwargs
565
584
 
585
+ if app_info_text is not None:
586
+ warnings.warn(
587
+ "The 'app_info_text' parameter is deprecated and will be removed in a future release. "
588
+ "Use 'ua_appname', 'ua_version', and `ua_url` to dynamically build a User-Agent instead",
589
+ DeprecationWarning,
590
+ stacklevel=2
591
+ )
592
+
566
593
  @property
567
- def client(self):
594
+ def client(self) -> SwaggerClient:
568
595
  if self._client is None:
569
596
  self._client = esi_client_factory(
570
597
  datasource=self._datasource,
571
598
  spec_file=self._spec_file,
572
599
  version=self._version,
573
- app_info_text=self._app_text,
600
+ app_info_text=self._app_text, # Deprecate in favour of the following variables
601
+ ua_appname=self._ua_appname,
602
+ ua_version=self._ua_version,
603
+ ua_url=self._ua_url,
574
604
  **self._kwargs,
575
605
  )
576
606
  return self._client
577
607
 
578
- def __str__(self):
608
+ def __str__(self) -> str:
579
609
  return 'EsiClientProvider'
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  from functools import wraps
3
+ from typing import Union
3
4
 
4
5
  from .models import Token, CallbackRedirect
5
6
 
@@ -7,7 +8,7 @@ from .models import Token, CallbackRedirect
7
8
  logger = logging.getLogger(__name__)
8
9
 
9
10
 
10
- def _check_callback(request):
11
+ def _check_callback(request) -> Union[Token, None]:
11
12
  # ensure session installed in database
12
13
  if not request.session.exists(request.session.session_key):
13
14
  logger.debug("Creating new session for %s", request.user)
@@ -0,0 +1,57 @@
1
+ # Generated by Django 4.2 on 2025-04-04 01:28
2
+
3
+ from django.conf import settings
4
+ from django.db import migrations, models
5
+ import django.db.models.deletion
6
+
7
+ # Squashmigration imported a RunPython, not needed
8
+ # Manually optimized
9
+
10
+
11
+ class Migration(migrations.Migration):
12
+
13
+ replaces = [('esi', '0001_initial'), ('esi', '0002_scopes_20161208'), ('esi', '0003_hide_tokens_from_admin_site'), ('esi', '0004_remove_unique_access_token'), ('esi', '0005_remove_token_length_limit'), ('esi', '0006_remove_url_length_limit'), ('esi', '0007_fix_mysql_8_migration'), ('esi', '0008_nullable_refresh_token'), ('esi', '0009_set_old_tokens_to_sso_v1'), ('esi', '0010_set_new_tokens_to_sso_v2'), ('esi', '0011_add_token_indices'), ('esi', '0012_fix_token_type_choices')]
14
+
15
+ initial = True
16
+
17
+ dependencies = [
18
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
19
+ ]
20
+
21
+ operations = [
22
+ migrations.CreateModel(
23
+ name='Scope',
24
+ fields=[
25
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
26
+ ('name', models.CharField(help_text='The official EVE name for the scope.', max_length=100, unique=True)),
27
+ ('help_text', models.TextField(help_text='The official EVE description of the scope.')),
28
+ ],
29
+ ),
30
+ migrations.CreateModel(
31
+ name='Token',
32
+ fields=[
33
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
34
+ ('created', models.DateTimeField(auto_now_add=True)),
35
+ ('access_token', models.TextField(editable=False, help_text='The access token granted by SSO.')),
36
+ ('refresh_token', models.TextField(editable=False, help_text='A re-usable token to generate new access tokens upon expiry.', null=True)),
37
+ ('character_id', models.IntegerField(db_index=True, help_text='The ID of the EVE character who authenticated by SSO.')),
38
+ ('character_name', models.CharField(db_index=True, help_text='The name of the EVE character who authenticated by SSO.', max_length=100)),
39
+ ('token_type', models.CharField(choices=[('character', 'Character'), ('corporation', 'Corporation')], default='character', help_text='The applicable range of the token.', max_length=100)),
40
+ ('character_owner_hash', models.CharField(db_index=True, help_text='The unique string identifying this character and its owning EVE account. Changes if the owning account changes.', max_length=254)),
41
+ ('scopes', models.ManyToManyField(blank=True, help_text='The access scopes granted by this token.', to='esi.scope')),
42
+ ('user', models.ForeignKey(blank=True, help_text='The user to whom this token belongs.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
43
+ ('sso_version', models.IntegerField(default=2, help_text='EVE SSO Version.')),
44
+ ],
45
+ ),
46
+ migrations.CreateModel(
47
+ name='CallbackRedirect',
48
+ fields=[
49
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
50
+ ('url', models.TextField(default='/', help_text='The internal URL to redirect this callback towards.')),
51
+ ('session_key', models.CharField(help_text='Session key identifying the session this redirect was created for.', max_length=254, unique=True)),
52
+ ('state', models.CharField(help_text='OAuth2 state string representing this session.', max_length=128)),
53
+ ('created', models.DateTimeField(auto_now_add=True)),
54
+ ('token', models.ForeignKey(blank=True, help_text='Token generated by a completed code exchange from callback processing.', null=True, on_delete=django.db.models.deletion.CASCADE, to='esi.token')),
55
+ ],
56
+ ),
57
+ ]
@@ -1,6 +1,7 @@
1
1
  import datetime
2
2
  import re
3
3
  import logging
4
+ from typing import ClassVar
4
5
 
5
6
  from bravado.client import SwaggerClient
6
7
  from requests.auth import HTTPBasicAuth
@@ -54,7 +55,7 @@ class Scope(models.Model):
54
55
  except IndexError:
55
56
  return name
56
57
 
57
- def __str__(self):
58
+ def __str__(self) -> str:
58
59
  return self.name
59
60
 
60
61
 
@@ -121,16 +122,16 @@ class Token(models.Model):
121
122
  default=2
122
123
  )
123
124
 
124
- objects = TokenManager()
125
+ objects: ClassVar[TokenManager] = TokenManager()
125
126
 
126
- def __str__(self):
127
+ def __str__(self) -> str:
127
128
  try:
128
129
  scopes = sorted(s.name for s in self.scopes.all())
129
130
  except ValueError:
130
131
  scopes = []
131
132
  return f'{self.character_name} - {", ".join(scopes)}'
132
133
 
133
- def __repr__(self):
134
+ def __repr__(self) -> str:
134
135
  return "<{}(id={}): {}, {}>".format(
135
136
  self.__class__.__name__,
136
137
  self.pk,
@@ -248,7 +249,7 @@ class Token(models.Model):
248
249
  logger.debug("Not a refreshable token.")
249
250
  raise NotRefreshableTokenError()
250
251
 
251
- def refresh_or_delete(self):
252
+ def refresh_or_delete(self) -> None:
252
253
  """Refresh this token or delete it if it can not be refreshed."""
253
254
  try:
254
255
  self.refresh()
@@ -345,10 +346,10 @@ class CallbackRedirect(models.Model):
345
346
  )
346
347
  )
347
348
 
348
- def __str__(self):
349
+ def __str__(self) -> str:
349
350
  return f"{self.session_key}: {self.url}"
350
351
 
351
- def __repr__(self):
352
+ def __repr__(self) -> str:
352
353
  return "<{}(pk={}): {} to {}>".format(
353
354
  self.__class__.__name__, self.pk,
354
355
  self.session_key, self.url
@@ -1,5 +1,6 @@
1
1
  from datetime import timedelta
2
2
  import logging
3
+ from math import ceil
3
4
 
4
5
  from celery import shared_task
5
6
 
@@ -26,14 +27,15 @@ def cleanup_callbackredirect(max_age=300):
26
27
 
27
28
 
28
29
  @shared_task
29
- def cleanup_token():
30
+ def cleanup_token() -> None:
30
31
  """
31
- Delete expired :model:`esi.Token` models.
32
+ Delete Orphaned Tokens, then refresh or delete expired :model:`esi.Token` models.
32
33
  """
33
34
  orphaned_tokens = Token.objects.filter(user__isnull=True)
34
35
  if orphaned_tokens.exists():
35
36
  logger.info("Deleting %d orphaned tokens.", orphaned_tokens.count())
36
37
  orphaned_tokens.delete()
38
+
37
39
  expired_tokens = Token.objects.exclude(user__isnull=True).get_expired()
38
40
  if expired_tokens.exists():
39
41
  logger.info(
@@ -46,6 +48,31 @@ def cleanup_token():
46
48
  refresh_or_delete_token.apply_async(args=[token_pk], priority=8)
47
49
 
48
50
 
51
+ @shared_task
52
+ def cleanup_token_subset(fraction: int = 48) -> None:
53
+ """
54
+ Delete Orphaned Tokens, then refresh or delete a subset of expired :model:`esi.Token` models.
55
+
56
+ This task operates on 1/fraction of the oldest tokens and can be called on a more regular schedule
57
+ """
58
+ orphaned_tokens = Token.objects.filter(user__isnull=True)
59
+ if orphaned_tokens.exists():
60
+ logger.info("Deleting %d orphaned tokens.", orphaned_tokens.count())
61
+ orphaned_tokens.delete()
62
+
63
+ expired_tokens = Token.objects.exclude(user__isnull=True).get_expired()
64
+ expired_tokens_subset = expired_tokens.filter(
65
+ refresh_token__isnull=False
66
+ ).order_by("created")[:ceil(expired_tokens.count() / fraction)]
67
+
68
+ if expired_tokens.exists():
69
+ logger.info(
70
+ f"Triggering bulk refresh of subset/possible {expired_tokens_subset.count()}/{expired_tokens.count()} expired tokens."
71
+ )
72
+ for token_pk in expired_tokens_subset.values_list("pk", flat=True):
73
+ refresh_or_delete_token.apply_async(args=[token_pk], priority=8)
74
+
75
+
49
76
  @shared_task
50
77
  def refresh_or_delete_token(token_pk: int):
51
78
  token = Token.objects.get(pk=token_pk)
@@ -10,7 +10,7 @@
10
10
  <meta name="description" content="">
11
11
  <meta name="author" content="">
12
12
 
13
- <title>{% trans "ESI Token Selection" %}</title>
13
+ <title>{% translate "ESI Token Selection" %}</title>
14
14
 
15
15
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.1/css/bootstrap.min.css" integrity="sha512-T584yQ/tdRR5QwOpfvDfVQUidzfgc2339Lc8uBDtcp/wYu80d7jwBgAxbyMh0a9YM9F8N3tdErpFI8iaGx6x5g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
16
16
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.slim.min.js" integrity="sha512-6ORWJX/LrnSjBzwefdNUyLCMTIsGoNP6NftMy2UAm1JBm6PRZCO1d7OHBStWpVFZLO+RerTvqX/Z9mBFfCJZ4A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
@@ -41,11 +41,11 @@
41
41
  </head>
42
42
  <body class="bg-dark">
43
43
  <div class="container-fluid mt-5">
44
- <h3 class="text-light font-weight-light text-center">{% trans "Select Character" %}</h3><br>
44
+ <h3 class="text-light font-weight-light text-center">{% translate "Select Character" %}</h3><br>
45
45
  <div class="mt-2">
46
46
  <div class="col-lg-10 offset-lg-1 col-md-12" style="padding-left: 0 !important; padding-right: 0 !important;">
47
47
  <div class="card text-white bg-secondary">
48
- <div class="card-header text-center">{% trans "Scopes Requested" %}</div>
48
+ <div class="card-header text-center">{% translate "Scopes Requested" %}</div>
49
49
  <div class="card-body align-middle text-center">
50
50
  {% for scope in scopes %}<span class="badge badge-dark">{{ scope|scope_friendly_name }}</span> {% endfor %}
51
51
  </div>
@@ -58,7 +58,7 @@
58
58
  {% if tokens|length > 14 %}
59
59
  <form method="post">
60
60
  <div class="card text-white bg-secondary m-2 sso-card">
61
- <div class="card-header text-center">{% trans "New Character" %}</div>
61
+ <div class="card-header text-center">{% translate "New Character" %}</div>
62
62
  <div class="card-body text-center d-none d-sm-block">
63
63
  <input type="image" formmethod="post" class="ra-avatar img-responsive character-image rounded-lg"
64
64
  src="https://images.evetech.net/characters/1/portrait?size=256">
@@ -68,7 +68,7 @@
68
68
  <input type="hidden" name="_add" value="True">
69
69
  <a href="#" type="submit" class="m-1">
70
70
  <input type="image" name="go" src="{% static 'esi\img\EVE_SSO_Login_Buttons_Small_Black.png'%}"
71
- alt="{% trans "Add Token" %}">
71
+ alt="{% translate "Add Token" %}">
72
72
  </a>
73
73
  </div>
74
74
  </div>
@@ -85,8 +85,8 @@
85
85
  </div>
86
86
  <div class="card-footer">
87
87
  <input type="hidden" name="_token" value="{{ token.pk }}">
88
- <button class="btn btn-success w-100" type="submit" title="{% trans "Select" %}" formmethod="post">
89
- {% trans "Select" %}
88
+ <button class="btn btn-success w-100" type="submit" title="{% translate "Select" %}" formmethod="post">
89
+ {% translate "Select" %}
90
90
  </button>
91
91
  </div>
92
92
  </div>
@@ -95,7 +95,7 @@
95
95
  <form method="post">
96
96
  {% csrf_token %}
97
97
  <div class="card text-white bg-secondary m-2 sso-card">
98
- <div class="card-header text-center">{% trans "New Character" %}</div>
98
+ <div class="card-header text-center">{% translate "New Character" %}</div>
99
99
  <div class="card-body text-center d-none d-sm-block">
100
100
  <input type="image" formmethod="post" class="ra-avatar img-responsive character-image rounded-lg"
101
101
  src="https://images.evetech.net/characters/1/portrait?size=256">
@@ -104,7 +104,7 @@
104
104
  <input type="hidden" name="_add" value="True">
105
105
  <a href="#" type="submit" class="m-1">
106
106
  <input type="image" name="go" src="{% static 'esi\img\EVE_SSO_Login_Buttons_Small_Black.png'%}"
107
- alt="{% trans "Add Token" %}">
107
+ alt="{% translate "Add Token" %}">
108
108
  </a>
109
109
  </div>
110
110
  </div>
@@ -43,5 +43,5 @@ class BravadoResponseStub:
43
43
  self.headers = headers if headers else dict()
44
44
  self.raw_bytes = raw_bytes
45
45
 
46
- def __str__(self):
46
+ def __str__(self) -> str:
47
47
  return f"{self.status_code} {self.reason}"