new_contents: from django.utils.translation import ugettext_lazy as _
  from django.template.loader import render_to_string
  
  from allauth.account.models import EmailAddress
  
  from InvenTree.helpers import inheritors
  import InvenTree.tasks
  
  
  # region notification classes
  # region base classes
  class NotificationMethod:
      method_name = ''
  
      def __init__(self, obj, entry_name, receivers) -> None:
          # check if a sending fnc is defined
          if (not 'send' in self) and (not 'send_bulk' in self):
              raise NotImplementedError('A NotificationMethod must either define a `send` or a `send_bulk` method')
  
          # define arguments
          self.obj = obj
          self.entry_name = entry_name
          self.receiers = receivers
  
          # gather recipiends
          self.recipiends = self.get_recipiends()
  
      def get_recipiends(self):
          return False
  
      def cleanup(self):
          return True
  
  
  class SingleNotificationMethod(NotificationMethod):
      def send(self, context):
          raise NotImplementedError('The `send` method must be overriden!')
  
  
  class BulkNotificationMethod(NotificationMethod):
      def send_bulk(self, context):
          raise NotImplementedError('The `send` method must be overriden!')
  # endregion
  
  # region implementations
  class EmailNotification(BulkNotificationMethod):
      method_name = 'mail'
  
      def get_recipiends(self):
          return EmailAddress.objects.filter(
              user__in=self.receiers,
          )
  
      def send_bulk(self, context):
          # TODO: In the future, include the part image in the email template
  
          if not 'template' in context:
              raise NotImplementedError('Templates must be provided in the `context`')
          if not 'html' in context['template']:
              raise NotImplementedError("template['html'] must be provided in the `context`")
          if not 'subject' in context['template']:
              raise NotImplementedError("template['subject'] must be provided in the `context`")
  
          html_message = render_to_string(context['template']['html'], context)
          recipients = self.recipiends.values_list('email', flat=True)
  
          InvenTree.tasks.send_email(context['template']['subject'], '', recipients, html_message=html_message)
  
          return True
  # endregion
  # endregion
  
  
  def trigger_notifaction(
      obj, entry_name=None, obj_ref='pk',
      receivers=None, receiver_fnc=None, receiver_args=[], receiver_kwargs={},
      notification_context={}
      ):
      """
      Send out an notification
      """
  
      # set defaults
      if not entry_name:
          entry_name = obj._meta.modelname
  
      # resolve objekt reference
      obj_ref_value = getattr(obj, obj_ref)
      # lets try with some dafaults
      if not obj_ref_value:
          obj_ref_value = getattr(obj, 'pk')
      if not obj_ref_value:
          obj_ref_value = getattr(obj, 'id')
      if not obj_ref_value:
          raise KeyError(f"Could not resolve an object reference for '{str(obj)}' with {obj_ref}, pk, id")
  
  
      # Check if we have notified recently...
      delta = timedelta(days=1)
  
      if NotificationEntry.check_recent(entry_name, obj_ref_value, delta):
          logger.info(f"Notification '{entry_name}' has recently been sent for '{str(obj)}' - SKIPPING")
          return
  
      logger.info(f"Gathering users for notification '{entry_name}'")
      # Collect possible receivers
      if not receivers:
          receivers = receiver_fnc(*receiver_args, **receiver_kwargs)
  
      if receivers:
          logger.info(f"Sending notification '{entry_name}' for '{str(obj)}'")
  
          # collect possible methods
          delivery_methods = inheritors(NotificationMethod)
  
          for method in delivery_methods:
              logger.info(f"Triggering method '{method.method_name}'")
              try:
                  deliver_notification(method, obj, entry_name, receivers, notification_context)
              except Exception as error:
                  logger.error(error)
  
          # save delivery flag
          NotificationEntry.notify(entry_name, obj_ref_value)
      else:
          logger.info(f"No possible users for notification '{entry_name}'")
  
  
  def deliver_notification(cls: NotificationMethod, obj, entry_name: str, receivers, notification_context: dict):
      method = cls(obj, entry_name, receivers)
  
      if method.recipiends and method.recipiends > 0:
          logger.info(f"Notify users via '{method.method_name}' for notification '{entry_name}' for '{str(obj)}'")
  
          if 'send_bulk' in method:
              method.send_bulk(notification_context)
  
          elif 'send' in method:
              for rec in method.recipiends:
                  method.send(notification_context)
  
          else:
              raise NotImplementedError('No delivery method found')
  
subject: add new more generalized notification method