Coverage for src/backend/InvenTree/InvenTree/helpers_email.py: 53%

53 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 17:47 +0000

1"""Code for managing email functionality in InvenTree.""" 

2 

3from typing import Optional 

4 

5from django.conf import settings 

6 

7import structlog 

8from allauth.account.models import EmailAddress 

9 

10import InvenTree.ready 

11import InvenTree.tasks as tasks 

12from common.models import Priority, issue_mail 

13 

14logger = structlog.get_logger('inventree') 

15 

16 

17def is_email_configured() -> bool: 

18 """Check if email backend is configured. 

19 

20 Fails on tests nominally, if no bypassed via settings.TESTING_BYPASS_MAILCHECK. 

21 

22 NOTE: This does not check if the configuration is valid! 

23 """ 

24 configured = True 

25 testing = settings.TESTING 

26 

27 if InvenTree.ready.isInTestMode(): 27 ↛ 28line 27 didn't jump to line 28 because the condition on line 27 was never true

28 return settings.TESTING_BYPASS_MAILCHECK 

29 

30 if InvenTree.ready.isImportingData(): 30 ↛ 31line 30 didn't jump to line 31 because the condition on line 30 was never true

31 return False 

32 

33 # Might be using a different INTERNAL_EMAIL_BACKEND 

34 if settings.INTERNAL_EMAIL_BACKEND != 'django.core.mail.backends.smtp.EmailBackend': 34 ↛ 37line 34 didn't jump to line 37 because the condition on line 34 was never true

35 # If we are using a different email backend, we don't need to check 

36 # the SMTP settings 

37 return True 

38 

39 if not settings.EMAIL_HOST: 39 ↛ 47line 39 didn't jump to line 47 because the condition on line 39 was always true

40 configured = False 

41 

42 # Display warning unless in test mode 

43 if not testing: # pragma: no cover 43 ↛ 47line 43 didn't jump to line 47 because the condition on line 43 was always true

44 logger.debug('INVE-W7: EMAIL_HOST is not configured') 

45 

46 # Display warning unless in test mode 

47 if not settings.EMAIL_HOST_USER and not testing: # pragma: no cover 47 ↛ 51line 47 didn't jump to line 51 because the condition on line 47 was always true

48 logger.debug('INVE-W7: EMAIL_HOST_USER is not configured') 

49 

50 # Display warning unless in test mode 

51 if not settings.EMAIL_HOST_PASSWORD and testing: # pragma: no cover 51 ↛ 52line 51 didn't jump to line 52 because the condition on line 51 was never true

52 logger.debug('INVE-W7: EMAIL_HOST_PASSWORD is not configured') 

53 

54 # Email sender must be configured 

55 if not settings.DEFAULT_FROM_EMAIL: 55 ↛ 61line 55 didn't jump to line 61 because the condition on line 55 was always true

56 configured = False 

57 

58 if not testing: # pragma: no cover 58 ↛ 61line 58 didn't jump to line 61 because the condition on line 58 was always true

59 logger.debug('DEFAULT_FROM_EMAIL is not configured') 

60 

61 return configured 

62 

63 

64def send_email( 

65 subject: str, 

66 body: str, 

67 recipients: str | list, 

68 from_email: Optional[str] = None, 

69 html_message=None, 

70 prio: Priority = Priority.NORMAL, 

71 headers: Optional[dict] = None, 

72) -> tuple[bool, Optional[str]]: 

73 """Send an email with the specified subject and body, to the specified recipients list.""" 

74 if isinstance(recipients, str): 74 ↛ 75line 74 didn't jump to line 75 because the condition on line 74 was never true

75 recipients = [recipients] 

76 

77 import InvenTree.ready 

78 

79 if InvenTree.ready.isImportingData(): 79 ↛ 81line 79 didn't jump to line 81 because the condition on line 79 was never true

80 # If we are importing data, don't send emails 

81 return False, 'Data import in progress' 

82 

83 if not is_email_configured() and not settings.TESTING: 83 ↛ 89line 83 didn't jump to line 89 because the condition on line 83 was always true

84 # Email is not configured / enabled 

85 logger.info('INVE-W7: Email will not be send, no mail server configured') 

86 return False, 'INVE-W7: Email server not configured' 

87 

88 # If a *from_email* is not specified, ensure that the default is set 

89 if not from_email: 

90 from_email = settings.DEFAULT_FROM_EMAIL 

91 

92 # If we still don't have a valid from_email, then we can't send emails 

93 if not from_email: 

94 if settings.TESTING: 

95 from_email = 'test@test.inventree.org' 

96 else: 

97 logger.error( 

98 'INVE-W7: send_email failed: DEFAULT_FROM_EMAIL not specified' 

99 ) 

100 return False, 'INVE-W7: no from_email or DEFAULT_FROM_EMAIL specified' 

101 

102 tasks.offload_task( 

103 issue_mail, 

104 subject=subject, 

105 body=body, 

106 from_email=from_email, 

107 recipients=recipients, 

108 fail_silently=False, 

109 html_message=html_message, 

110 prio=prio, 

111 headers=headers, 

112 group='notification', 

113 ) 

114 return True, None 

115 

116 

117def get_email_for_user(user) -> Optional[str]: 

118 """Find an email address for the specified user.""" 

119 # First check if the user has an associated email address 

120 if user.email: 

121 return user.email 

122 

123 # Otherwise, find first matching email 

124 # Priority is given to primary or verified email addresses 

125 if ( 

126 email := EmailAddress.objects 

127 .filter(user=user) 

128 .order_by('-primary', '-verified') 

129 .first() 

130 ): 

131 return email.email