Coverage for website/newsletters/services.py: 68.00%

65 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2025-08-14 10:31 +0000

1import base64 

2import logging 

3from io import BytesIO 

4 

5from django.core.files.base import ContentFile 

6from django.template.loader import get_template 

7from django.utils import timezone 

8 

9import requests 

10from bs4 import BeautifulSoup 

11from PIL import Image 

12 

13from events.models import Event 

14from newsletters import emails 

15from partners.models import Partner 

16 

17from .signals import sent_newsletter 

18 

19logger = logging.getLogger(__name__) 

20 

21 

22def save_to_disk(newsletter): 

23 """Write the newsletter as HTML to file (in all languages).""" 

24 main_partner = Partner.objects.filter(is_main_partner=True).first() 

25 local_partners = split_local_partners() 

26 

27 html_template = get_template("newsletters/email.html") 

28 

29 context = { 

30 "newsletter": newsletter, 

31 "agenda_events": ( 

32 newsletter.newslettercontent_set.filter(newsletteritem=None).order_by( 

33 "newsletterevent__start_datetime" 

34 ) 

35 ), 

36 "main_partner": main_partner, 

37 "local_partners": local_partners, 

38 } 

39 

40 html_message = html_template.render(context) 

41 html_message = embed_linked_html_images(html_message) 

42 

43 newsletter.rendered_file = ContentFile( 

44 html_message.encode("utf-8"), name="newsletter.html" 

45 ) 

46 newsletter.save() 

47 

48 

49def embed_linked_html_images(html_input): 

50 bs = BeautifulSoup(html_input, "html.parser") 

51 

52 output = html_input 

53 images = bs.findAll("img") 

54 

55 for image in images: 

56 source = image["src"] 

57 if not "source".startswith(("http://", "https://")): 57 ↛ 59line 57 didn't jump to line 59 because the condition on line 57 was always true

58 continue 

59 try: 

60 response = requests.get(source, timeout=30.0) 

61 image = Image.open(BytesIO(response.content)) 

62 buffer = BytesIO() 

63 image.save(buffer, format="png") 

64 base64_image = base64.b64encode(buffer.getvalue()).decode("utf-8") 

65 encoded_image = "data:image/png;base64," + base64_image 

66 output = output.replace(source, encoded_image) 

67 except OSError: 

68 logger.warning(f"Image could not be found: {image}") 

69 

70 return output 

71 

72 

73def get_agenda(start_date): 

74 end_date = start_date + timezone.timedelta(weeks=2) 

75 published_events = Event.objects.filter(published=True) 

76 base_events = published_events.filter( 

77 start__gte=start_date, end__lt=end_date 

78 ).order_by("start") 

79 if base_events.count() < 10: 

80 more_events = published_events.filter(end__gte=end_date).order_by("start") 

81 return [*base_events, *more_events][:10] 

82 return base_events 

83 

84 

85def send_newsletter(newsletter): 

86 emails.send_newsletter(newsletter) 

87 newsletter.sent = True 

88 newsletter.save() 

89 

90 sent_newsletter.send(sender=None, newsletter=newsletter) 

91 

92 save_to_disk(newsletter) 

93 

94 

95def split_local_partners(): 

96 all_local_partners = Partner.objects.filter( 

97 is_local_partner=True, is_active=True 

98 ).order_by("?") 

99 local_partner_count = len(all_local_partners) 

100 local_partners = [] 

101 for i in range(local_partner_count // 2): 101 ↛ 102line 101 didn't jump to line 102 because the loop on line 101 never started

102 local_partners.append( 

103 [all_local_partners[i * 2], all_local_partners[i * 2 + 1]] 

104 ) 

105 

106 if local_partner_count % 2 != 0: 106 ↛ 107line 106 didn't jump to line 107 because the condition on line 106 was never true

107 local_partners.append([all_local_partners[local_partner_count - 1]]) 

108 

109 return local_partners