django-esi 8.1.0__py3-none-any.whl

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.
Files changed (100) hide show
  1. django_esi-8.1.0.dist-info/METADATA +93 -0
  2. django_esi-8.1.0.dist-info/RECORD +100 -0
  3. django_esi-8.1.0.dist-info/WHEEL +4 -0
  4. django_esi-8.1.0.dist-info/licenses/LICENSE +674 -0
  5. esi/__init__.py +7 -0
  6. esi/admin.py +42 -0
  7. esi/aiopenapi3/client.py +79 -0
  8. esi/aiopenapi3/plugins.py +224 -0
  9. esi/app_settings.py +112 -0
  10. esi/apps.py +11 -0
  11. esi/checks.py +56 -0
  12. esi/clients.py +657 -0
  13. esi/decorators.py +271 -0
  14. esi/errors.py +22 -0
  15. esi/exceptions.py +51 -0
  16. esi/helpers.py +63 -0
  17. esi/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  18. esi/locale/cs_CZ/LC_MESSAGES/django.po +53 -0
  19. esi/locale/de/LC_MESSAGES/django.mo +0 -0
  20. esi/locale/de/LC_MESSAGES/django.po +58 -0
  21. esi/locale/en/LC_MESSAGES/django.mo +0 -0
  22. esi/locale/en/LC_MESSAGES/django.po +54 -0
  23. esi/locale/es/LC_MESSAGES/django.mo +0 -0
  24. esi/locale/es/LC_MESSAGES/django.po +59 -0
  25. esi/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  26. esi/locale/fr_FR/LC_MESSAGES/django.po +59 -0
  27. esi/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  28. esi/locale/it_IT/LC_MESSAGES/django.po +59 -0
  29. esi/locale/ja/LC_MESSAGES/django.mo +0 -0
  30. esi/locale/ja/LC_MESSAGES/django.po +58 -0
  31. esi/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  32. esi/locale/ko_KR/LC_MESSAGES/django.po +58 -0
  33. esi/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  34. esi/locale/nl_NL/LC_MESSAGES/django.po +53 -0
  35. esi/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  36. esi/locale/pl_PL/LC_MESSAGES/django.po +53 -0
  37. esi/locale/ru/LC_MESSAGES/django.mo +0 -0
  38. esi/locale/ru/LC_MESSAGES/django.po +61 -0
  39. esi/locale/sk/LC_MESSAGES/django.mo +0 -0
  40. esi/locale/sk/LC_MESSAGES/django.po +55 -0
  41. esi/locale/uk/LC_MESSAGES/django.mo +0 -0
  42. esi/locale/uk/LC_MESSAGES/django.po +57 -0
  43. esi/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  44. esi/locale/zh_Hans/LC_MESSAGES/django.po +58 -0
  45. esi/management/commands/__init__.py +0 -0
  46. esi/management/commands/esi_clear_spec_cache.py +21 -0
  47. esi/management/commands/generate_esi_stubs.py +661 -0
  48. esi/management/commands/migrate_to_ssov2.py +188 -0
  49. esi/managers.py +303 -0
  50. esi/managers.pyi +85 -0
  51. esi/migrations/0001_initial.py +55 -0
  52. esi/migrations/0002_scopes_20161208.py +56 -0
  53. esi/migrations/0003_hide_tokens_from_admin_site.py +23 -0
  54. esi/migrations/0004_remove_unique_access_token.py +18 -0
  55. esi/migrations/0005_remove_token_length_limit.py +23 -0
  56. esi/migrations/0006_remove_url_length_limit.py +18 -0
  57. esi/migrations/0007_fix_mysql_8_migration.py +18 -0
  58. esi/migrations/0008_nullable_refresh_token.py +18 -0
  59. esi/migrations/0009_set_old_tokens_to_sso_v1.py +18 -0
  60. esi/migrations/0010_set_new_tokens_to_sso_v2.py +18 -0
  61. esi/migrations/0011_add_token_indices.py +28 -0
  62. esi/migrations/0012_fix_token_type_choices.py +18 -0
  63. esi/migrations/0013_squashed_0012_fix_token_type_choices.py +57 -0
  64. esi/migrations/__init__.py +0 -0
  65. esi/models.py +349 -0
  66. esi/openapi_clients.py +1225 -0
  67. esi/rate_limiting.py +107 -0
  68. esi/signals.py +21 -0
  69. esi/static/esi/img/EVE_SSO_Login_Buttons_Large_Black.png +0 -0
  70. esi/static/esi/img/EVE_SSO_Login_Buttons_Large_White.png +0 -0
  71. esi/static/esi/img/EVE_SSO_Login_Buttons_Small_Black.png +0 -0
  72. esi/static/esi/img/EVE_SSO_Login_Buttons_Small_White.png +0 -0
  73. esi/stubs.py +2 -0
  74. esi/stubs.pyi +6807 -0
  75. esi/tasks.py +78 -0
  76. esi/templates/esi/select_token.html +116 -0
  77. esi/templatetags/__init__.py +0 -0
  78. esi/templatetags/scope_tags.py +8 -0
  79. esi/tests/__init__.py +134 -0
  80. esi/tests/client_authed_pilot.py +63 -0
  81. esi/tests/client_public_pilot.py +53 -0
  82. esi/tests/factories.py +47 -0
  83. esi/tests/factories_2.py +60 -0
  84. esi/tests/jwt_factory.py +135 -0
  85. esi/tests/test_checks.py +48 -0
  86. esi/tests/test_clients.py +1019 -0
  87. esi/tests/test_decorators.py +578 -0
  88. esi/tests/test_management_command.py +307 -0
  89. esi/tests/test_managers.py +673 -0
  90. esi/tests/test_models.py +403 -0
  91. esi/tests/test_openapi.json +854 -0
  92. esi/tests/test_openapi.py +1017 -0
  93. esi/tests/test_swagger.json +489 -0
  94. esi/tests/test_swagger_full.json +51112 -0
  95. esi/tests/test_tasks.py +116 -0
  96. esi/tests/test_templatetags.py +22 -0
  97. esi/tests/test_views.py +331 -0
  98. esi/tests/threading_pilot.py +69 -0
  99. esi/urls.py +9 -0
  100. esi/views.py +129 -0
