EmailVerify LogoEmailVerify

Django

Email checker for Django. Python email verification in Django views and forms.

커스텀 검증기, 폼 필드 및 미들웨어를 사용하여 Django 애플리케이션에 EmailVerify를 통합하세요.

설치

pip install emailverify

설정

Django 설정에 추가하세요:

# settings.py
EMAILVERIFY_API_KEY = os.environ.get('EMAILVERIFY_API_KEY')

# 선택적 설정
EMAILVERIFY_TIMEOUT = 10  # 초
EMAILVERIFY_BLOCK_DISPOSABLE = True
EMAILVERIFY_BLOCK_ROLE_BASED = False
EMAILVERIFY_CACHE_DURATION = 3600  # 1시간

커스텀 검증기

재사용 가능한 이메일 검증기를 생성하세요.

# validators.py
from django.core.exceptions import ValidationError
from django.conf import settings
from emailverify import Client
import logging

logger = logging.getLogger(__name__)

def validate_email_deliverable(email):
    """이메일 주소가 전송 가능한지 검증합니다."""
    client = Client(api_key=settings.EMAILVERIFY_API_KEY)

    try:
        result = client.verify(email)

        if result.status == 'invalid':
            raise ValidationError(
                '유효한 이메일 주소를 입력해 주세요.',
                code='invalid_email'
            )

        if getattr(settings, 'EMAILVERIFY_BLOCK_DISPOSABLE', True):
            if result.result.disposable:
                raise ValidationError(
                    '일회용 이메일 주소는 허용되지 않습니다.',
                    code='disposable_email'
                )

        if getattr(settings, 'EMAILVERIFY_BLOCK_ROLE_BASED', False):
            if result.result.role:
                raise ValidationError(
                    '역할 기반 이메일 주소는 허용되지 않습니다.',
                    code='role_email'
                )

    except Exception as e:
        # 에러를 로깅하지만 제출은 차단하지 않음
        logger.warning(f'{email}에 대한 이메일 검증 실패: {e}')


class EmailDeliverableValidator:
    """커스터마이즈 가능한 옵션이 있는 클래스 기반 검증기."""

    def __init__(self, block_disposable=True, block_role_based=False):
        self.block_disposable = block_disposable
        self.block_role_based = block_role_based
        self.client = Client(api_key=settings.EMAILVERIFY_API_KEY)

    def __call__(self, email):
        try:
            result = self.client.verify(email)

            if result.status == 'invalid':
                raise ValidationError(
                    '유효한 이메일 주소를 입력해 주세요.',
                    code='invalid_email'
                )

            if self.block_disposable and result.result.disposable:
                raise ValidationError(
                    '일회용 이메일 주소는 허용되지 않습니다.',
                    code='disposable_email'
                )

            if self.block_role_based and result.result.role:
                raise ValidationError(
                    '역할 기반 이메일 주소는 허용되지 않습니다.',
                    code='role_email'
                )

        except ValidationError:
            raise
        except Exception as e:
            logger.warning(f'이메일 검증 실패: {e}')

    def __eq__(self, other):
        return (
            isinstance(other, EmailDeliverableValidator) and
            self.block_disposable == other.block_disposable and
            self.block_role_based == other.block_role_based
        )

모델 필드

모델 필드에 검증을 추가하세요.

# models.py
from django.db import models
from .validators import validate_email_deliverable

class User(models.Model):
    email = models.EmailField(
        unique=True,
        validators=[validate_email_deliverable]
    )
    name = models.CharField(max_length=255)
    email_verification_status = models.CharField(
        max_length=20,
        blank=True,
        null=True
    )
    email_verification_score = models.FloatField(
        blank=True,
        null=True
    )
    email_verified_at = models.DateTimeField(
        blank=True,
        null=True
    )

    class Meta:
        db_table = 'users'

폼 통합

기본 폼

# forms.py
from django import forms
from django.core.validators import EmailValidator
from .validators import EmailDeliverableValidator

class RegistrationForm(forms.Form):
    name = forms.CharField(max_length=255)
    email = forms.EmailField(
        validators=[
            EmailValidator(),
            EmailDeliverableValidator(
                block_disposable=True,
                block_role_based=False
            )
        ]
    )
    password = forms.CharField(widget=forms.PasswordInput)
    password_confirm = forms.CharField(widget=forms.PasswordInput)

    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data.get('password')
        password_confirm = cleaned_data.get('password_confirm')

        if password and password_confirm and password != password_confirm:
            raise forms.ValidationError('비밀번호가 일치하지 않습니다.')

        return cleaned_data

모델 폼

# forms.py
from django import forms
from .models import User
from .validators import validate_email_deliverable

class UserForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['name', 'email']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 이메일 필드에 검증기 추가
        self.fields['email'].validators.append(validate_email_deliverable)

