Skip to content

Commit

Permalink
Merge pull request #16 from ARYAN-NIKNEZHAD/fix/settings
Browse files Browse the repository at this point in the history
🔧 feat: Add custom error handling and enhanced configuration
  • Loading branch information
sepehr-akbarzadeh authored Aug 14, 2024
2 parents 8338c48 + 5475926 commit 2a3d926
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 116 deletions.
12 changes: 11 additions & 1 deletion sage_contact/constants/settings.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
# constants.py
GEOIP_PATH = None

SAGE_CONTACT_GEOIP_PATH = None

# Email
EMAIL_CONFIRMATION_SUBJECT = "We have received your contact request"
EMAIL_EXTRA_HEADERS_MIME_VERSION = "1.0"
EMAIL_EXTRA_HEADERS_CONTENT_TYPE = "text/html; charset=UTF-8"
EMAIL_EXTRA_HEADERS_CONTENT_TRANSFER_ENCODING = "quoted-printable"
EMAIL_EXTRA_HEADERS_X_PRIORITY = "3"
EMAIL_EXTRA_HEADERS_X_AUTO_RESPONSE_SUPPRESS = "All"
EMAIL_EXTRA_HEADERS_X_SPAMD_RESULT = "default: False [-0.90 / 15.00]"
4 changes: 2 additions & 2 deletions sage_contact/forms/mixins/geo_ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ def set_ip_address(self, instance):
instance.ip_address = ip_address

def set_country_from_ip(self, instance):
geoip_path = getattr(settings, "GEOIP_PATH", None)
geoip_path = getattr(settings, 'SAGE_CONTACT_GEOIP_PATH', None)
if geoip_path is None:
logger.info("GEOIP_PATH is not set. Skipping country setting.")
logger.info('SAGE_CONTACT_GEOIP_PATH is not set. Skipping country setting.')
return

ip_address = instance.ip_address
Expand Down
211 changes: 108 additions & 103 deletions sage_contact/settings/check.py
Original file line number Diff line number Diff line change
@@ -1,122 +1,127 @@
# check.py
import os

from django.conf import settings
from django.core.checks import Error, register
from django.template.utils import get_app_template_dirs
from typing import List, Dict, Any
import os

from .exc import DjangoSageContactError, DjangoSageContactConfigurationError


@register()
def check_geoip_path(app_configs, **kwargs):
errors = []
geoip_path = getattr(settings, "GEOIP_PATH", None)
def check_django_sage_contact_config(app_configs: Dict[str, Any], **kwargs: Any) -> List[Error]:
"""
Check the django-sage-contact and email template configuration for the application.
if geoip_path is not None and not os.path.exists(geoip_path):
errors.append(
Error(
"GEOIP_PATH is set to a non-existent path",
hint="Ensure the path set in GEOIP_PATH exists.",
id="geoip.E002",
)
)
This function verifies that all required settings are present and ensures the settings are correct.
Any errors encountered during these checks are returned.
Parameters
----------
app_configs : dict
The application configurations.
**kwargs
Additional keyword arguments.
Returns
-------
list of Error
A list of Error objects representing any configuration errors found.
# Check for SAGE_CONTACT_SUPPORT_EMAIL_TEMPLATE_PATH
send_email = getattr(settings, "SEND_EMAIL_AFTER_SAGE_CONTACT_SUPPORT_FORM", True)
template_path = getattr(settings, "SAGE_CONTACT_SUPPORT_EMAIL_TEMPLATE_PATH", None)
Examples
--------
>>> errors = check_geoip_path(app_configs)
>>> if errors:
... for error in errors:
... print(error)
"""
errors: List[Error] = []

