django-cfg 1.5.8__py3-none-any.whl → 1.5.14__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.

Potentially problematic release.


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

Files changed (119) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/commands/serializers.py +152 -0
  3. django_cfg/apps/api/commands/views.py +32 -0
  4. django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
  5. django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
  6. django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
  7. django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
  8. django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
  9. django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
  10. django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
  11. django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
  12. django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
  13. django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
  14. django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +5 -5
  15. django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
  16. django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
  17. django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
  18. django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
  19. django_cfg/apps/integrations/grpc/admin/config.py +113 -9
  20. django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
  21. django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
  22. django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
  23. django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
  24. django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
  25. django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
  26. django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
  27. django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
  28. django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +130 -0
  29. django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +171 -96
  30. django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
  31. django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
  32. django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
  33. django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
  34. django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
  35. django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
  36. django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
  37. django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
  38. django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
  39. django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
  40. django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
  41. django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
  42. django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
  43. django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
  44. django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
  45. django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
  46. django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
  47. django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
  48. django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
  49. django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
  50. django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
  51. django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
  52. django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
  53. django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
  54. django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
  55. django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
  56. django_cfg/apps/integrations/grpc/urls.py +8 -0
  57. django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
  58. django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
  59. django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
  60. django_cfg/apps/integrations/grpc/utils/streaming_logger.py +177 -0
  61. django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
  62. django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
  63. django_cfg/apps/integrations/grpc/views/charts.py +21 -14
  64. django_cfg/apps/integrations/grpc/views/config.py +8 -6
  65. django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
  66. django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
  67. django_cfg/apps/integrations/grpc/views/services.py +30 -21
  68. django_cfg/apps/integrations/grpc/views/testing.py +45 -43
  69. django_cfg/apps/integrations/rq/views/jobs.py +19 -9
  70. django_cfg/apps/integrations/rq/views/schedule.py +7 -3
  71. django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
  72. django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
  73. django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
  74. django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
  75. django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
  76. django_cfg/config.py +33 -0
  77. django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
  78. django_cfg/management/commands/check_endpoints.py +2 -2
  79. django_cfg/management/commands/check_settings.py +3 -10
  80. django_cfg/management/commands/clear_constance.py +3 -10
  81. django_cfg/management/commands/create_token.py +4 -11
  82. django_cfg/management/commands/list_urls.py +4 -10
  83. django_cfg/management/commands/migrate_all.py +18 -12
  84. django_cfg/management/commands/migrator.py +4 -11
  85. django_cfg/management/commands/script.py +4 -10
  86. django_cfg/management/commands/show_config.py +8 -16
  87. django_cfg/management/commands/show_urls.py +5 -11
  88. django_cfg/management/commands/superuser.py +4 -11
  89. django_cfg/management/commands/tree.py +5 -10
  90. django_cfg/management/utils/README.md +402 -0
  91. django_cfg/management/utils/__init__.py +29 -0
  92. django_cfg/management/utils/mixins.py +176 -0
  93. django_cfg/middleware/pagination.py +53 -54
  94. django_cfg/models/api/grpc/__init__.py +15 -21
  95. django_cfg/models/api/grpc/config.py +155 -73
  96. django_cfg/models/ngrok/config.py +7 -6
  97. django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
  98. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
  99. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
  100. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
  101. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
  102. django_cfg/modules/django_client/core/parser/base.py +114 -30
  103. django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
  104. django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
  105. django_cfg/modules/django_email/management/commands/test_email.py +4 -10
  106. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
  107. django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
  108. django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
  109. django_cfg/modules/django_unfold/navigation.py +6 -18
  110. django_cfg/pyproject.toml +1 -1
  111. django_cfg/registry/modules.py +1 -4
  112. django_cfg/requirements.txt +52 -0
  113. django_cfg/static/frontend/admin.zip +0 -0
  114. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/METADATA +1 -1
  115. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/RECORD +118 -97
  116. django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
  117. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/WHEEL +0 -0
  118. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/entry_points.txt +0 -0
  119. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,192 @@
