text: import logging
  import os
  import smtplib
  import sys
  import time
  from datetime import datetime
  
  from django.conf import settings
  from django.contrib.sites.models import Site
  from django.core import mail
  from django.core.management.base import BaseCommand
  from django.template.loader import render_to_string
  from django.utils.translation import activate
  from django.utils.translation import deactivate
  from django.utils.translation import gettext as _
  
  from django_nyt import models
  from django_nyt import settings as nyt_settings
  
  # Daemon / mail loop sleep between each database poll (seconds)
  SLEEP_TIME = 120
  
  
  class Command(BaseCommand):
      can_import_settings = True
      # @ReservedAssignment
      help = (
          'Sends notification emails to subscribed users taking into account '
          'the subscription interval'
      )
  
      def add_arguments(self, parser):
          parser.add_argument(
              '--daemon', '-d',
              action='store_true',
              dest='daemon',
              help='Go to daemon mode and exit'
          )
          parser.add_argument(
              '--cron', '-c',
              action='store_true',
              dest='cron',
              help='Do not loop, just send out emails once and exit'
          )
          parser.add_argument(
              '--pid-file',
              action='store',
              dest='pid',
              help='Where to write PID before exiting',
              default='/tmp/nyt_daemon.pid'
          )
          parser.add_argument(
              '--log-file',
              action='store',
              dest='log',
              help='Where daemon should write its log',
              default='/tmp/nyt_daemon.log'
          )
          parser.add_argument(
              '--no-sys-exit',
              action='store_true',
              dest='no_sys_exit',
              help='Skip sys-exit after forking daemon (for testing purposes)'
          )
          parser.add_argument(
              '--daemon-sleep-interval',
              action='store',
              dest='sleep_time',
              help='Minimum sleep between each polling of the database.',
              default=SLEEP_TIME
          )
  
      def _send_user_notifications(self, context, connection):
          subject = _(nyt_settings.EMAIL_SUBJECT)
  
          message = render_to_string(
              'emails/notification_email_message.txt',
              context
          )
          email = mail.EmailMessage(
              subject, message, nyt_settings.EMAIL_SENDER,
              [context['user'].email], connection=connection
          )
          self.logger.info("Sending to: %s" % context['user'].email)
          email.send(fail_silently=False)
  
      def _daemonize(self):
          self.logger.info("Daemon mode enabled, forking")
          try:
              fpid = os.fork()
              if fpid > 0:
                  # Running as daemon now. PID is fpid
                  self.logger.info("PID: %s" % str(fpid))
                  with open(self.options['pid'], "w") as pid_file:
                      pid_file.write(str(fpid))
                  if not self.options['no_sys_exit']:
                      sys.exit(0)
          except OSError as e:
              sys.stderr.write(
                  "fork failed: %d (%s)\n" %
                  (e.errno, e.strerror))
              sys.exit(1)
  
      def handle(self, *args, **options):  # noqa: max-complexity=12
          # activate the language
          activate(settings.LANGUAGE_CODE)
  
          options.setdefault('daemon', False)
          options.setdefault('cron', False)
          options.setdefault('no_sys_exit', False)
  
          self.options = options
  
          daemon = options['daemon']
          cron = options['cron']
  
          assert not (daemon and cron), (
              "You cannot both choose cron and daemon options"
          )
  
          self.logger = logging.getLogger('django_nyt')
  
          if not self.logger.handlers:
              if daemon:
                  handler = logging.FileHandler(filename=options['log'])
              else:
                  handler = logging.StreamHandler(self.stdout)
              self.logger.addHandler(handler)
              self.logger.setLevel(logging.INFO)
  
          self.logger.info("Starting django_nyt e-mail dispatcher")
  
          if not nyt_settings.SEND_EMAILS:
              print("E-mails disabled - quitting.")
              sys.exit()
  
          # Run as daemon, ie. fork the process
          if daemon:
              self._daemonize()
  
          # create a connection to smtp server for reuse
          connection = mail.get_connection()
  
          if cron:
              self.send_mails(connection)
              return
  
          if not daemon:
              print("Entering send-loop, CTRL+C to exit")
          try:
              self.send_loop(connection, int(options['sleep_time']))
          except KeyboardInterrupt:
              print("\nQuitting...")
  
          # deactivate the language
          deactivate()
  
      def send_loop(self, connection, sleep_time):
  
          # This could be /improved by looking up the last notified person
          last_sent = None
  
          while True:
  
              started_sending_at = datetime.now()
              self.logger.info(
                  "Starting send loop at %s" %
                  str(started_sending_at))
              if last_sent:
                  user_settings = models.Settings.objects.filter(
                      interval__lte=(
                          (started_sending_at - last_sent).seconds // 60) // 60
                  ).order_by('user')
              else:
                  user_settings = None
  
              self.send_mails(
                  connection,
                  last_sent=last_sent,
                  user_settings=user_settings
              )
  
              connection.close()
              last_sent = datetime.now()
              elapsed_seconds = (last_sent - started_sending_at).seconds
              time.sleep(
                  max(
                      (min(nyt_settings.INTERVALS)[0] - elapsed_seconds) * 60,
                      sleep_time,
                      0
                  )
              )
  
      def _send_batch(self, context, connection, setting):
          """
          Loops through emails in a list of notifications and tries to send
          to each recepient
  
          """
          # STMP connection send loop
          notifications = context['notifications']
  
          if len(context['notifications']) == 0:
              return
  
          while True:
              try:
                  self._send_user_notifications(context, connection)
                  for n in notifications:
                      n.is_emailed = True
                      n.save()
                  break
              except smtplib.SMTPSenderRefused:
                  self.logger.error(
                      (
                          "E-mail refused by SMTP server ({}), "
                          "skipping!"
                      ).format(setting.user.email))
                  continue
              except smtplib.SMTPException as e:
                  self.logger.error(
                      (
                          "You have an error with your SMTP server "
                          "connection, error is: {}"
                      ).format(e))
                  self.logger.error("Sleeping for 30s then retrying...")
                  time.sleep(30)
              except Exception as e:
                  self.logger.error(
                      (
                          "Unhandled exception while sending, giving "
                          "up: {}"
                      ).format(e))
                  raise
  
      def send_mails(self, connection, last_sent=None, user_settings=None):
          """
          Does the lookups and sends out email digests to anyone who has them
          due.
          """
  
          self.logger.debug("Entering send_mails()")
  
          connection.open()
  
          if not user_settings:
              user_settings = models.Settings.objects.all().order_by('user')
  
          context = {'user': None,
                     'username': None,
                     'notifications': None,
                     'digest': None,
                     'site': Site.objects.get_current()}
  
          for setting in user_settings:
              context['user'] = setting.user
              context['username'] = getattr(
                  setting.user, setting.user.USERNAME_FIELD)
              # Which notifications are remaining for the user's settings
              context['notifications'] = []
              # get the index of the tuple corresponding to the interval and
              # get the string name
              idx = [y[0] for y in nyt_settings.INTERVALS].index(
                  setting.interval)
              context['digest'] = nyt_settings.INTERVALS[idx][1]
              for subscription in setting.subscription_set.filter(
                  send_emails=True,
                  latest__is_emailed=False
              ):
                  context['notifications'].append(subscription.latest)
  
              self._send_batch(context, connection, setting)
  