diff --git a/main/settings/__init__.py b/main/settings/__init__.py index 1851e5d..f3c291d 100644 --- a/main/settings/__init__.py +++ b/main/settings/__init__.py @@ -1 +1,2 @@ -from base_settings import * \ No newline at end of file +from .base_settings import * +from .settings import * \ No newline at end of file diff --git a/main/settings/settings.py b/main/settings/settings.py new file mode 100644 index 0000000..1a85698 --- /dev/null +++ b/main/settings/settings.py @@ -0,0 +1,269 @@ +from .base_settings import * +from django.utils.translation import gettext_lazy as _ +from decouple import config + +SECRET_KEY = config('SECRET_KEY', default=SECRET_KEY) + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = config('DEBUG', default=DEBUG, cast=bool) + +ALLOWED_HOSTS = ['*'] + + +INSTALLED_APPS += [ + # pip installed apps + 'drf_spectacular', + 'oauth2_provider', + 'rest_framework', + 'rest_framework_gis', + 'corsheaders', + 'crispy_forms', + 'crispy_bootstrap5', + 'django_filters', + 'jalali_date', + 'taggit', + 'colorfield', + # 'django_minio_backend', + 'django_minio_backend.apps.DjangoMinioBackendConfig', + + # local apps + 'utils', + 'apps.users', + 'apps.gooyal_oauth2', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', + # 'django.middleware.locale.LocaleMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'oauth2_provider.middleware.OAuth2TokenMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +OAUTH2_PROVIDER_APPLICATION_MODEL = "gooyal_oauth2.Application" +OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "gooyal_oauth2.AccessToken" +OAUTH2_PROVIDER_ID_TOKEN_MODEL = "gooyal_oauth2.IDToken" +OAUTH2_PROVIDER_GRANT_MODEL = "gooyal_oauth2.Grant" +OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "gooyal_oauth2.RefreshToken" + +BASE_OAUTH2_PROVIDER_PUBLIC_URL = config("BASE_OAUTH2_PROVIDER_PUBLIC_URL") +BASE_OAUTH2_PROVIDER_PRIVATE_URL = config("BASE_OAUTH2_PROVIDER_PRIVATE_URL") + +CLIENT_ID = config('CLIENT_ID') +CLIENT_SECRET = config('CLIENT_SECRET') +SCOPES = config('SCOPES', default='') +OAUTH2_PROVIDER = { + # this is the list of available scopes + # 'SCOPES_BACKEND_CLASS': 'apps.gooyal_oauth2.scopes.Scopes', + # 'SCOPES': {'read': 'Read scope', + # 'write': 'Write scope', + # 'groups': 'Access to your groups', + # 'introspection': 'Introspect token scope'}, + 'OAUTH2_VALIDATOR_CLASS': 'apps.gooyal_oauth2.validators.OAuth2Validator', + 'RESOURCE_SERVER_INTROSPECTION_URL': BASE_OAUTH2_PROVIDER_PRIVATE_URL + '/introspect/', + # 'RESOURCE_SERVER_AUTH_TOKEN': '3yUqsWtwKYKHnfivFcJu', # OR this but not both: + 'RESOURCE_SERVER_INTROSPECTION_CREDENTIALS': (CLIENT_ID, CLIENT_SECRET), +} + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', + 'rest_framework.authentication.SessionAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + ), + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', + 'PAGE_SIZE': 200, + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", +} + + +TEMPLATES[0]['DIRS'] += [BASE_DIR / 'templates'] + +AUTHENTICATION_BACKENDS = ( + 'oauth2_provider.backends.OAuth2Backend', + 'django.contrib.auth.backends.ModelBackend', +) + + +DATABASES = { + 'default': { + "ENGINE": "django.contrib.gis.db.backends.postgis", + 'NAME': config('DB_NAME'), + 'USER': config('DB_USER'), + 'PASSWORD': config('DB_PASSWORD'), + 'HOST': config('DB_HOST', '127.0.0.1'), + 'PORT': config('DB_PORT', ''), + } +} + +USE_L10N = True + + +LOCALE_PATHS = [ + BASE_DIR / 'locale', +] + + +MEDIA_URL = 'media/' +MEDIA_ROOT = BASE_DIR / 'media' + + +if DEBUG == True: + STATICFILES_DIRS = ( + BASE_DIR / 'static', + ) +else: + STATIC_ROOT = BASE_DIR / 'static' + + + +LOGIN_URL = '/users/login/request/' +LOGIN_REDIRECT_URL = '/' + +CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" +CRISPY_TEMPLATE_PACK = "bootstrap5" + +SPECTACULAR_SETTINGS = { + 'TITLE': 'merchants', + 'DESCRIPTION': 'service api reference', + 'VERSION': '1.0.0', + 'SERVE_INCLUDE_SCHEMA': False, + # OTHER SETTINGS + "PARSER_WHITELIST": ["rest_framework.parsers.JSONParser"], + +} + +AUTH_USER_MODEL = 'users.User' +CORS_ORIGIN_ALLOW_ALL = True +CORS_ALLOW_ALL_ORIGINS = True +CSRF_TRUSTED_ORIGINS = ['http://*', 'https://*', 'http://*.gooyal.com', 'https://*.gooyal.com'] + +JALALI_DATE_DEFAULTS = { + 'Strftime': { + 'date': '%Y/%m/%d', + 'datetime': '%H:%M:%S _ %Y/%m/%d', + } +} + +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": config('BASE_REDIS_URL'), + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + } + } +} + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': '{levelname} AT: {asctime} LINE: {lineno} LOGGER: {name} FILE: {pathname} MESSAGE: {message}', + 'style': '{', + }, + 'simple': { + 'format': '{levelname} {message}', + 'style': '{', + }, + 'loki': { + 'class': "utils.logs.LokiFormatter", # required + }, + }, + + 'handlers': { + 'loki': { + 'level': 'DEBUG', # Log level. Required + 'class': 'utils.logs.LokiHandler', # Required + 'formatter': 'loki', # Loki formatter. Required + 'timeout': 1, # Post request timeout, default is 0.5. Optional + 'url': 'https://loki.winsoo.ir/loki/api/v1/push', # Loki url. Defaults to localhost. Optional. + # 'auth': ("user", "password"), # Basic auth to authenticate with loki. Default is None (i.e. no auth). Optional + 'tags': { "app": "ads", "env": "production" }, # Tags / Labels to attach to the log. Optional, but strongly encoraged to use. + 'mode': 'thread', # Push mode. Can be 'sync' or 'thread'. Sync is blocking, thread is non-blocking. Defaults to sync. Optional. + }, + 'file': { + 'level': 'DEBUG', + 'class': 'logging.handlers.TimedRotatingFileHandler', + 'filename': 'logs/app.log', + 'formatter': 'verbose', + "when": 'D', + "backupCount": 5, + }, + }, + 'loggers': { + 'django': { + 'handlers': ['loki'], + 'level': 'INFO', + }, + 'root': { + 'handlers': ['file'], + 'level': 'DEBUG', + 'propagate': True, + }, + }, +} + +from datetime import timedelta +from typing import List, Tuple + +STORAGES = { + "default": { + "BACKEND": "django_minio_backend.models.MinioBackend", + }, + "staticfiles": { + # "BACKEND": "django_minio_backend.models.MinioBackendStatic", + # "BACKEND": "django.core.files.storage.FileSystemStorage", + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, +} + +MINIO_ENDPOINT = config('MINIO_ENDPOINT', default='drive.gooyal.com') +MINIO_USE_HTTPS = config('MINIO_USE_HTTPS', default=True, cast=bool) + +MINIO_EXTERNAL_ENDPOINT = config('MINIO_EXTERNAL_ENDPOINT', default='drive.gooyal.com') # Default is same as MINIO_ENDPOINT +MINIO_EXTERNAL_ENDPOINT_USE_HTTPS = config('MINIO_EXTERNAL_ENDPOINT_USE_HTTPS', default=True, cast=bool) # Default is same as MINIO_USE_HTTPS +MINIO_REGION = None # Default is set to None +MINIO_ACCESS_KEY = config('MINIO_ACCESS_KEY') +MINIO_SECRET_KEY = config('MINIO_SECRET_KEY') +MINIO_URL_EXPIRY_HOURS = timedelta(days=1) # Default is 7 days (longest) if not defined +MINIO_CONSISTENCY_CHECK_ON_START = False +MINIO_MEDIA_FILES_BUCKET = config('MINIO_MEDIA_FILES_BUCKET') # replacement for MEDIA_ROOT +# MINIO_STATIC_FILES_BUCKET = 'my-static-files-bucket' # replacement for STATIC_ROOT + +MINIO_PRIVATE_BUCKETS = [ + 'default', +] +MINIO_PUBLIC_BUCKETS = [ + 'default-public', + MINIO_MEDIA_FILES_BUCKET +] +MINIO_POLICY_HOOKS: List[Tuple[str, dict]] = [] +MINIO_BUCKET_CHECK_ON_SAVE = True # Default: True // Creates bucket if missing, then save + +# Custom HTTP Client (OPTIONAL) +import os +import certifi +import urllib3 + +timeout = timedelta(minutes=5).seconds +ca_certs = os.environ.get('SSL_CERT_FILE') or certifi.where() +MINIO_HTTP_CLIENT: urllib3.poolmanager.PoolManager = urllib3.PoolManager( + timeout=urllib3.util.Timeout(connect=timeout, read=timeout), + maxsize=10, + cert_reqs='CERT_REQUIRED', + ca_certs=ca_certs, + retries=urllib3.Retry( + total=5, + backoff_factor=0.2, + status_forcelist=[500, 502, 503, 504] + ) +) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7fec122 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,186 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile +# +amqp==5.3.1 + # via kombu +argon2-cffi==25.1.0 + # via minio +argon2-cffi-bindings==21.2.0 + # via argon2-cffi +asgiref==3.9.1 + # via + # django + # django-cors-headers +async-timeout==5.0.1 + # via redis +attrs==25.3.0 + # via + # jsonschema + # referencing +billiard==4.2.1 + # via celery +celery==5.5.3 + # via -r requirements.in +certifi==2025.7.14 + # via + # minio + # requests +cffi==1.17.1 + # via + # argon2-cffi-bindings + # cryptography +charset-normalizer==3.4.2 + # via requests +click==8.2.1 + # via + # celery + # click-didyoumean + # click-plugins + # click-repl +click-didyoumean==0.3.1 + # via celery +click-plugins==1.1.1.2 + # via celery +click-repl==0.3.0 + # via celery +crispy-bootstrap5==2025.6 + # via -r requirements.in +cryptography==45.0.5 + # via jwcrypto +django==5.2.4 + # via + # -r requirements.in + # crispy-bootstrap5 + # django-cors-headers + # django-crispy-forms + # django-filter + # django-jalali-date + # django-minio-backend + # django-oauth-toolkit + # django-redis + # djangorestframework + # drf-spectacular +django-cors-headers==4.7.0 + # via -r requirements.in +django-crispy-forms==2.4 + # via + # -r requirements.in + # crispy-bootstrap5 +django-filter==25.1 + # via -r requirements.in +django-jalali-date==2.0.0 + # via -r requirements.in +django-minio-backend==3.8.0 + # via -r requirements.in +django-oauth-toolkit==3.0.1 + # via -r requirements.in +django-redis==6.0.0 + # via -r requirements.in +djangorestframework==3.16.0 + # via + # -r requirements.in + # drf-spectacular +drf-spectacular==0.28.0 + # via -r requirements.in +gevent==25.5.1 + # via -r requirements.in +greenlet==3.2.3 + # via gevent +gunicorn==23.0.0 + # via -r requirements.in +idna==3.10 + # via requests +inflection==0.5.1 + # via drf-spectacular +jalali-core==1.0.0 + # via jdatetime +jdatetime==5.2.0 + # via django-jalali-date +jsonschema==4.25.0 + # via drf-spectacular +jsonschema-specifications==2025.4.1 + # via jsonschema +jwcrypto==1.5.6 + # via django-oauth-toolkit +kombu==5.5.4 + # via celery +minio==7.2.16 + # via django-minio-backend +oauthlib==3.3.1 + # via django-oauth-toolkit +packaging==25.0 + # via + # gunicorn + # kombu +pillow==11.3.0 + # via -r requirements.in +prompt-toolkit==3.0.51 + # via click-repl +psycopg[binary,pool]==3.2.9 + # via -r requirements.in +psycopg-binary==3.2.9 + # via psycopg +psycopg-pool==3.2.6 + # via psycopg +pycparser==2.22 + # via cffi +pycryptodome==3.23.0 + # via minio +python-dateutil==2.9.0.post0 + # via celery +python-decouple==3.8 + # via -r requirements.in +pyyaml==6.0.2 + # via drf-spectacular +redis==6.2.0 + # via + # -r requirements.in + # django-redis +referencing==0.36.2 + # via + # jsonschema + # jsonschema-specifications +requests==2.32.4 + # via + # -r requirements.in + # django-oauth-toolkit +rpds-py==0.26.0 + # via + # jsonschema + # referencing +six==1.17.0 + # via python-dateutil +sqlparse==0.5.3 + # via django +typing-extensions==4.14.1 + # via + # jwcrypto + # minio + # psycopg + # psycopg-pool + # referencing +tzdata==2025.2 + # via kombu +uritemplate==4.2.0 + # via drf-spectacular +urllib3==2.5.0 + # via + # minio + # requests +vine==5.1.0 + # via + # amqp + # celery + # kombu +wcwidth==0.2.13 + # via prompt-toolkit +zope-event==5.1.1 + # via gevent +zope-interface==7.2 + # via gevent + +# The following packages are considered to be unsafe in a requirements file: +# setuptools