django-nativemojo 0.1.15__py3-none-any.whl → 0.1.17__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 (221) hide show
  1. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/METADATA +3 -2
  2. django_nativemojo-0.1.17.dist-info/RECORD +302 -0
  3. mojo/__init__.py +1 -1
  4. mojo/apps/account/management/commands/serializer_admin.py +121 -1
  5. mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
  6. mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
  7. mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
  8. mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
  9. mojo/apps/account/migrations/0010_group_avatar.py +20 -0
  10. mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
  11. mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
  12. mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
  13. mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
  14. mojo/apps/account/models/__init__.py +2 -0
  15. mojo/apps/account/models/device.py +279 -0
  16. mojo/apps/account/models/group.py +294 -8
  17. mojo/apps/account/models/member.py +14 -1
  18. mojo/apps/account/models/push/__init__.py +4 -0
  19. mojo/apps/account/models/push/config.py +112 -0
  20. mojo/apps/account/models/push/delivery.py +93 -0
  21. mojo/apps/account/models/push/device.py +66 -0
  22. mojo/apps/account/models/push/template.py +99 -0
  23. mojo/apps/account/models/user.py +190 -17
  24. mojo/apps/account/rest/__init__.py +2 -0
  25. mojo/apps/account/rest/device.py +39 -0
  26. mojo/apps/account/rest/group.py +8 -0
  27. mojo/apps/account/rest/push.py +187 -0
  28. mojo/apps/account/rest/user.py +95 -5
  29. mojo/apps/account/services/__init__.py +1 -0
  30. mojo/apps/account/services/push.py +363 -0
  31. mojo/apps/aws/migrations/0001_initial.py +206 -0
  32. mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
  33. mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
  34. mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
  35. mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
  36. mojo/apps/aws/models/__init__.py +19 -0
  37. mojo/apps/aws/models/email_attachment.py +99 -0
  38. mojo/apps/aws/models/email_domain.py +218 -0
  39. mojo/apps/aws/models/email_template.py +132 -0
  40. mojo/apps/aws/models/incoming_email.py +197 -0
  41. mojo/apps/aws/models/mailbox.py +288 -0
  42. mojo/apps/aws/models/sent_message.py +175 -0
  43. mojo/apps/aws/rest/__init__.py +6 -0
  44. mojo/apps/aws/rest/email.py +33 -0
  45. mojo/apps/aws/rest/email_ops.py +183 -0
  46. mojo/apps/aws/rest/messages.py +32 -0
  47. mojo/apps/aws/rest/send.py +101 -0
  48. mojo/apps/aws/rest/sns.py +403 -0
  49. mojo/apps/aws/rest/templates.py +19 -0
  50. mojo/apps/aws/services/__init__.py +32 -0
  51. mojo/apps/aws/services/email.py +390 -0
  52. mojo/apps/aws/services/email_ops.py +548 -0
  53. mojo/apps/docit/__init__.py +6 -0
  54. mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
  55. mojo/apps/docit/markdown_plugins/toc.py +12 -0
  56. mojo/apps/docit/migrations/0001_initial.py +113 -0
  57. mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
  58. mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
  59. mojo/apps/docit/models/__init__.py +17 -0
  60. mojo/apps/docit/models/asset.py +231 -0
  61. mojo/apps/docit/models/book.py +227 -0
  62. mojo/apps/docit/models/page.py +319 -0
  63. mojo/apps/docit/models/page_revision.py +203 -0
  64. mojo/apps/docit/rest/__init__.py +10 -0
  65. mojo/apps/docit/rest/asset.py +17 -0
  66. mojo/apps/docit/rest/book.py +22 -0
  67. mojo/apps/docit/rest/page.py +22 -0
  68. mojo/apps/docit/rest/page_revision.py +17 -0
  69. mojo/apps/docit/services/__init__.py +11 -0
  70. mojo/apps/docit/services/docit.py +315 -0
  71. mojo/apps/docit/services/markdown.py +44 -0
  72. mojo/apps/fileman/backends/s3.py +209 -0
  73. mojo/apps/fileman/models/file.py +45 -9
  74. mojo/apps/fileman/models/manager.py +269 -3
  75. mojo/apps/incident/migrations/0007_event_uid.py +18 -0
  76. mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
  77. mojo/apps/incident/migrations/0009_incident_status.py +18 -0
  78. mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
  79. mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
  80. mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
  81. mojo/apps/incident/models/__init__.py +1 -0
  82. mojo/apps/incident/models/event.py +35 -0
  83. mojo/apps/incident/models/incident.py +2 -0
  84. mojo/apps/incident/models/ticket.py +62 -0
  85. mojo/apps/incident/reporter.py +21 -3
  86. mojo/apps/incident/rest/__init__.py +1 -0
  87. mojo/apps/incident/rest/ticket.py +43 -0
  88. mojo/apps/jobs/__init__.py +489 -0
  89. mojo/apps/jobs/adapters.py +24 -0
  90. mojo/apps/jobs/cli.py +616 -0
  91. mojo/apps/jobs/daemon.py +370 -0
  92. mojo/apps/jobs/examples/sample_jobs.py +376 -0
  93. mojo/apps/jobs/examples/webhook_examples.py +203 -0
  94. mojo/apps/jobs/handlers/__init__.py +5 -0
  95. mojo/apps/jobs/handlers/webhook.py +317 -0
  96. mojo/apps/jobs/job_engine.py +734 -0
  97. mojo/apps/jobs/keys.py +203 -0
  98. mojo/apps/jobs/local_queue.py +363 -0
  99. mojo/apps/jobs/management/__init__.py +3 -0
  100. mojo/apps/jobs/management/commands/__init__.py +3 -0
  101. mojo/apps/jobs/manager.py +1327 -0
  102. mojo/apps/jobs/migrations/0001_initial.py +97 -0
  103. mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
  104. mojo/apps/jobs/models/__init__.py +6 -0
  105. mojo/apps/jobs/models/job.py +441 -0
  106. mojo/apps/jobs/rest/__init__.py +2 -0
  107. mojo/apps/jobs/rest/control.py +466 -0
  108. mojo/apps/jobs/rest/jobs.py +421 -0
  109. mojo/apps/jobs/scheduler.py +571 -0
  110. mojo/apps/jobs/services/__init__.py +6 -0
  111. mojo/apps/jobs/services/job_actions.py +465 -0
  112. mojo/apps/jobs/settings.py +209 -0
  113. mojo/apps/logit/models/log.py +3 -0
  114. mojo/apps/metrics/__init__.py +8 -1
  115. mojo/apps/metrics/redis_metrics.py +198 -0
  116. mojo/apps/metrics/rest/__init__.py +3 -0
  117. mojo/apps/metrics/rest/categories.py +266 -0
  118. mojo/apps/metrics/rest/helpers.py +48 -0
  119. mojo/apps/metrics/rest/permissions.py +99 -0
  120. mojo/apps/metrics/rest/values.py +277 -0
  121. mojo/apps/metrics/utils.py +17 -0
  122. mojo/decorators/http.py +40 -1
  123. mojo/helpers/aws/__init__.py +11 -7
  124. mojo/helpers/aws/inbound_email.py +309 -0
  125. mojo/helpers/aws/kms.py +413 -0
  126. mojo/helpers/aws/ses_domain.py +959 -0
  127. mojo/helpers/crypto/__init__.py +1 -1
  128. mojo/helpers/crypto/utils.py +15 -0
  129. mojo/helpers/location/__init__.py +2 -0
  130. mojo/helpers/location/countries.py +262 -0
  131. mojo/helpers/location/geolocation.py +196 -0
  132. mojo/helpers/logit.py +37 -0
  133. mojo/helpers/redis/__init__.py +2 -0
  134. mojo/helpers/redis/adapter.py +606 -0
  135. mojo/helpers/redis/client.py +48 -0
  136. mojo/helpers/redis/pool.py +225 -0
  137. mojo/helpers/request.py +8 -0
  138. mojo/helpers/response.py +8 -0
  139. mojo/middleware/auth.py +1 -1
  140. mojo/middleware/cors.py +40 -0
  141. mojo/middleware/logging.py +131 -12
  142. mojo/middleware/mojo.py +5 -0
  143. mojo/models/rest.py +271 -57
  144. mojo/models/secrets.py +86 -0
  145. mojo/serializers/__init__.py +16 -10
  146. mojo/serializers/core/__init__.py +90 -0
  147. mojo/serializers/core/cache/__init__.py +121 -0
  148. mojo/serializers/core/cache/backends.py +518 -0
  149. mojo/serializers/core/cache/base.py +102 -0
  150. mojo/serializers/core/cache/disabled.py +181 -0
  151. mojo/serializers/core/cache/memory.py +287 -0
  152. mojo/serializers/core/cache/redis.py +533 -0
  153. mojo/serializers/core/cache/utils.py +454 -0
  154. mojo/serializers/{manager.py → core/manager.py} +53 -4
  155. mojo/serializers/core/serializer.py +475 -0
  156. mojo/serializers/{advanced/formats → formats}/csv.py +116 -139
  157. mojo/serializers/suggested_improvements.md +388 -0
  158. testit/client.py +1 -1
  159. testit/helpers.py +14 -0
  160. testit/runner.py +23 -6
  161. django_nativemojo-0.1.15.dist-info/RECORD +0 -234
  162. mojo/apps/notify/README.md +0 -91
  163. mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
  164. mojo/apps/notify/admin.py +0 -52
  165. mojo/apps/notify/handlers/example_handlers.py +0 -516
  166. mojo/apps/notify/handlers/ses/__init__.py +0 -25
  167. mojo/apps/notify/handlers/ses/complaint.py +0 -25
  168. mojo/apps/notify/handlers/ses/message.py +0 -86
  169. mojo/apps/notify/management/commands/__init__.py +0 -1
  170. mojo/apps/notify/management/commands/process_notifications.py +0 -370
  171. mojo/apps/notify/mod +0 -0
  172. mojo/apps/notify/models/__init__.py +0 -12
  173. mojo/apps/notify/models/account.py +0 -128
  174. mojo/apps/notify/models/attachment.py +0 -24
  175. mojo/apps/notify/models/bounce.py +0 -68
  176. mojo/apps/notify/models/complaint.py +0 -40
  177. mojo/apps/notify/models/inbox.py +0 -113
  178. mojo/apps/notify/models/inbox_message.py +0 -173
  179. mojo/apps/notify/models/outbox.py +0 -129
  180. mojo/apps/notify/models/outbox_message.py +0 -288
  181. mojo/apps/notify/models/template.py +0 -30
  182. mojo/apps/notify/providers/aws.py +0 -73
  183. mojo/apps/notify/rest/ses.py +0 -0
  184. mojo/apps/notify/utils/__init__.py +0 -2
  185. mojo/apps/notify/utils/notifications.py +0 -404
  186. mojo/apps/notify/utils/parsing.py +0 -202
  187. mojo/apps/notify/utils/render.py +0 -144
  188. mojo/apps/tasks/README.md +0 -118
  189. mojo/apps/tasks/__init__.py +0 -44
  190. mojo/apps/tasks/manager.py +0 -644
  191. mojo/apps/tasks/rest/__init__.py +0 -2
  192. mojo/apps/tasks/rest/hooks.py +0 -0
  193. mojo/apps/tasks/rest/tasks.py +0 -76
  194. mojo/apps/tasks/runner.py +0 -439
  195. mojo/apps/tasks/task.py +0 -99
  196. mojo/apps/tasks/tq_handlers.py +0 -132
  197. mojo/helpers/crypto/__pycache__/hash.cpython-310.pyc +0 -0
  198. mojo/helpers/crypto/__pycache__/sign.cpython-310.pyc +0 -0
  199. mojo/helpers/crypto/__pycache__/utils.cpython-310.pyc +0 -0
  200. mojo/helpers/redis.py +0 -10
  201. mojo/models/meta.py +0 -262
  202. mojo/serializers/advanced/README.md +0 -363
  203. mojo/serializers/advanced/__init__.py +0 -247
  204. mojo/serializers/advanced/formats/__init__.py +0 -28
  205. mojo/serializers/advanced/formats/excel.py +0 -516
  206. mojo/serializers/advanced/formats/json.py +0 -239
  207. mojo/serializers/advanced/formats/response.py +0 -485
  208. mojo/serializers/advanced/serializer.py +0 -568
  209. mojo/serializers/optimized.py +0 -618
  210. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/LICENSE +0 -0
  211. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/NOTICE +0 -0
  212. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/WHEEL +0 -0
  213. /mojo/apps/{notify → aws/migrations}/__init__.py +0 -0
  214. /mojo/apps/{notify/handlers → docit/markdown_plugins}/__init__.py +0 -0
  215. /mojo/apps/{notify/management → docit/migrations}/__init__.py +0 -0
  216. /mojo/apps/{notify/providers → jobs/examples}/__init__.py +0 -0
  217. /mojo/apps/{notify/rest → jobs/migrations}/__init__.py +0 -0
  218. /mojo/{serializers → rest}/openapi.py +0 -0
  219. /mojo/serializers/{settings_example.py → examples/settings.py} +0 -0
  220. /mojo/{apps/notify/handlers/ses/bounce.py → serializers/formats/__init__.py} +0 -0
  221. /mojo/serializers/{advanced/formats → formats}/localizers.py +0 -0