1
+ """
2
+ Manager for GrpcApiKey model.
3
+
4
+ Provides convenient methods for API key management.
5
+ """
6
+
7
+ from datetime import timedelta
8
+ from typing import Optional
9
+
10
+ from django.contrib.auth import get_user_model
11
+ from django.db import models
12
+ from django.utils import timezone
13
+
14
+ User = get_user_model()
15
+
16
+
17
+ class GrpcApiKeyManager(models.Manager):
18
+ """
19
+ Manager for GrpcApiKey model.
20
+
21
+ Provides convenient methods for creating and validating API keys.
22
+ """
23
+
24
+ def create_for_user(
25
+ self,
26
+ user: User,
27
+ name: str,
28
+ description: str = "",
29
+ key_type: str = "service",
30
+ expires_in_days: Optional[int] = None,
31
+ created_by: Optional[User] = None,
32
+ ) -> "GrpcApiKey":
33
+ """
34
+ Create a new API key for a user.
35
+
36
+ Args:
37
+ user: User this key authenticates as
38
+ name: Descriptive name for this key
39
+ description: Additional details about this key
40
+ key_type: Type of key (service, cli, webhook, etc.)
41
+ expires_in_days: Number of days until expiration (None = never)
42
+ created_by: User who created this key
43
+
44
+ Returns:
45
+ Created GrpcApiKey instance
46
+
47
+ Example:
48
+ >>> key = GrpcApiKey.objects.create_for_user(
49
+ ... user=admin_user,
50
+ ... name="Analytics Service",
51
+ ... description="Internal analytics microservice",
52
+ ... expires_in_days=365,
53
+ ... )
54
+ """
55
+ expires_at = None
56
+ if expires_in_days:
57
+ expires_at = timezone.now() + timedelta(days=expires_in_days)
58
+
59
+ return self.create(
60
+ user=user,
61
+ name=name,
62
+ description=description,
63
+ key_type=key_type,
64
+ expires_at=expires_at,
65
+ created_by=created_by or user,
66
+ )
67
+
68
+ def get_by_key(self, key: str) -> Optional["GrpcApiKey"]:
69
+ """
70
+ Get API key by key string.
71
+
72
+ Args:
73
+ key: API key string
74
+
75
+ Returns:
76
+ GrpcApiKey instance or None if not found
77
+
78
+ Example:
79
+ >>> api_key = GrpcApiKey.objects.get_by_key("abc123...")
80
+ >>> if api_key and api_key.is_valid:
81
+ ... user = api_key.user
82
+ """
83
+ try:
84
+ return self.select_related("user").get(key=key)
85
+ except self.model.DoesNotExist:
86
+ return None
87
+
88
+ def validate_key(self, key: str) -> Optional[User]:
89
+ """
90
+ Validate API key and return associated user.
91
+
92
+ Args:
93
+ key: API key string
94
+
95
+ Returns:
96
+ User instance if key is valid, None otherwise
97
+
98
+ Example:
99
+ >>> user = GrpcApiKey.objects.validate_key("abc123...")
100
+ >>> if user:
101
+ ... print(f"Authenticated as {user.username}")
102
+ """
103
+ api_key = self.get_by_key(key)
104
+
105
+ if not api_key:
106
+ return None
107
+
108
+ if not api_key.is_valid:
109
+ return None
110
+
111
+ # Mark as used
112
+ api_key.mark_used()
113
+
114
+ return api_key.user
115
+
116
+ def active(self):
117
+ """
118
+ Get all active API keys.
119
+
120
+ Returns:
121
+ QuerySet of active keys
122
+
123
+ Example:
124
+ >>> active_keys = GrpcApiKey.objects.active()
125
+ """
126
+ return self.filter(is_active=True)
127
+
128
+ def valid(self):
129
+ """
130
+ Get all valid API keys (active and not expired).
131
+
132
+ Returns:
133
+ QuerySet of valid keys
134
+
135
+ Example:
136
+ >>> valid_keys = GrpcApiKey.objects.valid()
137
+ """
138
+ now = timezone.now()
139
+ return self.filter(
140
+ is_active=True
141
+ ).filter(
142
+ models.Q(expires_at__isnull=True) | models.Q(expires_at__gt=now)
143
+ )
144
+
145
+ def for_user(self, user: User):
146
+ """
147
+ Get all API keys for a user.
148
+
149
+ Args:
150
+ user: User instance
151
+
152
+ Returns:
153
+ QuerySet of keys for this user
154
+
155
+ Example:
156
+ >>> user_keys = GrpcApiKey.objects.for_user(request.user)
157
+ """
158
+ return self.filter(user=user)
159
+
160
+ def expired(self):
161
+ """
162
+ Get all expired API keys.
163
+
164
+ Returns:
165
+ QuerySet of expired keys
166
+
167
+ Example:
168
+ >>> expired_keys = GrpcApiKey.objects.expired()
169
+ """
170
+ return self.filter(
171
+ expires_at__isnull=False,
172
+ expires_at__lte=timezone.now()
173
+ )
174
+
175
+ def revoke_all_for_user(self, user: User) -> int:
176
+ """
177
+ Revoke all API keys for a user.
178
+
179
+ Args:
180
+ user: User instance
181
+
182
+ Returns:
183
+ Number of keys revoked
184
+
185
+ Example:
186
+ >>> count = GrpcApiKey.objects.revoke_all_for_user(user)
187
+ >>> print(f"Revoked {count} keys")
188
+ """
189
+ return self.filter(user=user, is_active=True).update(is_active=False)
190
+
191
+
192
+ __all__ = ["GrpcApiKeyManager"]
@@ -53,6 +53,10 @@ class GRPCServerStatusManager(models.Manager):
53
53
  ... )