뷰 통합

# views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from django.views import View
from emailverify import Client
from django.conf import settings
from .forms import RegistrationForm
from .models import User

class RegistrationView(View):
    template_name = 'registration/register.html'

    def get(self, request):
        form = RegistrationForm()
        return render(request, self.template_name, {'form': form})

    def post(self, request):
        form = RegistrationForm(request.POST)

        if form.is_valid():
            # 전체 결과로 추가 검증
            client = Client(api_key=settings.EMAILVERIFY_API_KEY)
            email = form.cleaned_data['email']

            try:
                result = client.verify(email)

                # 검증 데이터와 함께 사용자 생성
                user = User.objects.create(
                    name=form.cleaned_data['name'],
                    email=email,
                    email_verification_status=result.status,
                    email_verification_score=result.score,
                )
                user.set_password(form.cleaned_data['password'])
                user.save()

                messages.success(request, '회원가입이 완료되었습니다!')
                return redirect('login')

            except Exception as e:
                messages.error(request, '회원가입에 실패했습니다. 다시 시도해 주세요.')

        return render(request, self.template_name, {'form': form})

미들웨어

들어오는 요청에서 이메일을 검증하세요.

# middleware.py
from django.http import JsonResponse
from django.conf import settings
from emailverify import Client
import logging
import json

logger = logging.getLogger(__name__)

class EmailVerificationMiddleware:
    """POST 요청에서 이메일 주소를 검증하는 미들웨어."""

    def __init__(self, get_response):
        self.get_response = get_response
        self.client = Client(api_key=settings.EMAILVERIFY_API_KEY)
        self.protected_paths = getattr(
            settings,
            'EMAILVERIFY_PROTECTED_PATHS',
            ['/api/register/', '/api/subscribe/']
        )

    def __call__(self, request):
        if request.method == 'POST' and self._should_verify(request.path):
            email = self._extract_email(request)

            if email:
                try:
                    result = self.client.verify(email)

                    if result.status == 'invalid':
                        return JsonResponse(
                            {'error': '유효하지 않은 이메일 주소입니다'},
                            status=400
                        )

                    # 검증 결과를 요청에 첨부
                    request.email_verification = result

                except Exception as e:
                    logger.warning(f'이메일 검증 실패: {e}')

        return self.get_response(request)

    def _should_verify(self, path):
        return any(path.startswith(p) for p in self.protected_paths)

    def _extract_email(self, request):
        # JSON 본문 시도
        if request.content_type == 'application/json':
            try:
                data = json.loads(request.body)
                return data.get('email')
            except json.JSONDecodeError:
                pass

        # POST 데이터 시도
        return request.POST.get('email')

미들웨어 등록

# settings.py
MIDDLEWARE = [
    # ... 다른 미들웨어
    'myapp.middleware.EmailVerificationMiddleware',
]

EMAILVERIFY_PROTECTED_PATHS = [
    '/api/register/',
    '/api/subscribe/',
    '/api/contact/',
]

캐싱

API 호출을 줄이기 위해 검증 결과를 캐시하세요.

# services.py
from django.core.cache import cache
from django.conf import settings
from emailverify import Client
import hashlib

class EmailVerificationService:
    def __init__(self):
        self.client = Client(api_key=settings.EMAILVERIFY_API_KEY)
        self.cache_duration = getattr(
            settings,
            'EMAILVERIFY_CACHE_DURATION',
            3600
        )

    def verify(self, email):
        cache_key = self._get_cache_key(email)
        cached = cache.get(cache_key)

        if cached:
            return cached

        result = self.client.verify(email)
        cache.set(cache_key, result, self.cache_duration)

        return result

    def is_valid(self, email):
        result = self.verify(email)
        return result.status == 'valid'

    def is_disposable(self, email):
        result = self.verify(email)
        return result.result.disposable

    def clear_cache(self, email):
        cache.delete(self._get_cache_key(email))

    def _get_cache_key(self, email):
        email_hash = hashlib.md5(email.lower().encode()).hexdigest()
        return f'email_verification:{email_hash}'


# 사용법
verification_service = EmailVerificationService()

if verification_service.is_valid(email):
    # 유효한 이메일 처리
    pass

Django REST Framework

시리얼라이저 유효성 검사

# serializers.py
from rest_framework import serializers
from django.conf import settings
from emailverify import Client

class RegistrationSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=255)
    email = serializers.EmailField()
    password = serializers.CharField(write_only=True)

    def validate_email(self, value):
        client = Client(api_key=settings.EMAILVERIFY_API_KEY)

        try:
            result = client.verify(value)

            if result.status == 'invalid':
                raise serializers.ValidationError(
                    '유효한 이메일 주소를 입력해 주세요.'
                )

            if result.result.disposable:
                raise serializers.ValidationError(
                    '일회용 이메일은 허용되지 않습니다.'
                )

            # 나중에 사용하기 위해 결과 저장
            self.context['email_verification'] = result

        except serializers.ValidationError:
            raise
        except Exception as e:
            # 로깅하지만 차단하지 않음
            pass

        return value