if send_email:
if not template_path:
def get_settings() -> Dict[str, Any]:
return {
"SAGE_CONTACT_GEOIP_PATH": getattr(settings, 'SAGE_CONTACT_GEOIP_PATH', None),
"SEND_EMAIL_AFTER_SAGE_CONTACT_SUPPORT_FORM": getattr(settings, 'SEND_EMAIL_AFTER_SAGE_CONTACT_SUPPORT_FORM', True),
"SAGE_CONTACT_SUPPORT_EMAIL_TEMPLATE_PATH": getattr(settings, 'SAGE_CONTACT_SUPPORT_EMAIL_TEMPLATE_PATH', None),
"EMAIL_HOST": getattr(settings, 'EMAIL_HOST', None),
"EMAIL_PORT": getattr(settings, 'EMAIL_PORT', None),
"EMAIL_HOST_USER": getattr(settings, 'EMAIL_HOST_USER', None),
"EMAIL_HOST_PASSWORD": getattr(settings, 'EMAIL_HOST_PASSWORD', None),
"EMAIL_USE_TLS": getattr(settings, 'EMAIL_USE_TLS', None),
"BASE_DIR": getattr(settings, 'BASE_DIR', None),
"DEBUG": getattr(settings, 'DEBUG', True),
}

def check_geoip_path_setting(geoip_path: str) -> None:
if geoip_path and not os.path.exists(geoip_path):
errors.append(
Error(
"SAGE_CONTACT_SUPPORT_EMAIL_TEMPLATE_PATH is not set",
hint="Set the SAGE_CONTACT_SUPPORT_EMAIL_TEMPLATE_PATH in settings.",
id="sage_contact.E001",
'SAGE_CONTACT_GEOIP_PATH is set to a non-existent path',
hint='Ensure the path set in SAGE_CONTACT_GEOIP_PATH exists.',
id='geoip.E002',
)
)
else:
# Check in BASE_DIR first
if not os.path.exists(os.path.join(settings.BASE_DIR, template_path)):
# If not found in BASE_DIR, check in app template directories
template_found = False
for template_dir in get_app_template_dirs("templates"):
if os.path.exists(os.path.join(template_dir, template_path)):
template_found = True
break
if not template_found:
errors.append(
Error(
"SAGE_CONTACT_SUPPORT_EMAIL_TEMPLATE_PATH is set to a non-existent path",
hint="Ensure the path set in SAGE_CONTACT_SUPPORT_EMAIL_TEMPLATE_PATH exists.",
id="sage_contact.E002",

def check_email_template_path(send_email: bool, template_path: str, base_dir: str) -> None:
if send_email:
if not template_path:
raise DjangoSageContactConfigurationError(
detail='SAGE_CONTACT_SUPPORT_EMAIL_TEMPLATE_PATH is not set',
code='E001',
section_code='sage_contact'
)
else:
if not os.path.exists(os.path.join(base_dir, template_path)):
template_found = False
for template_dir in get_app_template_dirs('templates'):
if os.path.exists(os.path.join(template_dir, template_path)):
template_found = True
break
if not template_found:
raise DjangoSageContactConfigurationError(
detail='SAGE_CONTACT_SUPPORT_EMAIL_TEMPLATE_PATH is set to a non-existent path',
code='E002',
section_code='sage_contact'
)

def check_email_settings(send_email: bool, debug: bool) -> None:
if not debug and send_email:
email_settings: List[str] = ['EMAIL_HOST', 'EMAIL_PORT', 'EMAIL_HOST_USER', 'EMAIL_HOST_PASSWORD', 'EMAIL_USE_TLS']
for setting in email_settings:
if not getattr(settings, setting, None):
raise DjangoSageContactConfigurationError(
detail=f'{setting} is not set. The {setting} must be set in settings because DEBUG is False and '
'SEND_EMAIL_AFTER_SAGE_CONTACT_SUPPORT_FORM is True. Ensure all necessary '
'email settings are configured for the application to send emails in '
'production.',
code=f'E00{email_settings.index(setting) + 1}',
section_code='sage_contact'
)

# Additional check for email settings when DEBUG is False
if not settings.DEBUG and send_email:
if not getattr(settings, "EMAIL_HOST", None):
errors.append(
Error(
"EMAIL_HOST is not set",
hint=(
"The EMAIL_HOST must be set in settings because DEBUG is False and "
"SEND_EMAIL_AFTER_SAGE_CONTACT_SUPPORT_FORM is True. Ensure all necessary "
"email settings are configured for the application to send emails in "
"production."
),
id="sage_contact.E001",
)
)
if not getattr(settings, "EMAIL_PORT", None):
errors.append(
Error(
"EMAIL_PORT is not set",
hint=(
"The EMAIL_PORT must be set in settings because DEBUG is False and "
"SEND_EMAIL_AFTER_SAGE_CONTACT_SUPPORT_FORM is True. Ensure all necessary "
"email settings are configured for the application to send emails in "
"production."
),
id="sage_contact.E002",
)
)
if not getattr(settings, "EMAIL_HOST_USER", None):
errors.append(
Error(
"EMAIL_HOST_USER is not set",
hint=(
"The EMAIL_HOST_USER must be set in settings because DEBUG is False and "
"SEND_EMAIL_AFTER_SAGE_CONTACT_SUPPORT_FORM is True. Ensure all necessary "
"email settings are configured for the application to send emails in "
"production."
),
id="sage_contact.E003",
)
)
if not getattr(settings, "EMAIL_HOST_PASSWORD", None):
errors.append(
Error(
"EMAIL_HOST_PASSWORD is not set",
hint=(
"The EMAIL_HOST_PASSWORD must be set in settings because DEBUG is False and "
"SEND_EMAIL_AFTER_SAGE_CONTACT_SUPPORT_FORM is True. Ensure all necessary "
"email settings are configured for the application to send emails in "
"production."
),
id="sage_contact.E004",
)
try:
settings_dict: Dict[str, Any] = get_settings()
# get settings
geoip_path: str = settings_dict["SAGE_CONTACT_GEOIP_PATH"]
send_email_after_sage_contact_support_form: str = settings_dict["SEND_EMAIL_AFTER_SAGE_CONTACT_SUPPORT_FORM"]
sage_contact_support_email_template_path: str = settings_dict["SAGE_CONTACT_SUPPORT_EMAIL_TEMPLATE_PATH"]
base_dir: str = settings_dict["BASE_DIR"]
debug: bool = settings_dict["DEBUG"]

check_geoip_path_setting(geoip_path)
check_email_template_path(send_email_after_sage_contact_support_form, sage_contact_support_email_template_path, base_dir)
check_email_settings(send_email_after_sage_contact_support_form, debug)
except DjangoSageContactConfigurationError as e:
errors.append(
Error(
str(e),
id=f"{e.section_code}.{e.code}",
)
if not getattr(settings, "EMAIL_USE_TLS", None):
errors.append(
Error(
"EMAIL_USE_TLS is not set",
hint=(
"The EMAIL_USE_TLS must be set in settings because DEBUG is False and "
"SEND_EMAIL_AFTER_SAGE_CONTACT_SUPPORT_FORM is True. Ensure all necessary "
"email settings are configured for the application to send emails in "
"production."
),
id="sage_contact.E005",
)
)
except DjangoSageContactError as e:
errors.append(
Error(
str(e),
id=f"{e.section_code}.{e.code}",

)
)

return errors
return errors
74 changes: 74 additions & 0 deletions sage_contact/settings/exc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import uuid
from typing import Optional


class SageError(Exception):
"""Base class for all Sage exceptions.
Attributes:
status_code (int): HTTP status code associated with the error.
default_detail (str): Default error message.
default_code (str): Default error code.
section_code (str): Section code for categorizing errors.
detail (str): Specific error message.
code (str): Specific error code.
section_code (str): Section code for the specific error.
error_id (str): Unique identifier for the error instance.
Methods:
__init__(detail: Optional[str] = None, code: Optional[str] = None, section_code: Optional[str] = None):
Initializes the error with specific details.
__str__() -> str: Returns a formatted string representation of the error.
"""

status_code: int = 500
default_detail: str = "An error occurred."
default_code: str = "E5000"
section_code: str = "SAGE"

def __init__(self, detail: Optional[str] = None, code: Optional[str] = None, section_code: Optional[str] = None):
self.detail: str = detail if detail is not None else self.default_detail
self.code: str = code if code is not None else self.default_code
self.section_code: str = section_code if section_code is not None else self.section_code
self.error_id: str = str(uuid.uuid4())

def __str__(self) -> str:
return f"Error {self.section_code}{self.code} - {self.detail} (Error ID: {self.error_id})"


class DjangoSageContactError(SageError):
"""Exception raised for general django-sage-contact errors.
Inherits from:
SageError
Attributes:
status_code (int): HTTP status code associated with the error.
default_detail (str): Default error message for django-sage-contact errors.
default_code (str): Default error code for django-sage-contact errors.
section_code (str): Section code for django-sage-contact errors.
"""

status_code: int = 500
default_detail: str = "A Django-Sage-Contact error occurred."
default_code: str = "E5001"
section_code: str = "DSC"


class DjangoSageContactConfigurationError(DjangoSageContactError):
"""Exception raised for django-sage-contact configuration errors.
Inherits from:
DjangoSageContactError
Attributes:
status_code (int): HTTP status code associated with the error.
default_detail (str): Default error message for configuration errors.
default_code (str): Default error code for configuration errors.
section_code (str): Section code for configuration errors.
"""

status_code: int = 400
default_detail: str = "Invalid Django-Sage-Contact configuration."
default_code: str = "E4001"
section_code: str = "CFG"
30 changes: 20 additions & 10 deletions sage_contact/signals/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.template.loader import render_to_string
from django.db.models.signals import pre_save, post_save
from sage_contact.constants.settings import (
EMAIL_EXTRA_HEADERS_CONTENT_TRANSFER_ENCODING,
EMAIL_EXTRA_HEADERS_CONTENT_TYPE,
EMAIL_EXTRA_HEADERS_MIME_VERSION,
EMAIL_EXTRA_HEADERS_X_AUTO_RESPONSE_SUPPRESS,
EMAIL_EXTRA_HEADERS_X_PRIORITY,
EMAIL_EXTRA_HEADERS_X_SPAMD_RESULT,
EMAIL_CONFIRMATION_SUBJECT,
)
from sage_contact.models import (
FullSupportRequest,
)

from sage_contact.models import (FullSupportRequest, SupportRequestBase,
SupportRequestWithLocation,
SupportRequestWithPhone)


@receiver(pre_save, sender=FullSupportRequest)
Expand Down Expand Up @@ -49,7 +59,7 @@ def send_confirmation_email(sender, instance, created, **kwargs):
domain = current_site.domain

# Create the email subject and body using an HTML template
subject = "We have received your contact request"
subject = EMAIL_CONFIRMATION_SUBJECT
body = render_to_string(
template_path,
{
Expand All @@ -74,13 +84,13 @@ def send_confirmation_email(sender, instance, created, **kwargs):

# Add standard headers
email.extra_headers = {
"MIME-Version": "1.0",
"Content-Type": "text/html; charset=UTF-8",
"Content-Transfer-Encoding": "quoted-printable",
"X-Priority": "3",
"MIME-Version": EMAIL_EXTRA_HEADERS_MIME_VERSION,
"Content-Type": EMAIL_EXTRA_HEADERS_CONTENT_TYPE,
"Content-Transfer-Encoding": EMAIL_EXTRA_HEADERS_CONTENT_TRANSFER_ENCODING,
"X-Priority": EMAIL_EXTRA_HEADERS_X_PRIORITY,
"Message-ID": make_msgid(domain=domain),
"X-Auto-Response-Suppress": "All",
"X-Spamd-Result": "default: False [-0.90 / 15.00]",
"X-Auto-Response-Suppress": EMAIL_EXTRA_HEADERS_X_AUTO_RESPONSE_SUPPRESS,
"X-Spamd-Result": EMAIL_EXTRA_HEADERS_X_SPAMD_RESULT,
}

# Send the email
Expand Down

0 comments on commit 2a3d926

Please sign in to comment.