본문 바로가기
소프트웨어 마에스트로/BackEnd(Django)

[Django] mail service 도입하기 2

by alpakaka 2024. 9. 22.

mail 서비스를 도입해본다!

할일

1. welcome 메일 보내기

2. admin에 html 을 올리면 모든 활성사용자이자 메일을 받기로 한 유저에게 보내기 인데 메일 체크 여부가 없어서 일단 모든 활성 유저

 

 

일단 mail을 만들어봤는데

username 관련으로 db 를 살펴보기 위해.. admin 페이지로 갔더니 다음과 같은 에러가 떴다.

일단 다음에 해야지..  비번을 잘못입력한건가... 일단 Csrf 관련 오류가 아니길 바라고 있다.

 

1 번 welcome 이메일을 보내는 와중에 이해못하는 에러가 발생했다.

이런식으로 코드를 작성했다.

그런데 왜인지 pytest 를 할 때 마다 device create 가 출력이 되지 않았다.

음 확인해보니 DoesnotExist 후에 바로 가장 바깥의 Exception 으로 가고 있었다.

왜지... 찾아봐야겠다..

찾아보니 일단 에러가 발생해서 처리된 후에도 밖에서 또 잡히는 것 같다.

그래서 이런식으로처리하지 말고 다른 식으로 처리해야될 것 같다.

일단 임시 방편으로 이런식으로 해결했다. 테스트에서 통과하면 바꿔야겠다.

 

또 이런 에러가 발생했다.

무슨에러인고하니.. 

html 은 templates 안에 속해야 찾을 수 있는 에러인듯하다. 그래서 폴더를 만들고 그 안에 파일을 넣어주었다.

https://june98.tistory.com/81

 

Python/Django TemplateDoesNotExist 에러 처리 방법!!

최근 Django project를 하며 두들겨 맞고 있는데 이상하게 정말 자주 보이는 에러가 있다! Django에 대해 잘 몰라서 발생하는 경우가 많았는데 오늘은 간략하게 이 에러에 대한 대처 방법을 다뤄보려

june98.tistory.com

 

그랬더니 잘 해결되었다!

 

테스트 코드는 다음과 같다.

@pytest.mark.django_db
class TestGoogleLogin:
    @pytest.fixture
    def api_client(self):
        return APIClient()

    @patch("accounts.views.id_token.verify_oauth2_token")
    @patch("accounts.views.send_email")  # Ensure the correct patch path
    def test_google_login_new_user(
        self, mock_send_email, mock_verify_oauth2_token, api_client
    ):
        # Mock the token verification response
        mock_verify_oauth2_token.return_value = {
            "iss": "accounts.google.com",
            "email": "testuser@example.com",
        }

        # Define the URL for the GoogleLogin view
        url = reverse(
            "google_login"
        )  # Replace 'google-login' with your actual URL name

        # Create a mock request
        data = {"token": "mock_token", "device_token": "mock_device_token"}

        # Call the view
        response = api_client.post(url, data, format="json")
        print(response.data)

        # Check that the user was created
        user = User.objects.get(username="testuser@example.com")
        assert user is not None

        # Check that the device was created
        device = Device.objects.get(user_id=user.id, token="mock_device_token")
        assert device is not None

        # Check that the email was sent
        mock_send_email.assert_called_once_with(
            "계정이메일@gmail.com",
            "Welcome to join us",
            welcome_email(user.username),
        )

        # Check the response status
        assert response.status_code == status.HTTP_200_OK
        assert "refresh" in response.data
        assert "access" in response.data

잘 통과했다.

이제 뷰를 좀 리팩토링해본다.

 

class GoogleLogin(APIView):
    google_client_id = settings.SECRETS.get("GCID")
    authentication_classes = []
    permission_classes = [AllowAny]

    def post(self, request):
        token = request.data.get("token")
        device_token = request.data.get("device_token")
        if not device_token or not token:
            raise Exception("device token and token is required")
        try:
            idinfo = id_token.verify_oauth2_token(
                token, requests.Request(), audience=GOOGLE_CLIENT_ID
            )
            if "accounts.google.com" in idinfo["iss"]:
                email = idinfo["email"]
                user = self.get_or_create_user(email)
                Device.objects.get_or_create(user_id=user, token=device_token)
                refresh = CustomRefreshToken.for_user(user, device_token)
                return Response(
                    {
                        "refresh": str(refresh),
                        "access": str(refresh.access_token),
                    }
                )
        except Exception:
            return Response(status=status.HTTP_400_BAD_REQUEST)

    def get_or_create_user(self, email):
        try:
            user = User.objects.get(username=email, password="")
        except User.DoesNotExist:
            user = User.objects.create(username=email, password="")
            send_email(
                "확인용이메일@gmail.com",
                "Welcome to join us",
                welcome_email(user.username),
            )
        return user