esi/decorators.py ADDED
@@ -0,0 +1,271 @@
1
+ import logging
2
+ import time
3
+ from functools import wraps
4
+ from typing import Any
5
+ from collections.abc import Callable
6
+
7
+ from django.core.cache import cache
8
+
9
+ from esi.rate_limiting import ESIRateLimitBucket, ESIRateLimits
10
+
11
+ from .models import CallbackRedirect, Token
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def _check_callback(request) -> Token | None:
17
+ # ensure session installed in database
18
+ if not request.session.exists(request.session.session_key):
19
+ logger.debug("Creating new session for %s", request.user)
20
+ request.session.create()
21
+
22
+ # clean up callback redirect, pass token if new requested
23
+ try:
24
+ model = CallbackRedirect.objects.get(session_key=request.session.session_key)
25
+ token = Token.objects.get(pk=model.token.pk)
26
+ model.delete()
27
+ logger.debug(
28
+ "Retrieved new token from callback for %s session %s",
29
+ request.user,
30
+ request.session.session_key[:5])
31
+ return token
32
+
33
+ except (CallbackRedirect.DoesNotExist, Token.DoesNotExist, AttributeError):
34
+ logger.debug(
35
+ "No callback for %s session %s",
36
+ request.user,
37
+ request.session.session_key[:5],
38
+ exc_info=True
39
+ )
40
+ return None
41
+
42
+
43
+ def tokens_required(scopes='', new=False):
44
+ """
45
+ Decorator for views to request an ESI Token.
46
+ Accepts required scopes as a space-delimited string
47
+ or list of strings of scope names.
48
+ Can require a new token to be retrieved by SSO.
49
+ Returns a QueryDict of Tokens.
50
+ """
51
+
52
+ def decorator(view_func):
53
+ @wraps(view_func)
54
+ def _wrapped_view(request, *args, **kwargs):
55
+
56
+ # if we're coming back from SSO with a new token, return it
57
+ token = _check_callback(request)
58
+ if token:
59
+ tokens = Token.objects.filter(pk=token.pk)
60
+ logger.debug("Returning new token.")
61
+ return view_func(request, tokens, *args, **kwargs)
62
+
63
+ if not new:
64
+ # ensure user logged in to check existing tokens
65
+ if not request.user.is_authenticated:
66
+ logger.debug(
67
+ "Session %s is not logged in. Redirecting to login.",
68
+ request.session.session_key[:5]
69
+ )
70
+ from django.contrib.auth.views import redirect_to_login
71
+ return redirect_to_login(request.get_full_path())
72
+
73
+ # collect tokens in db, check if still valid, return if any
74
+ tokens = (
75
+ Token.objects
76
+ .filter(user__pk=request.user.pk)
77
+ .require_scopes(scopes)
78
+ .require_valid()
79
+ )
80
+ if tokens.exists():
81
+ logger.debug(
82
+ "Retrieved %s tokens for %s session %s",
83
+ tokens.count(),
84
+ request.user,
85
+ request.session.session_key[:5]
86
+ )
87
+ return view_func(request, tokens, *args, **kwargs)
88
+
89
+ # trigger creation of new token via sso
90
+ logger.debug(
91
+ "No tokens identified for %s session %s. Redirecting to SSO.",
92
+ request.user,
93
+ request.session.session_key[:5]
94
+ )
95
+ from esi.views import sso_redirect
96
+ return sso_redirect(request, scopes=scopes)
97
+
98
+ return _wrapped_view
99
+
100
+ return decorator
101
+
102
+
103
+ def token_required(scopes='', new=False):
104
+ """
105
+ Decorator for views which supplies a single,
106
+ user-selected token for the view to process.
107
+ Same parameters as tokens_required.
108
+ """
109
+
110
+ def decorator(view_func):
111
+ @wraps(view_func)
112
+ def _wrapped_view(request, *args, **kwargs):
113
+
114
+ # if we're coming back from SSO with a new token, return it
115
+ token = _check_callback(request)
116
+ if token:
117
+ logger.debug(
118
+ "Got new token from %s session %s. Returning to view.",
119
+ request.user,
120
+ request.session.session_key[:5]
121
+ )
122
+ return view_func(request, token, *args, **kwargs)
123
+
124
+ # if we're selecting a token, return it
125
+ if request.method == 'POST':
126
+ if request.POST.get("_add", False):
127
+ logger.debug(
128
+ "%s has selected to add new token. Redirecting to SSO.",
129
+ request.user
130
+ )
131
+ # user has selected to add a new token
132
+ from esi.views import sso_redirect
133
+ return sso_redirect(request, scopes=scopes)
134
+
135
+ token_pk = request.POST.get('_token', None)
136
+ if token_pk:
137
+ logger.debug(
138
+ "%s has selected token %s", request.user, token_pk
139
+ )
140
+ try:
141
+ token = Token.objects.get(pk=token_pk)
142
+ # ensure token belongs to this user and has required scopes
143
+ if (
144
+ (
145
+ (token.user and token.user == request.user)
146
+ or not token.user
147
+ )
148
+ and Token.objects
149
+ .filter(pk=token_pk)
150
+ .require_scopes(scopes)
151
+ .require_valid()
152
+ .exists()
153
+ ):
154
+ logger.debug(
155
+ "Selected token fulfills requirements of view. "
156
+ "Returning."
157
+ )
158
+ return view_func(request, token, *args, **kwargs)
159
+
160
+ except Token.DoesNotExist:
161
+ logger.debug("Token %s not found.", token_pk)
162
+
163
+ if not new:
164
+ # present the user with token choices
165
+ tokens = (
166
+ Token.objects
167
+ .filter(user__pk=request.user.pk)
168
+ .require_scopes(scopes)
169
+ .require_valid()
170
+ )
171
+ if tokens.exists():
172
+ logger.debug(
173
+ "Returning list of available tokens for %s.", request.user
174
+ )
175
+ from esi.views import select_token
176
+ return select_token(request, scopes=scopes, new=new)
177
+ else:
178
+ logger.debug(
179
+ "No tokens found for %s session %s with scopes %s",
180
+ request.user,
181
+ request.session.session_key[:5],
182
+ scopes
183
+ )
184
+
185
+ # prompt the user to add a new token
186
+ logger.debug(
187
+ "Redirecting %s session %s to SSO.",
188
+ request.user,
189
+ request.session.session_key[:5]
190
+ )
191
+ from esi.views import sso_redirect
192
+ return sso_redirect(request, scopes=scopes)
193
+
194
+ return _wrapped_view
195
+
196
+ return decorator
197
+
198
+
199
+ def single_use_token(scopes='', new=False):
200
+ """
201
+ Decorator for views which supplies a single use token granted via sso login
202
+ regardless of login state.
203
+ Same parameters as tokens_required.
204
+ """
205
+
206
+ def decorator(view_func):
207
+ @wraps(view_func)
208
+ def _wrapped_view(request, *args, **kwargs):
209
+
210
+ # if we're coming back from SSO for a new token, return it
211
+ token = _check_callback(request)
212
+ if token:
213
+ logger.debug(
214
+ "Got new token from session %s. Returning to view.",
215
+ request.session.session_key[:5]
216
+ )
217
+ return view_func(request, token, *args, **kwargs)
218
+
219
+ # prompt the user to login for a new token
220
+ logger.debug(
221
+ "Redirecting session %s to SSO.", request.session.session_key[:5]
222
+ )
223
+ from esi.views import sso_redirect
224
+ return sso_redirect(request, scopes=scopes)
225
+
226
+ return _wrapped_view
227
+
228
+ return decorator
229
+
230
+
231
+ def wait_for_esi_errorlimit_reset(cache_key="esi_error_limit_reset", poll_interval=1) -> Callable[..., Callable[..., Any]]:
232
+ """
233
+ Decorator to apply a polling sleep while the ESI Server/Client is in an Error Limit state
234
+ The preferred non-blocking method is to retry your tasks after the limit reset time has passed
235
+
236
+ Args:
237
+ cache_key (str, optional): NOT USUALLY CHANGED. Defaults to "esi_error_limit_reset".
238
+ poll_interval (int, optional): Interval in seconds to poll redis. Defaults to 1.
239
+ """
240
+ def decorator(func):
241
+ def wrapper(*args, **kwargs):
242
+ reset = cache.get(cache_key)
243
+ if reset is not None:
244
+ logger.error(f"ESI Error Limited, waiting {reset}s before retrying...")
245
+ while cache.get(cache_key):
246
+ time.sleep(poll_interval)
247
+ return func(*args, **kwargs)
248
+ return wrapper
249
+ return decorator
250
+
251
+
252
+ def esi_rate_limiter_bucketed(bucket: ESIRateLimitBucket, raise_on_limit: bool = True):
253
+ """
254
+ Decorator for custom manual rate limits on some endpoints to apply a polling sleep while the bucket is exhausted.
255
+ MARKET_DATA_HISTORY
256
+ CHARACTER_CORPORATION_HISTORY
257
+ The preferred non-blocking method is to retry your tasks after the limit reset time has passed
258
+
259
+
260
+ Args:
261
+ bucket (ESIRateLimitBucket): The Bucket to rate limit against
262
+ raise_on_limit (bool, optional): Whether to raise an Exception when the limit is reached. Defaults to True.
263
+ """
264
+ # TODO Investigate esi cache hits.
265
+ def decorator(func):
266
+ @wraps(func)
267
+ def wrapper(*args, **kwargs):
268
+ ESIRateLimits.check_decr_bucket(bucket, raise_on_limit)
269
+ return func(*args, **kwargs)
270
+ return wrapper
271
+ return decorator
esi/errors.py ADDED
@@ -0,0 +1,22 @@
1
+ class DjangoEsiException(Exception):
2
+ pass
3
+
4
+
5
+ class TokenError(DjangoEsiException):
6
+ pass
7
+
8
+
9
+ class TokenInvalidError(TokenError):
10
+ pass
11
+
12
+
13
+ class TokenExpiredError(TokenError):
14
+ pass
15
+
16
+
17
+ class NotRefreshableTokenError(TokenError):
18
+ pass
19
+
20
+
21
+ class IncompleteResponseError(DjangoEsiException):
22
+ pass
esi/exceptions.py ADDED
@@ -0,0 +1,51 @@
1
+ import dataclasses
2
+
3
+ from aiopenapi3.errors import HTTPServerError as base_HTTPServerError
4
+ from aiopenapi3.errors import HTTPClientError as base_HTTPClientError
5
+ from aiopenapi3.errors import HTTPError
6
+
7
+
8
+ class ESIErrorLimitException(Exception):
9
+ """ESI Global Error Limit Exceeded
10
+ https://developers.eveonline.com/docs/services/esi/best-practices/#error-limit
11
+ """
12
+
13
+ def __init__(self, reset=None, *args, **kwargs) -> None:
14
+ self.reset = reset
15
+ msg = kwargs.get("message") or (
16
+ f"ESI Error limited. Reset in {reset} seconds." if reset else "ESI Error limited."
17
+ )
18
+ super().__init__(msg, *args)
19
+
20
+
21
+ class ESIBucketLimitException(Exception):
22
+ """Endpoint (Bucket) Specific Rate Limit Exceeded"""
23
+
24
+ def __init__(self, bucket, reset: float = 0, *args, **kwargs) -> None:
25
+ self.bucket = bucket
26
+ self.reset = reset
27
+ msg = kwargs.get("message") or f"ESI bucket limit reached for {bucket}."
28
+ super().__init__(msg, *args)
29
+
30
+
31
+ @dataclasses.dataclass(repr=False)
32
+ class HTTPNotModified(HTTPError):
33
+ """The HTTP Status is 304"""
34
+
35
+ status_code: int
36
+ headers: dict[str, str]
37
+
38
+ def __str__(self):
39
+ return f"""<{self.__class__.__name__} {self.status_code} {self.headers}>"""
40
+
41
+
42
+ @dataclasses.dataclass(repr=False)
43
+ class HTTPClientError(base_HTTPClientError):
44
+ """HTTP Response Code 4xx"""
45
+ pass
46
+
47
+
48
+ @dataclasses.dataclass(repr=False)
49
+ class HTTPServerError(base_HTTPServerError):
50
+ """HTTP Response Code 5xx"""
51
+ pass
esi/helpers.py ADDED
@@ -0,0 +1,63 @@
1
+ from esi.models import Token
2
+ from string import capwords
3
+
4
+
5
+ def get_token(character_id: int, scopes: list) -> Token:
6
+ """Helper method to get a valid token for a specific character with specific scopes.
7
+
8
+ Args:
9
+ character_id: Character to filter on.
10
+ scopes: array of ESI scope strings to search for.
11
+
12
+ Returns:
13
+ Matching Token
14
+ """
15
+ qs = (
16
+ Token.objects
17
+ .filter(character_id=character_id)
18
+ .require_scopes(scopes)
19
+ .require_valid()
20
+ )
21
+ token = qs.first()
22
+ if token is None:
23
+ raise Token.DoesNotExist(
24
+ f"No valid token found for character_id={character_id} with required scopes."
25
+ )
26
+ return token
27
+
28
+
29
+ def pascal_case_string(string: str) -> str:
30
+ """
31
+ Convert a string to PascalCase by capitalizing the first letter of each word and removing spaces,
32
+ but only if the string contains spaces or hyphens.
33
+
34
+ This function checks if the input string contains spaces or hyphens. If so, it replaces hyphens with spaces,
35
+ capitalizes the first letter of each word, removes the spaces, and returns the resulting PascalCase string.
36
+ If the input string does not contain spaces or hyphens, it is returned unchanged.
37
+
38
+ Behaviour:
39
+ Any string containing spaces or hyphens will be converted to PascalCase.
40
+ Strings without spaces or hyphens will be returned unchanged.
41
+ This gives you the opportunity to use already formatted strings as needed.
42
+
43
+ Examples:
44
+ - "app name" -> "AppName"
45
+ - "app-name" -> "AppName"
46
+ - "appname" -> "appname"
47
+ - "AppName" -> "AppName"
48
+ - "appName" -> "appName"
49
+ - "app_name" -> "app_name"
50
+
51
+ :param string: The input string to be converted to PascalCase.
52
+ :type string: str
53
+ :return: The PascalCase formatted string, or the original string if no spaces or hyphens are present.
54
+ :rtype: str
55
+ """
56
+
57
+ # Check if the string contains spaces or hyphens
58
+ if any(c in string for c in (" ", "-")):
59
+ # Replace hyphens with spaces, capitalize each word, and remove spaces
60
+ return capwords(string.replace("-", " ")).replace(" ", "")
61
+
62
+ # Return the original string if no spaces or hyphens are present
63
+ return string
Binary file
@@ -0,0 +1,53 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: Django ESI 8.1.0\n"
10
+ "Report-Msgid-Bugs-To: https://gitlab.com/allianceauth/django-esi/-/issues\n"
11
+ "POT-Creation-Date: 2025-11-19 16:22+1000\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "Language: \n"
16
+ "MIME-Version: 1.0\n"
17
+ "Content-Type: text/plain; charset=UTF-8\n"
18
+ "Content-Transfer-Encoding: 8bit\n"
19
+
20
+ #: esi/models.py:66
21
+ msgid "Character"
22
+ msgstr ""
23
+
24
+ #: esi/models.py:67
25
+ msgid "Corporation"
26
+ msgstr ""
27
+
28
+ #: esi/templates/esi/select_token.html:13
29
+ msgid "ESI Token Selection"
30
+ msgstr ""
31
+
32
+ #: esi/templates/esi/select_token.html:44
33
+ msgid "Select Character"
34
+ msgstr ""
35
+
36
+ #: esi/templates/esi/select_token.html:48
37
+ msgid "Scopes Requested"
38
+ msgstr ""
39
+
40
+ #: esi/templates/esi/select_token.html:61
41
+ #: esi/templates/esi/select_token.html:98
42
+ msgid "New Character"
43
+ msgstr ""
44
+
45
+ #: esi/templates/esi/select_token.html:71
46
+ #: esi/templates/esi/select_token.html:107
47
+ msgid "Add Token"
48
+ msgstr ""
49
+
50
+ #: esi/templates/esi/select_token.html:88
51
+ #: esi/templates/esi/select_token.html:89
52
+ msgid "Select"
53
+ msgstr ""
Binary file
@@ -0,0 +1,58 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ # Translators:
7
+ # Peter Pfeufer, 2023
8
+ #
9
+ #, fuzzy
10
+ msgid ""
11
+ msgstr ""
12
+ "Project-Id-Version: Django ESI 8.1.0\n"
13
+ "Report-Msgid-Bugs-To: https://gitlab.com/allianceauth/django-esi/-/issues\n"
14
+ "POT-Creation-Date: 2025-11-19 16:22+1000\n"
15
+ "PO-Revision-Date: 2023-10-25 11:04+0000\n"
16
+ "Last-Translator: Peter Pfeufer, 2023\n"
17
+ "Language-Team: German (https://app.transifex.com/alliance-auth/teams/107430/"
18
+ "de/)\n"
19
+ "Language: de\n"
20
+ "MIME-Version: 1.0\n"
21
+ "Content-Type: text/plain; charset=UTF-8\n"
22
+ "Content-Transfer-Encoding: 8bit\n"
23
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
24
+
25
+ #: esi/models.py:66
26
+ msgid "Character"
27
+ msgstr "Charakter"
28
+
29
+ #: esi/models.py:67
30
+ msgid "Corporation"
31
+ msgstr "Corporation"
32
+
33
+ #: esi/templates/esi/select_token.html:13
34
+ msgid "ESI Token Selection"
35
+ msgstr "ESI Token Auswahl"
36
+
37
+ #: esi/templates/esi/select_token.html:44
38
+ msgid "Select Character"
39
+ msgstr "Charakter Auswahl"
40
+
41
+ #: esi/templates/esi/select_token.html:48
42
+ msgid "Scopes Requested"
43
+ msgstr "Bereiche angefordert"
44
+
45
+ #: esi/templates/esi/select_token.html:61
46
+ #: esi/templates/esi/select_token.html:98
47
+ msgid "New Character"
48
+ msgstr "Neuer Charakter"
49
+
50
+ #: esi/templates/esi/select_token.html:71
51
+ #: esi/templates/esi/select_token.html:107
52
+ msgid "Add Token"
53
+ msgstr "Token hinzufügen"
54
+
55
+ #: esi/templates/esi/select_token.html:88
56
+ #: esi/templates/esi/select_token.html:89
57
+ msgid "Select"
58
+ msgstr "Auswählen"
Binary file
@@ -0,0 +1,54 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: Django ESI 8.1.0\n"
10
+ "Report-Msgid-Bugs-To: https://gitlab.com/allianceauth/django-esi/-/issues\n"
11
+ "POT-Creation-Date: 2025-11-19 16:22+1000\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "Language: \n"
16
+ "MIME-Version: 1.0\n"
17
+ "Content-Type: text/plain; charset=UTF-8\n"
18
+ "Content-Transfer-Encoding: 8bit\n"
19
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20
+
21
+ #: esi/models.py:66
22
+ msgid "Character"
23
+ msgstr ""
24
+
25
+ #: esi/models.py:67
26
+ msgid "Corporation"
27
+ msgstr ""
28
+
29
+ #: esi/templates/esi/select_token.html:13
30
+ msgid "ESI Token Selection"
31
+ msgstr ""
32
+
33
+ #: esi/templates/esi/select_token.html:44
34
+ msgid "Select Character"
35
+ msgstr ""
36
+
37
+ #: esi/templates/esi/select_token.html:48
38
+ msgid "Scopes Requested"
39
+ msgstr ""
40
+
41
+ #: esi/templates/esi/select_token.html:61
42
+ #: esi/templates/esi/select_token.html:98
43
+ msgid "New Character"
44
+ msgstr ""
45
+
46
+ #: esi/templates/esi/select_token.html:71
47
+ #: esi/templates/esi/select_token.html:107
48
+ msgid "Add Token"
49
+ msgstr ""
50
+
51
+ #: esi/templates/esi/select_token.html:88
52
+ #: esi/templates/esi/select_token.html:89
53
+ msgid "Select"
54
+ msgstr ""
Binary file
@@ -0,0 +1,59 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ # Translators:
7
+ # trenus, 2023
8
+ #
9
+ #, fuzzy
10
+ msgid ""
11
+ msgstr ""
12
+ "Project-Id-Version: Django ESI 8.1.0\n"
13
+ "Report-Msgid-Bugs-To: https://gitlab.com/allianceauth/django-esi/-/issues\n"
14
+ "POT-Creation-Date: 2025-11-19 16:22+1000\n"
15
+ "PO-Revision-Date: 2023-10-25 11:04+0000\n"
16
+ "Last-Translator: trenus, 2023\n"
17
+ "Language-Team: Spanish (https://app.transifex.com/alliance-auth/teams/107430/"
18
+ "es/)\n"
19
+ "Language: es\n"
20
+ "MIME-Version: 1.0\n"
21
+ "Content-Type: text/plain; charset=UTF-8\n"
22
+ "Content-Transfer-Encoding: 8bit\n"
23
+ "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? "
24
+ "1 : 2;\n"
25
+
26
+ #: esi/models.py:66
27
+ msgid "Character"
28
+ msgstr ""
29
+
30
+ #: esi/models.py:67
31
+ msgid "Corporation"
32
+ msgstr ""
33
+
34
+ #: esi/templates/esi/select_token.html:13
35
+ msgid "ESI Token Selection"
36
+ msgstr "Selección de tokens ESI"
37
+
38
+ #: esi/templates/esi/select_token.html:44
39
+ msgid "Select Character"
40
+ msgstr "Seleccionar personaje"
41
+
42
+ #: esi/templates/esi/select_token.html:48
43
+ msgid "Scopes Requested"
44
+ msgstr "Ámbitos solicitados"
45
+
46
+ #: esi/templates/esi/select_token.html:61
47
+ #: esi/templates/esi/select_token.html:98
48
+ msgid "New Character"
49
+ msgstr "Nuevo personaje"
50
+
51
+ #: esi/templates/esi/select_token.html:71
52
+ #: esi/templates/esi/select_token.html:107
53
+ msgid "Add Token"
54
+ msgstr ""
55
+
56
+ #: esi/templates/esi/select_token.html:88
57
+ #: esi/templates/esi/select_token.html:89
58
+ msgid "Select"
59
+ msgstr "Seleccionar"
Binary file