@@ -1,4 +1,4 @@
1
1
  from .aes import encrypt, decrypt
2
- from .utils import random_bytes, random_string
2
+ from .utils import random_bytes, random_string, b64_encode, b64_decode
3
3
  from .hash import hash
4
4
  from .sign import generate_signature as sign, verify_signature as verify
@@ -1,5 +1,7 @@
1
1
  from Crypto.Random import get_random_bytes
2
2
  import string
3
+ from base64 import b64encode, b64decode
4
+ import json
3
5
 
4
6
 
5
7
  def generate_key(bit_size=128):
@@ -24,3 +26,16 @@ def random_string(length, allow_digits=True, allow_chars=True, allow_special=Tru
24
26
  raise ValueError("At least one character set (digits, chars, special) must be allowed")
25
27
  random_bytes = get_random_bytes(length)
26
28
  return ''.join(characters[b % len(characters)] for b in random_bytes)
29
+
30
+
31
+ def b64_encode(data):
32
+ if isinstance(data, dict):
33
+ data = json.dumps(data)
34
+ return b64encode(data.encode('utf-8')).decode('utf-8')
35
+
36
+
37
+ def b64_decode(data):
38
+ dec = b64decode(data.encode('utf-8')).decode('utf-8')
39
+ if dec[0] == '{':
40
+ return json.loads(dec)
41
+ return dec
@@ -0,0 +1,2 @@
1
+ # This file marks the 'location' directory as a Python package.
2
+ from .geolocation import geolocate_ip
@@ -0,0 +1,262 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Provides a simple lookup for converting ISO 3166-1 alpha-2 country codes to country names.
4
+ """
5
+
6
+ COUNTRY_CODES = {
7
+ 'AF': 'Afghanistan',
8
+ 'AX': 'Åland Islands',
9
+ 'AL': 'Albania',
10
+ 'DZ': 'Algeria',
11
+ 'AS': 'American Samoa',
12
+ 'AD': 'Andorra',
13
+ 'AO': 'Angola',
14
+ 'AI': 'Anguilla',
15
+ 'AQ': 'Antarctica',
16
+ 'AG': 'Antigua and Barbuda',
17
+ 'AR': 'Argentina',
18
+ 'AM': 'Armenia',
19
+ 'AW': 'Aruba',
20
+ 'AU': 'Australia',
21
+ 'AT': 'Austria',
22
+ 'AZ': 'Azerbaijan',
23
+ 'BS': 'Bahamas',
24
+ 'BH': 'Bahrain',
25
+ 'BD': 'Bangladesh',
26
+ 'BB': 'Barbados',
27
+ 'BY': 'Belarus',
28
+ 'BE': 'Belgium',
29
+ 'BZ': 'Belize',
30
+ 'BJ': 'Benin',
31
+ 'BM': 'Bermuda',
32
+ 'BT': 'Bhutan',
33
+ 'BO': 'Bolivia, Plurinational State of',
34
+ 'BQ': 'Bonaire, Sint Eustatius and Saba',
35
+ 'BA': 'Bosnia and Herzegovina',
36
+ 'BW': 'Botswana',
37
+ 'BV': 'Bouvet Island',
38
+ 'BR': 'Brazil',
39
+ 'IO': 'British Indian Ocean Territory',
40
+ 'BN': 'Brunei Darussalam',
41
+ 'BG': 'Bulgaria',
42
+ 'BF': 'Burkina Faso',
43
+ 'BI': 'Burundi',
44
+ 'CV': 'Cabo Verde',
45
+ 'KH': 'Cambodia',
46
+ 'CM': 'Cameroon',
47
+ 'CA': 'Canada',
48
+ 'KY': 'Cayman Islands',
49
+ 'CF': 'Central African Republic',
50
+ 'TD': 'Chad',
51
+ 'CL': 'Chile',
52
+ 'CN': 'China',
53
+ 'CX': 'Christmas Island',
54
+ 'CC': 'Cocos (Keeling) Islands',
55
+ 'CO': 'Colombia',
56
+ 'KM': 'Comoros',
57
+ 'CG': 'Congo',
58
+ 'CD': 'Congo, the Democratic Republic of the',
59
+ 'CK': 'Cook Islands',
60
+ 'CR': 'Costa Rica',
61
+ 'CI': "Côte d'Ivoire",
62
+ 'HR': 'Croatia',
63
+ 'CU': 'Cuba',
64
+ 'CW': 'Curaçao',
65
+ 'CY': 'Cyprus',
66
+ 'CZ': 'Czechia',
67
+ 'DK': 'Denmark',
68
+ 'DJ': 'Djibouti',
69
+ 'DM': 'Dominica',
70
+ 'DO': 'Dominican Republic',
71
+ 'EC': 'Ecuador',
72
+ 'EG': 'Egypt',
73
+ 'SV': 'El Salvador',
74
+ 'GQ': 'Equatorial Guinea',
75
+ 'ER': 'Eritrea',
76
+ 'EE': 'Estonia',
77
+ 'SZ': 'Eswatini',
78
+ 'ET': 'Ethiopia',
79
+ 'FK': 'Falkland Islands (Malvinas)',
80
+ 'FO': 'Faroe Islands',
81
+ 'FJ': 'Fiji',
82
+ 'FI': 'Finland',
83
+ 'FR': 'France',
84
+ 'GF': 'French Guiana',
85
+ 'PF': 'French Polynesia',
86
+ 'TF': 'French Southern Territories',
87
+ 'GA': 'Gabon',
88
+ 'GM': 'Gambia',
89
+ 'GE': 'Georgia',
90
+ 'DE': 'Germany',
91
+ 'GH': 'Ghana',
92
+ 'GI': 'Gibraltar',
93
+ 'GR': 'Greece',
94
+ 'GL': 'Greenland',
95
+ 'GD': 'Grenada',
96
+ 'GP': 'Guadeloupe',
97
+ 'GU': 'Guam',
98
+ 'GT': 'Guatemala',
99
+ 'GG': 'Guernsey',
100
+ 'GN': 'Guinea',
101
+ 'GW': 'Guinea-Bissau',
102
+ 'GY': 'Guyana',
103
+ 'HT': 'Haiti',
104
+ 'HM': 'Heard Island and McDonald Islands',
105
+ 'VA': 'Holy See',
106
+ 'HN': 'Honduras',
107
+ 'HK': 'Hong Kong',
108
+ 'HU': 'Hungary',
109
+ 'IS': 'Iceland',
110
+ 'IN': 'India',
111
+ 'ID': 'Indonesia',
112
+ 'IR': 'Iran, Islamic Republic of',
113
+ 'IQ': 'Iraq',
114
+ 'IE': 'Ireland',
115
+ 'IM': 'Isle of Man',
116
+ 'IL': 'Israel',
117
+ 'IT': 'Italy',
118
+ 'JM': 'Jamaica',
119
+ 'JP': 'Japan',
120
+ 'JE': 'Jersey',
121
+ 'JO': 'Jordan',
122
+ 'KZ': 'Kazakhstan',
123
+ 'KE': 'Kenya',
124
+ 'KI': 'Kiribati',
125
+ 'KP': "Korea, Democratic People's Republic of",
126
+ 'KR': 'Korea, Republic of',
127
+ 'KW': 'Kuwait',
128
+ 'KG': 'Kyrgyzstan',
129
+ 'LA': "Lao People's Democratic Republic",
130
+ 'LV': 'Latvia',
131
+ 'LB': 'Lebanon',
132
+ 'LS': 'Lesotho',
133
+ 'LR': 'Liberia',
134
+ 'LY': 'Libya',
135
+ 'LI': 'Liechtenstein',
136
+ 'LT': 'Lithuania',
137
+ 'LU': 'Luxembourg',
138
+ 'MO': 'Macao',
139
+ 'MG': 'Madagascar',
140
+ 'MW': 'Malawi',
141
+ 'MY': 'Malaysia',
142
+ 'MV': 'Maldives',
143
+ 'ML': 'Mali',
144
+ 'MT': 'Malta',
145
+ 'MH': 'Marshall Islands',
146
+ 'MQ': 'Martinique',
147
+ 'MR': 'Mauritania',
148
+ 'MU': 'Mauritius',
149
+ 'YT': 'Mayotte',
150
+ 'MX': 'Mexico',
151
+ 'FM': 'Micronesia, Federated States of',
152
+ 'MD': 'Moldova, Republic of',
153
+ 'MC': 'Monaco',
154
+ 'MN': 'Mongolia',
155
+ 'ME': 'Montenegro',
156
+ 'MS': 'Montserrat',
157
+ 'MA': 'Morocco',
158
+ 'MZ': 'Mozambique',
159
+ 'MM': 'Myanmar',
160
+ 'NA': 'Namibia',
161
+ 'NR': 'Nauru',
162
+ 'NP': 'Nepal',
163
+ 'NL': 'Netherlands',
164
+ 'NC': 'New Caledonia',
165
+ 'NZ': 'New Zealand',
166
+ 'NI': 'Nicaragua',
167
+ 'NE': 'Niger',
168
+ 'NG': 'Nigeria',
169
+ 'NU': 'Niue',
170
+ 'NF': 'Norfolk Island',
171
+ 'MK': 'North Macedonia',
172
+ 'MP': 'Northern Mariana Islands',
173
+ 'NO': 'Norway',
174
+ 'OM': 'Oman',
175
+ 'PK': 'Pakistan',
176
+ 'PW': 'Palau',
177
+ 'PS': 'Palestine, State of',
178
+ 'PA': 'Panama',
179
+ 'PG': 'Papua New Guinea',
180
+ 'PY': 'Paraguay',
181
+ 'PE': 'Peru',
182
+ 'PH': 'Philippines',
183
+ 'PN': 'Pitcairn',
184
+ 'PL': 'Poland',
185
+ 'PT': 'Portugal',
186
+ 'PR': 'Puerto Rico',
187
+ 'QA': 'Qatar',
188
+ 'RE': 'Réunion',
189
+ 'RO': 'Romania',
190
+ 'RU': 'Russian Federation',
191
+ 'RW': 'Rwanda',
192
+ 'BL': 'Saint Barthélemy',
193
+ 'SH': 'Saint Helena, Ascension and Tristan da Cunha',
194
+ 'KN': 'Saint Kitts and Nevis',
195
+ 'LC': 'Saint Lucia',
196
+ 'MF': 'Saint Martin (French part)',
197
+ 'PM': 'Saint Pierre and Miquelon',
198
+ 'VC': 'Saint Vincent and the Grenadines',
199
+ 'WS': 'Samoa',
200
+ 'SM': 'San Marino',
201
+ 'ST': 'Sao Tome and Principe',
202
+ 'SA': 'Saudi Arabia',
203
+ 'SN': 'Senegal',
204
+ 'RS': 'Serbia',
205
+ 'SC': 'Seychelles',
206
+ 'SL': 'Sierra Leone',
207
+ 'SG': 'Singapore',
208
+ 'SX': 'Sint Maarten (Dutch part)',
209
+ 'SK': 'Slovakia',
210
+ 'SI': 'Slovenia',
211
+ 'SB': 'Solomon Islands',
212
+ 'SO': 'Somalia',
213
+ 'ZA': 'South Africa',
214
+ 'GS': 'South Georgia and the South Sandwich Islands',
215
+ 'SS': 'South Sudan',
216
+ 'ES': 'Spain',
217
+ 'LK': 'Sri Lanka',
218
+ 'SD': 'Sudan',
219
+ 'SR': 'Suriname',
220
+ 'SJ': 'Svalbard and Jan Mayen',
221
+ 'SE': 'Sweden',
222
+ 'CH': 'Switzerland',
223
+ 'SY': 'Syrian Arab Republic',
224
+ 'TW': 'Taiwan, Province of China',
225
+ 'TJ': 'Tajikistan',
226
+ 'TZ': 'Tanzania, United Republic of',
227
+ 'TH': 'Thailand',
228
+ 'TL': 'Timor-Leste',
229
+ 'TG': 'Togo',
230
+ 'TK': 'Tokelau',
231
+ 'TO': 'Tonga',
232
+ 'TT': 'Trinidad and Tobago',
233
+ 'TN': 'Tunisia',
234
+ 'TR': 'Turkey',
235
+ 'TM': 'Turkmenistan',
236
+ 'TC': 'Turks and Caicos Islands',
237
+ 'TV': 'Tuvalu',
238
+ 'UG': 'Uganda',
239
+ 'UA': 'Ukraine',
240
+ 'AE': 'United Arab Emirates',
241
+ 'GB': 'United Kingdom of Great Britain and Northern Ireland',
242
+ 'US': 'United States of America',
243
+ 'UM': 'United States Minor Outlying Islands',
244
+ 'UY': 'Uruguay',
245
+ 'UZ': 'Uzbekistan',
246
+ 'VU': 'Vanuatu',
247
+ 'VE': 'Venezuela, Bolivarian Republic of',
248
+ 'VN': 'Viet Nam',
249
+ 'VG': 'Virgin Islands, British',
250
+ 'VI': 'Virgin Islands, U.S.',
251
+ 'WF': 'Wallis and Futuna',
252
+ 'EH': 'Western Sahara',
253
+ 'YE': 'Yemen',
254
+ 'ZM': 'Zambia',
255
+ 'ZW': 'Zimbabwe',
256
+ }
257
+
258
+ def get_country_name(code):
259
+ """Returns the full country name for a given alpha-2 code."""
260
+ if not code or not isinstance(code, str):
261
+ return None
262
+ return COUNTRY_CODES.get(code.upper(), code)
@@ -0,0 +1,196 @@
1
+ import requests
2
+ import ipaddress
3
+ import random
4
+ from mojo.helpers.settings import settings
5
+ from .countries import get_country_name
6
+
7
+ # Lazy-load model to avoid circular imports
8
+ _GeoLocatedIP = None
9
+ GEOLOCATION_PROVIDERS = settings.get('GEOLOCATION_PROVIDERS', ['ipinfo'])
10
+
11
+
12
+ def get_geo_located_ip_model():
13
+ global _GeoLocatedIP
14
+ if _GeoLocatedIP is None:
15
+ from mojo.apps.account.models.device import GeoLocatedIP
16
+ _GeoLocatedIP = GeoLocatedIP
17
+ return _GeoLocatedIP
18
+
19
+
20
+ def geolocate_ip(ip_address):
21
+ """
22
+ Fetches geolocation data for a given IP address. It handles both
23
+ public IPs (by calling an external provider) and private IPs.
24
+ Returns a normalized dictionary of geolocation data.
25
+ """
26
+ # 1. Handle private/reserved IPs
27
+ try:
28
+ ip_obj = ipaddress.ip_address(ip_address)
29
+ if ip_obj.is_private or ip_obj.is_reserved:
30
+ return {
31
+ 'provider': 'internal',
32
+ 'country_name': 'Private Network',
33
+ 'region': 'Private' if ip_obj.is_private else 'Reserved',
34
+ }
35
+ except ValueError:
36
+ return None # Invalid IP
37
+
38
+ # 2. Handle public IPs by dispatching to a randomly selected provider
39
+ providers = GEOLOCATION_PROVIDERS
40
+ provider = random.choice(providers)
41
+ api_key_setting_name = f'GEOLOCATION_API_KEY_{provider.upper()}'
42
+ api_key = getattr(settings, api_key_setting_name, None)
43
+
44
+ provider_map = {
45
+ 'ipinfo': fetch_from_ipinfo,
46
+ 'ipstack': fetch_from_ipstack,
47
+ 'ip-api': fetch_from_ipapi,
48
+ 'maxmind': fetch_from_maxmind,
49
+ }
50
+
51
+ fetch_function = provider_map.get(provider)
52
+
53
+ if fetch_function:
54
+ return fetch_function(ip_address, api_key)
55
+ else:
56
+ # In a real app, you might want to log this or handle it differently
57
+ print(f"[Geolocation Error] Provider '{provider}' is not supported.")
58
+ return None
59
+
60
+
61
+ def refresh_geolocation_for_ip(ip_address):
62
+ """
63
+ This function is the entry point for the background task.
64
+ It gets or creates a GeoLocatedIP record and refreshes it if necessary.
65
+ """
66
+ GeoLocatedIP = get_geo_located_ip_model()
67
+
68
+ # Get or create the record, then call its internal refresh logic.
69
+ geo_record, created = GeoLocatedIP.objects.get_or_create(ip_address=ip_address)
70
+
71
+ if created or geo_record.is_expired:
72
+ geo_record.refresh()
73
+
74
+ return geo_record
75
+
76
+
77
+ def fetch_from_ipinfo(ip_address, api_key):
78
+ """
79
+ Fetches geolocation data from the ipinfo.io API and normalizes it.
80
+ Fails gracefully by returning None if any error occurs.
81
+ """
82
+ try:
83
+ url = f"https://ipinfo.io/{ip_address}"
84
+ if api_key:
85
+ url += f"?token={api_key}"
86
+
87
+ response = requests.get(url, timeout=5)
88
+ response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
89
+ data = response.json()
90
+
91
+ # Normalize the data to our model's schema
92
+ loc_parts = data.get('loc', '').split(',')
93
+ latitude = float(loc_parts[0]) if len(loc_parts) == 2 else None
94
+ longitude = float(loc_parts[1]) if len(loc_parts) == 2 else None
95
+ country_code = data.get('country')
96
+
97
+ return {
98
+ 'provider': 'ipinfo',
99
+ 'country_code': country_code,
100
+ 'country_name': get_country_name(country_code),
101
+ 'region': data.get('region'),
102
+ 'city': data.get('city'),
103
+ 'postal_code': data.get('postal'),
104
+ 'latitude': latitude,
105
+ 'longitude': longitude,
106
+ 'timezone': data.get('timezone'),
107
+ 'data': data # Store the raw response
108
+ }
109
+
110
+ except Exception as e:
111
+ # In a real application, you would want to log this error.
112
+ print(f"[Geolocation Error] Failed to fetch from ipinfo.io for IP {ip_address}: {e}")
113
+ return None
114
+
115
+
116
+ def fetch_from_ipstack(ip_address, api_key):
117
+ """
118
+ Fetches geolocation data from the ipstack.com API and normalizes it.
119
+ """
120
+ if not api_key:
121
+ print("[Geolocation Error] ipstack provider requires an API key (GEOLOCATION_API_KEY_IPSTACK).")
122
+ return None
123
+ try:
124
+ url = f"http://api.ipstack.com/{ip_address}?access_key={api_key}"
125
+ response = requests.get(url, timeout=5)
126
+ response.raise_for_status()
127
+ data = response.json()
128
+
129
+ if data.get('success') is False:
130
+ error_info = data.get('error', {}).get('info', 'Unknown error')
131
+ print(f"[Geolocation Error] ipstack API error: {error_info}")
132
+ return None
133
+
134
+ country_code = data.get('country_code')
135
+ return {
136
+ 'provider': 'ipstack',
137
+ 'country_code': country_code,
138
+ 'country_name': data.get('country_name') or get_country_name(country_code),
139
+ 'region': data.get('region_name'),
140
+ 'city': data.get('city'),
141
+ 'postal_code': data.get('zip'),
142
+ 'latitude': data.get('latitude'),
143
+ 'longitude': data.get('longitude'),
144
+ 'data': data
145
+ }
146
+ except Exception as e:
147
+ print(f"[Geolocation Error] Failed to fetch from ipstack.com for IP {ip_address}: {e}")
148
+ return None
149
+
150
+
151
+ def fetch_from_ipapi(ip_address, api_key=None):
152
+ """
153
+ Fetches geolocation data from the ip-api.com API and normalizes it.
154
+ Note: The free tier does not require an API key.
155
+ """
156
+ try:
157
+ url = f"http://ip-api.com/json/{ip_address}"
158
+ response = requests.get(url, timeout=5)
159
+ response.raise_for_status()
160
+ data = response.json()
161
+
162
+ if data.get('status') == 'fail':
163
+ error_info = data.get('message', 'Unknown error')
164
+ print(f"[Geolocation Error] ip-api.com API error: {error_info}")
165
+ return None
166
+
167
+ country_code = data.get('countryCode')
168
+ return {
169
+ 'provider': 'ip-api',
170
+ 'country_code': country_code,
171
+ 'country_name': data.get('country') or get_country_name(country_code),
172
+ 'region': data.get('regionName'),
173
+ 'city': data.get('city'),
174
+ 'postal_code': data.get('zip'),
175
+ 'latitude': data.get('lat'),
176
+ 'longitude': data.get('lon'),
177
+ 'timezone': data.get('timezone'),
178
+ 'data': data
179
+ }
180
+ except Exception as e:
181
+ print(f"[Geolocation Error] Failed to fetch from ip-api.com for IP {ip_address}: {e}")
182
+ return None
183
+
184
+
185
+ def fetch_from_maxmind(ip_address, api_key):
186
+ """
187
+ Placeholder for MaxMind GeoIP2 web service integration.
188
+ """
189
+ # MaxMind's GeoIP2 web services are best accessed via their official client library.
190
+ # See: https://github.com/maxmind/geoip2-python
191
+ # This is a placeholder for where you would integrate the geoip2.webservice.Client.
192
+ # You would typically fetch account_id and license_key from settings here instead of a single api_key.
193
+ raise NotImplementedError(
194
+ "MaxMind provider requires the 'geoip2' client library. "
195
+ "Set GEOLOCATION_API_KEY_MAXMIND_ACCOUNT_ID and GEOLOCATION_API_KEY_MAXMIND_LICENSE_KEY in your settings."
196
+ )
mojo/helpers/logit.py CHANGED
@@ -48,6 +48,40 @@ def color_print(msg, color, end="\n"):
48
48
  ConsoleLogger.print_message(msg, color, end)
49
49
 
50
50
 
51
+ # Convenience logging functions with automatic logger routing
52
+ _mojo_logger = None
53
+ _debug_logger = None
54
+ _error_logger = None
55
+
56
+ def info(*args):
57
+ """Log info messages to mojo.log"""
58
+ global _mojo_logger
59
+ if _mojo_logger is None:
60
+ _mojo_logger = get_logger("mojo", "mojo.log")
61
+ _mojo_logger.info(*args)
62
+
63
+ def warn(*args):
64
+ """Log warning messages to mojo.log"""
65
+ global _mojo_logger
66
+ if _mojo_logger is None:
67
+ _mojo_logger = get_logger("mojo", "mojo.log")
68
+ _mojo_logger.warning(*args)
69
+
70
+ def debug(*args):
71
+ """Log debug messages to debug.log"""
72
+ global _debug_logger
73
+ if _debug_logger is None:
74
+ _debug_logger = get_logger("debug", "debug.log", debug=True)
75
+ _debug_logger.info(*args)
76
+
77
+ def error(*args):
78
+ """Log error messages to error.log"""
79
+ global _error_logger
80
+ if _error_logger is None:
81
+ _error_logger = get_logger("error", "error.log")
82
+ _error_logger.error(*args)
83
+
84
+
51
85
  # Mask sensitive data in the log
52
86
  def mask_sensitive_data(text):
53
87
  sensitive_patterns = [
@@ -142,6 +176,9 @@ class Logger:
142
176
  def warning(self, *args):
143
177
  self.logger.warning(self._build_log(*args))
144
178
 
179
+ def warn(self, *args):
180
+ self.logger.warning(self._build_log(*args))
181
+
145
182
  def error(self, *args):
146
183
  self.logger.error(self._build_log(*args))
147
184
 
@@ -0,0 +1,2 @@
1
+ from .client import get_connection
2
+ from .adapter import RedisAdapter, reset_adapter, get_adapter