함수로 처리하니 깔끔해졌다. 

일단 이정도로 하면 될 것 같다.

 

2번 문제인 메일을 작성하면 사용자 모두에게 전달하는 로직을 만들어본다.

일단 생각중인 것은 admin 페이지에 html 파일을 올리면 보낼 수 있도록 하는 로직을 생각하고 있다.

그러니 일단 admin 에 파일을 올리기, 버튼을 누르면 해당 함수를 호출하도록 수정하기를 진행하면 될 것 같다.

 

아주 간단하게 admin 페이지에 html 을 올리면 바로 모든 사용자에게 보내는 로직을 짜본다

이런식으로 작성한 후에 admin 에 등록해주었다.

저기 이메일을 팀 이메일로 했을 때는 잘 동작을 했다.

그러나 문제가 발생했는데 그 이유는 다음과 같았다.

이런 오류가 발생했다.

도메인 관련 문제라고 생각하는데 들어가보니 도메인 verification 이 실패되어 있었다.

그래서 다시 도메인을 확인하도록 변경해본다. 그런데 이게 5시간 정도 걸릴 수 있다고 해서 일단 진행해보고 내일도 fail 이 떠있으면 원인을 파악하고 안되면 다른 곳으로 옮기는 편이 나을 것 같다. 그리고 추가적으로 도메인이 완료가 되면 보내는 송신 이메일도 변경해야한다.

 

그리고 우리팀은 아직 api 횟수 제한을 넣지 않은 상태라 이것을 넣어보려고 한다.

에 잘 정돈된 정리글이 있길래 확인해주었다.

https://velog.io/@kny8092/%EC%B2%98%EB%A6%AC%EC%9C%A8-%EC%A0%9C%ED%95%9C-%EC%9E%A5%EC%B9%98-%EB%B6%99%EC%9D%B4%EA%B8%B0-%EC%97%AC%EC%A0%95

 

일단 횟수제한은 가장 기본적인 내용이기 때문에 지원되는 곳은 없는지 확인해보려고 한다. 

조건은 다음과 같은데 django나 aws에서 지원이 되면 일단 이걸로 갈 예정이다, 아직 규모가 크지 않기 때문에 비용이 저렴한 것으로 사용할 예정이며 같은 비용이면 레퍼런스가 많은 곳을 사용하려고한다.

 

찾았던 블로그글에서는  bucket4j 를 사용하는 것 같길래 한번 찾아봤다.

"" Bucket4j는 Token bucket 알고리즘을 기반으로 하는 Java 속도 제한 라이브러리 입니다. ""
그러므로 파이썬에 맞는 라이브러리를 찾으러 가보겠다...

 

https://hunstory.tistory.com/72

 

[Django] 스로틀링(Throttling) - 요청 속도 제어

목차 스로틀링(Throttling) 이란? 클라이언트가 API에 보낼 수 있는 요청 속도를 제어 할 수 있게 해준다. Request가 안정적인 상태의 요청 속도 및 버스트 한도를 초과할 경우, 요청을 실패시키고 "429 T

hunstory.tistory.com

여기를 확인해보니 django 자체에서 지원하는 것 같다.

일단 더 찾아봐보면 될 것 같다.

 

이쪽을 찾아보니 우리 모델에 문제가 발생했다.

1. 유료유저인지 무료유저인지 모름 (속성이 없음)

2. 이메일을 보내도 되는지 확인 안하고 있음

3. 프론트쪽에 결제하는 창이 없음

 

그래서 일단 모든 유저에게 프리미엄으로 해놓아야할 듯 하다.

일단 model 부터 변경할 필요가 있어보인다.