54
54
  >>> status.is_running
55
55
  True
56
+
57
+ Note:
58
+ External/internal server detection is automatic based on env_mode.
59
+ Production mode assumes external server (Docker), dev/test assumes local.
56
60
  """
57
61
  if pid is None:
58
62
  pid = os.getpid()
@@ -64,18 +68,22 @@ class GRPCServerStatusManager(models.Manager):
64
68
  # Mark any existing server at this address as stopped
65
69
  self.stop_servers_at_address(address)
66
70
 
67
- # Create new server status
68
- status = self.create(
71
+ # Create or update server status (handles restart with same instance_id)
72
+ status, created = self.update_or_create(
69
73
  instance_id=instance_id,
70
- host=host,
71
- port=port,
72
- address=address,
73
- pid=pid,
74
- hostname=hostname,
75
- status=self.model.StatusChoices.STARTING,
76
- max_workers=max_workers,
77
- enable_reflection=enable_reflection,
78
- enable_health_check=enable_health_check,
74
+ defaults={
75
+ "host": host,
76
+ "port": port,
77
+ "address": address,
78
+ "pid": pid,
79
+ "hostname": hostname,
80
+ "status": self.model.StatusChoices.STARTING,
81
+ "max_workers": max_workers,
82
+ "enable_reflection": enable_reflection,
83
+ "enable_health_check": enable_health_check,
84
+ "started_at": timezone.now(),
85
+ "last_heartbeat": timezone.now(),
86
+ },
79
87
  )
80
88
 
81
89
  return status
@@ -0,0 +1,143 @@
1
+ # Generated by Django 5.2.7 on 2025-11-04 05:14
2
+
3
+ import django.db.models.deletion
4
+ import django_cfg.apps.integrations.grpc.models.grpc_api_key
5
+ from django.conf import settings
6
+ from django.db import migrations, models
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ ("grpc", "0004_grpcserverstatus_registered_services"),
13
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14
+ ]
15
+
16
+ operations = [
17
+ migrations.CreateModel(
18
+ name="GrpcApiKey",
19
+ fields=[
20
+ (
21
+ "id",
22
+ models.BigAutoField(
23
+ auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
24
+ ),
25
+ ),
26
+ (
27
+ "key",
28
+ models.CharField(
29
+ db_index=True,
30
+ default=django_cfg.apps.integrations.grpc.models.grpc_api_key.generate_api_key,
31
+ help_text="API key (auto-generated)",
32
+ max_length=64,
33
+ unique=True,
34
+ ),
35
+ ),
36
+ (
37
+ "name",
38
+ models.CharField(
39
+ help_text="Descriptive name for this key (e.g., 'Analytics Service')",
40
+ max_length=255,
41
+ ),
42
+ ),
43
+ (
44
+ "description",
45
+ models.TextField(
46
+ blank=True, help_text="Additional details about this key's purpose"
47
+ ),
48
+ ),
49
+ (
50
+ "key_type",
51
+ models.CharField(
52
+ choices=[
53
+ ("service", "Service-to-Service"),
54
+ ("cli", "CLI Tool"),
55
+ ("webhook", "Webhook"),
56
+ ("internal", "Internal System"),
57
+ ("development", "Development"),
58
+ ],
59
+ default="service",
60
+ help_text="Type of API key",
61
+ max_length=20,
62
+ ),
63
+ ),
64
+ (
65
+ "is_active",
66
+ models.BooleanField(
67
+ db_index=True,
68
+ default=True,
69
+ help_text="Whether this key is currently active (can be used)",
70
+ ),
71
+ ),
72
+ (
73
+ "expires_at",
74
+ models.DateTimeField(
75
+ blank=True,
76
+ db_index=True,
77
+ help_text="When this key expires (null = never expires)",
78
+ null=True,
79
+ ),
80
+ ),
81
+ (
82
+ "last_used_at",
83
+ models.DateTimeField(
84
+ blank=True, help_text="When this key was last used", null=True
85
+ ),
86
+ ),
87
+ (
88
+ "request_count",
89
+ models.IntegerField(
90
+ default=0, help_text="Total number of requests made with this key"
91
+ ),
92
+ ),
93
+ (
94
+ "created_at",
95
+ models.DateTimeField(
96
+ auto_now_add=True, db_index=True, help_text="When this key was created"
97
+ ),
98
+ ),
99
+ (
100
+ "updated_at",
101
+ models.DateTimeField(auto_now=True, help_text="When this key was last updated"),
102
+ ),
103
+ (
104
+ "created_by",
105
+ models.ForeignKey(
106
+ blank=True,
107
+ help_text="User who created this key",
108
+ null=True,
109
+ on_delete=django.db.models.deletion.SET_NULL,
110
+ related_name="created_grpc_api_keys",
111
+ to=settings.AUTH_USER_MODEL,
112
+ ),
113
+ ),
114
+ (
115
+ "user",
116
+ models.ForeignKey(
117
+ help_text="User this key authenticates as",
118
+ on_delete=django.db.models.deletion.CASCADE,
119
+ related_name="grpc_api_keys",
120
+ to=settings.AUTH_USER_MODEL,
121
+ ),
122
+ ),
123
+ ],
124
+ options={
125
+ "verbose_name": "gRPC API Key",
126
+ "verbose_name_plural": "gRPC API Keys",
127
+ "db_table": "django_cfg_grpc_api_key",
128
+ "ordering": ["-created_at"],
129
+ "indexes": [
130
+ models.Index(
131
+ fields=["user", "-created_at"], name="django_cfg__user_id_9c5276_idx"
132
+ ),
133
+ models.Index(
134
+ fields=["is_active", "-created_at"], name="django_cfg__is_acti_26421c_idx"
135
+ ),
136
+ models.Index(fields=["expires_at"], name="django_cfg__expires_8f83cf_idx"),
137
+ models.Index(
138
+ fields=["key_type", "-created_at"], name="django_cfg__key_typ_6ad1cb_idx"
139
+ ),
140
+ ],
141
+ },
142
+ ),
143
+ ]
@@ -0,0 +1,34 @@
1
+ # Generated by Django 5.2.7 on 2025-11-04 05:30
2
+
3
+ import django.db.models.deletion
4
+ from django.conf import settings
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ("grpc", "0005_grpcapikey"),
12
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AddField(
17
+ model_name="grpcrequestlog",
18
+ name="api_key",
19
+ field=models.ForeignKey(
20
+ blank=True,
21
+ help_text="API key used for authentication (if applicable)",
22
+ null=True,
23
+ on_delete=django.db.models.deletion.SET_NULL,
24
+ related_name="request_logs",
25
+ to="grpc.grpcapikey",
26
+ ),
27
+ ),
28
+ migrations.AddIndex(
29
+ model_name="grpcrequestlog",
30
+ index=models.Index(
31
+ fields=["api_key", "-created_at"], name="django_cfg__api_key_218ecc_idx"
32
+ ),
33
+ ),
34
+ ]
@@ -2,10 +2,12 @@
2
2
  Models for gRPC app.
3
3
  """
