This commit is contained in:
Sayyid Hamid Mahdavi 2026-03-29 13:40:20 +03:30
parent b1867a9a07
commit da7d25d29e
7 changed files with 131 additions and 12 deletions

View file

@ -1,4 +1,12 @@
from django.contrib import admin
from .models import Config
class ConfigAdmin(admin.ModelAdmin):
list_display = ['key', 'value', 'value_type', 'get_value', 'description', 'comment']
admin.site.register(Config, ConfigAdmin)

31
apps/core/decorators.py Normal file
View file

@ -0,0 +1,31 @@
from functools import wraps
from django.utils import timezone
from apps.core.models import Config, ConfigValueChoices
from utils.exceptions import ServiceUnavailable
def service_availability(key:str):
def with_params(view_func):
@wraps(view_func)
def wrapper(self, request, *args, **kwargs):
print(key)
print(f"Executing {view_func.__name__} action")
service_is_available = Config.get_value_of(f"SERVICE_IS_AVAILABLE_{key.upper()}", "True", ConfigValueChoices.BOOLEAN)
config = Config.objects.get(key=f"SERVICE_IS_AVAILABLE_{key.upper()}")
if not service_is_available:
raise ServiceUnavailable(
detail={
# "code": 'service_unavailable_at_now',
# "timestamp": timezone.now().isoformat(),
"message": config.description or "این سرویس در حال حاضر در دسترس نیست"
},
)
return view_func(self, request, *args, **kwargs)
return wrapper
return with_params

View file

@ -0,0 +1,31 @@
# Generated by Django 5.1.4 on 2026-03-29 13:38
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Config',
fields=[
('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
('updated_at', models.DateTimeField(auto_now=True, db_index=True)),
('key', models.CharField(db_index=True, max_length=255, unique=True, verbose_name='key')),
('value', models.TextField(blank=True, null=True, verbose_name='value')),
('value_type', models.CharField(choices=[('INT', 'Integer'), ('FLOAT', 'Float'), ('BOOLEAN', 'Boolean'), ('STRING', 'String'), ('DATE', 'Date'), ('DATETIME', 'DateTime'), ('JSON', 'JSON')], max_length=64)),
('description', models.TextField(blank=True, null=True, verbose_name='description')),
('comment', models.TextField(blank=True, null=True, verbose_name='comment')),
],
options={
'abstract': False,
},
),
]

View file

View file

@ -6,6 +6,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from django.core.cache import cache
class BaseModel(models.Model):
id = None
uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, db_index=True)
@ -16,7 +17,6 @@ class BaseModel(models.Model):
abstract = True
class BasePGModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(auto_now=True, db_index=True)
@ -39,8 +39,64 @@ class BasePGModel(models.Model):
abstract = True
class ConfigValueChoices(models.TextChoices):
INT = 'INT', _('Integer')
FLOAT = 'FLOAT', _('Float')
BOOLEAN = 'BOOLEAN', _('Boolean')
STRING = 'STRING', _('String')
DATE = 'DATE', _('Date')
DATETIME = 'DATETIME', _('DateTime')
JSON = 'JSON', _('JSON')
class Config(BaseModel):
key = models.CharField(_('key'), max_length=255, unique=True, db_index=True)
value = models.TextField(_('value'), null=True, blank=True)
value_type = models.CharField(choices=ConfigValueChoices.choices, max_length=64)
description = models.TextField(_('description'), null=True, blank=True)
comment = models.TextField(_('comment'), null=True, blank=True)
def get_value(self):
return Config.get_value_of(self.key)
@staticmethod
def type_cast(value, value_type):
try:
if value_type == ConfigValueChoices.STRING.value:
value = str(value)
elif value_type == ConfigValueChoices.BOOLEAN.value:
value = value.lower() == 'true'
elif value_type == ConfigValueChoices.DATE.value:
value = datetime.fromisoformat(value)
elif value_type == ConfigValueChoices.DATETIME.value:
value = datetime.fromisoformat(value)
elif value_type == ConfigValueChoices.JSON.value:
value = json.loads(value)
elif value_type == ConfigValueChoices.INT.value:
value = int(value)
elif value_type == ConfigValueChoices.FLOAT.value:
value = float(value)
except Exception as e:
value = None
return value
@staticmethod
def get_value_of(key, default=None, casting_type=None):
# TODO: use cache
config, created = Config.objects.get_or_create(key=key, defaults={'value': default})
if config.value is None:
return default
value = Config.type_cast(config.value, casting_type or config.value_type)
return value

View file

@ -10,7 +10,8 @@ from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView
from utils.exceptions import StatusError
from apps.core.decorators import service_availability
from utils.exceptions import ServiceUnavailable
# Create your views here.
@ -22,16 +23,8 @@ class HomeView(TemplateView):
class StatusView(APIView):
permission_classes = [AllowAny]
@service_availability('status')
def get(self, request, format=None):
if self.request.GET.get('raise_error'):
raise StatusError(
# code='service_unavailable_at_now',
detail={
# "code": 'service_unavailable_at_now',
"timestamp": timezone.now().isoformat(),
"message": "برخی سرویس‌های خارجی در دسترس نیستند لطفا بعدا تلاش کنید"
},
)
data = {
"status": "ok",

View file

@ -84,7 +84,7 @@ class UnprocessableEntity(APIException):
default_detail = 'The request was well-formed but cannot be processed due to semantic errors.'
default_code = 'unprocessable_entity'
class StatusError(APIException):
class ServiceUnavailable(APIException):
status_code = status.HTTP_503_SERVICE_UNAVAILABLE
default_ = _('SERVICE_UNAVAILABLE')
default_code = 'service_unavailable'