커스텀 권한

# permissions.py
from rest_framework.permissions import BasePermission
from django.conf import settings
from emailverify import Client

class ValidEmailPermission(BasePermission):
    """유효한 이메일 주소가 있는 요청만 허용."""

    message = '유효한 이메일 주소가 필요합니다.'

    def has_permission(self, request, view):
        email = request.data.get('email')

        if not email:
            return True  # 다른 검증기가 누락된 이메일 처리

        client = Client(api_key=settings.EMAILVERIFY_API_KEY)

        try:
            result = client.verify(email)
            return result.status != 'invalid'
        except Exception:
            return True  # API 에러 시 허용

관리 명령어

사용자 이메일 대량 검증.

# management/commands/verify_emails.py
from django.core.management.base import BaseCommand
from django.conf import settings
from emailverify import Client
from myapp.models import User
import time

class Command(BaseCommand):
    help = '모든 사용자 이메일 주소 검증'

    def add_arguments(self, parser):
        parser.add_argument(
            '--batch-size',
            type=int,
            default=100,
            help='배치당 이메일 수'
        )

    def handle(self, *args, **options):
        client = Client(api_key=settings.EMAILVERIFY_API_KEY)
        batch_size = options['batch_size']

        users = User.objects.filter(
            email_verification_status__isnull=True
        )

        total = users.count()
        self.stdout.write(f'{total}개 이메일 검증 중...')

        # 대량 검증을 위한 이메일 수집
        emails = list(users.values_list('email', flat=True)[:10000])

        # 대량 작업 제출
        job = client.verify_bulk(emails)
        self.stdout.write(f'작업 ID: {job.job_id}')

        # 완료 대기
        while True:
            status = client.get_bulk_job_status(job.job_id)
            self.stdout.write(
                f'진행률: {status.progress_percent}%',
                ending='\r'
            )

            if status.status == 'completed':
                break

            time.sleep(5)

        self.stdout.write('')

        # 결과 가져와서 처리
        results = client.get_bulk_job_results(job.job_id)

        for result in results.results:
            User.objects.filter(email=result.email).update(
                email_verification_status=result.status,
                email_verification_score=result.score,
            )

        self.stdout.write(self.style.SUCCESS('검증 완료!'))

        # 요약 출력
        summary = {}
        for result in results.results:
            summary[result.status] = summary.get(result.status, 0) + 1

        for status, count in summary.items():
            self.stdout.write(f'  {status}: {count}')

시그널

모델 이벤트에 반응하세요.

# signals.py
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.conf import settings
from emailverify import Client
from .models import User
import logging

logger = logging.getLogger(__name__)

@receiver(pre_save, sender=User)
def verify_user_email(sender, instance, **kwargs):
    """사용자 저장 전 이메일 검증."""
    # 이메일이 변경되지 않은 경우 건너뛰기
    if instance.pk:
        try:
            old_instance = User.objects.get(pk=instance.pk)
            if old_instance.email == instance.email:
                return
        except User.DoesNotExist:
            pass

    client = Client(api_key=settings.EMAILVERIFY_API_KEY)

    try:
        result = client.verify(instance.email)
        instance.email_verification_status = result.status
        instance.email_verification_score = result.score

    except Exception as e:
        logger.warning(f'이메일 검증 실패: {e}')

테스트

# tests.py
from django.test import TestCase
from unittest.mock import patch, MagicMock
from .validators import validate_email_deliverable
from django.core.exceptions import ValidationError

class EmailValidationTest(TestCase):
    @patch('validators.Client')
    def test_valid_email_passes(self, mock_client):
        mock_result = MagicMock()
        mock_result.status = 'valid'
        mock_result.result.disposable = False
        mock_result.result.role = False

        mock_client.return_value.verify.return_value = mock_result

        # 예외가 발생하지 않아야 함
        validate_email_deliverable('valid@example.com')

    @patch('validators.Client')
    def test_invalid_email_fails(self, mock_client):
        mock_result = MagicMock()
        mock_result.status = 'invalid'

        mock_client.return_value.verify.return_value = mock_result

        with self.assertRaises(ValidationError):
            validate_email_deliverable('invalid@example.com')

    @patch('validators.Client')
    def test_disposable_email_blocked(self, mock_client):
        mock_result = MagicMock()
        mock_result.status = 'valid'
        mock_result.result.disposable = True

        mock_client.return_value.verify.return_value = mock_result

        with self.assertRaises(ValidationError):
            validate_email_deliverable('temp@mailinator.com')

관련 리소스

On this page