4
4
 
5
+ from .grpc_api_key import GrpcApiKey
5
6
  from .grpc_request_log import GRPCRequestLog
6
7
  from .grpc_server_status import GRPCServerStatus
7
8
 
8
9
  __all__ = [
10
+ "GrpcApiKey",
9
11
  "GRPCRequestLog",
10
12
  "GRPCServerStatus",
11
13
  ]
@@ -0,0 +1,198 @@
1
+ """
2
+ gRPC API Key Model.
3
+
4
+ Django model for managing API keys used for gRPC authentication.
5
+ Provides secure, revocable authentication for services and CLI tools.
6
+ """
7
+
8
+ import secrets
9
+ from django.conf import settings
10
+ from django.db import models
11
+ from django.utils import timezone
12
+
13
+
14
+ def generate_api_key() -> str:
15
+ """
16
+ Generate a secure random API key.
17
+
18
+ Returns:
19
+ 32-character hex string (128 bits of entropy)
20
+ """
21
+ return secrets.token_hex(32)
22
+
23
+
24
+ class GrpcApiKey(models.Model):
25
+ """
26
+ API Key for gRPC authentication.
27
+
28
+ Provides secure, revocable authentication for:
29
+ - Service-to-service communication
30
+ - CLI tools and scripts
31
+ - Internal systems
32
+ - Development and testing
33
+
34
+ Features:
35
+ - Auto-generated secure keys
36
+ - User association for permissions
37
+ - Expiration support
38
+ - Usage tracking
39
+ - Easy revocation via admin
40
+
41
+ Example:
42
+ >>> # Create API key for a service
43
+ >>> key = GrpcApiKey.objects.create_for_user(
44
+ ... user=admin_user,
45
+ ... name="Analytics Service",
46
+ ... description="Internal analytics microservice"
47
+ ... )
48
+ >>> print(key.key) # Use this in service config
49
+
50
+ >>> # Check if key is valid
51
+ >>> if key.is_valid:
52
+ ... user = key.user
53
+ """
54
+
55
+ # Custom manager
56
+ from ..managers.grpc_api_key import GrpcApiKeyManager
57
+ objects: GrpcApiKeyManager = GrpcApiKeyManager()
58
+
59
+ class KeyTypeChoices(models.TextChoices):
60
+ """Type of API key."""
61
+ SERVICE = "service", "Service-to-Service"
62
+ CLI = "cli", "CLI Tool"
63
+ WEBHOOK = "webhook", "Webhook"
64
+ INTERNAL = "internal", "Internal System"
65
+ DEVELOPMENT = "development", "Development"
66
+
67
+ # Identity
68
+ key = models.CharField(
69
+ max_length=64,
70
+ unique=True,
71
+ default=generate_api_key,
72
+ db_index=True,
73
+ help_text="API key (auto-generated)",
74
+ )
75
+
76
+ name = models.CharField(
77
+ max_length=255,
78
+ help_text="Descriptive name for this key (e.g., 'Analytics Service')",
79
+ )
80
+
81
+ description = models.TextField(
82
+ blank=True,
83
+ help_text="Additional details about this key's purpose",
84
+ )
85
+
86
+ key_type = models.CharField(
87
+ max_length=20,
88
+ choices=KeyTypeChoices.choices,
89
+ default=KeyTypeChoices.SERVICE,
90
+ help_text="Type of API key",
91
+ )
92
+
93
+ # User association
94
+ user = models.ForeignKey(
95
+ settings.AUTH_USER_MODEL,
96
+ on_delete=models.CASCADE,
97
+ related_name="grpc_api_keys",
98
+ help_text="User this key authenticates as",
99
+ )
100
+
101
+ # Status
102
+ is_active = models.BooleanField(
103
+ default=True,
104
+ db_index=True,
105
+ help_text="Whether this key is currently active (can be used)",
106
+ )
107
+
108
+ # Expiration
109
+ expires_at = models.DateTimeField(
110
+ null=True,
111
+ blank=True,
112
+ db_index=True,
113
+ help_text="When this key expires (null = never expires)",
114
+ )
115
+
116
+ # Usage tracking
117
+ last_used_at = models.DateTimeField(
118
+ null=True,
119
+ blank=True,
120
+ help_text="When this key was last used",
121
+ )
122
+
123
+ request_count = models.IntegerField(
124
+ default=0,
125
+ help_text="Total number of requests made with this key",
126
+ )
127
+
128
+ # Metadata
129
+ created_by = models.ForeignKey(
130
+ settings.AUTH_USER_MODEL,
131
+ on_delete=models.SET_NULL,
132
+ null=True,
133
+ blank=True,
134
+ related_name="created_grpc_api_keys",
135
+ help_text="User who created this key",
136
+ )
137
+
138
+ # Timestamps
139
+ created_at = models.DateTimeField(
140
+ auto_now_add=True,
141
+ db_index=True,
142
+ help_text="When this key was created",
143
+ )
144
+
145
+ updated_at = models.DateTimeField(
146
+ auto_now=True,
147
+ help_text="When this key was last updated",
148
+ )
149
+
150
+ class Meta:
151
+ db_table = "django_cfg_grpc_api_key"
152
+ ordering = ["-created_at"]
153
+ indexes = [
154
+ models.Index(fields=["user", "-created_at"]),
155
+ models.Index(fields=["is_active", "-created_at"]),
156
+ models.Index(fields=["expires_at"]),
157
+ models.Index(fields=["key_type", "-created_at"]),
158
+ ]
159
+ verbose_name = "gRPC API Key"
160
+ verbose_name_plural = "gRPC API Keys"
161
+
162
+ def __str__(self) -> str:
163
+ """String representation."""
164
+ status = "✓" if self.is_valid else "✗"
165
+ return f"{status} {self.name} ({self.user.username})"
166
+
167
+ @property
168
+ def is_expired(self) -> bool:
169
+ """Check if key has expired."""
170
+ if not self.expires_at:
171
+ return False
172
+ return timezone.now() > self.expires_at
173
+
174
+ @property
175
+ def is_valid(self) -> bool:
176
+ """Check if key is valid (active and not expired)."""
177
+ return self.is_active and not self.is_expired
178
+
179
+ @property
180
+ def masked_key(self) -> str:
181
+ """Return masked version of key for display."""
182
+ if len(self.key) <= 8:
183
+ return self.key
184
+ return f"{self.key[:4]}...{self.key[-4:]}"
185
+
186
+ def mark_used(self) -> None:
187
+ """Mark this key as used (update last_used_at and increment counter)."""
188
+ self.last_used_at = timezone.now()
189
+ self.request_count += 1
190
+ self.save(update_fields=["last_used_at", "request_count"])
191
+
192
+ def revoke(self) -> None:
193
+ """Revoke this key (set is_active=False)."""
194
+ self.is_active = False
195
+ self.save(update_fields=["is_active"])
196
+
197
+
198
+ __all__ = ["GrpcApiKey", "generate_api_key"]
@@ -9,6 +9,7 @@ from django.conf import settings
9
9
  from django.db import models
10
10
  from django.utils import timezone
11
11
 
12
+ from .grpc_api_key import GrpcApiKey
12
13
 
13
14
  class GRPCRequestLog(models.Model):
14
15
  """
@@ -138,6 +139,15 @@ class GRPCRequestLog(models.Model):
138
139
  help_text="Authenticated user (if applicable)",
139
140
  )
140
141
 
142
+ api_key = models.ForeignKey(
143
+ GrpcApiKey,
144
+ on_delete=models.SET_NULL,
145
+ null=True,
146
+ blank=True,
147
+ related_name="request_logs",
148
+ help_text="API key used for authentication (if applicable)",
149
+ )
150
+
141
151
  is_authenticated = models.BooleanField(
142
152
  default=False,
143
153
  db_index=True,
@@ -186,6 +196,7 @@ class GRPCRequestLog(models.Model):
186
196
  models.Index(fields=["method_name", "-created_at"]),
187
197
  models.Index(fields=["status", "-created_at"]),
188
198
  models.Index(fields=["user", "-created_at"]),
199
+ models.Index(fields=["api_key", "-created_at"]),
189
200
  models.Index(fields=["grpc_status_code", "-created_at"]),
190
201
  ]
191
202
  verbose_name = "gRPC Request Log"