Beispiel #1
0
    def __init__(self, flags, default=None, *args, **kwargs):
        if isinstance(flags, dict):
            # Get only integer keys in correct range
            valid_keys = (k for k in flags.keys()
                          if isinstance(k, int) and (0 <= k < MAX_FLAG_COUNT))
            if not valid_keys:
                raise ValueError('Wrong keys or empty dictionary')
            # Fill list with values from dict or with empty values
            flags = [flags.get(i, '') for i in range(max(valid_keys) + 1)]

        if len(flags) > MAX_FLAG_COUNT:
            raise ValueError('Too many flags')

        self._arg_flags = flags
        flags = list(flags)
        labels = []
        for num, flag in enumerate(flags):
            if isinstance(flag, (tuple, list)):
                flags[num] = flag[0]
                labels.append(flag[1])
            else:
                labels.append(flag)

        if isinstance(default, (list, tuple, set, frozenset)):
            new_value = 0
            for flag in default:
                new_value |= Bit(flags.index(flag))
            default = new_value

        BigIntegerField.__init__(self, default=default, *args, **kwargs)
        self.flags = flags
        self.labels = labels
 def __getattr__(self, key):
     if key == '_flags':
         # Since __getattr__ is for fallback, reaching here from Python
         # means that there's no '_flags' attribute in this object,
         # which may be caused by intermediate state while copying etc.
         raise AttributeError("'%s' object has no attribute '%s'" %
                              (self.__class__.__name__, key))
     try:
         flags = self._flags
     except AttributeError:
         raise AttributeError("'%s' object has no attribute '%s'" %
                              (self.__class__.__name__, key))
     try:
         flag = flags.index(key)
     except ValueError:
         raise AttributeError("flag {} is not registered".format(key))
     return Bit(flag)
Beispiel #3
0
 def itervalues(self):
     for flag in self._flags:
         yield Bit(self._flags.index(flag))
Beispiel #4
0
 def iteritems(self):
     for flag in self._flags:
         yield flag, Bit(self._flags.index(flag))
Beispiel #5
0
 def __getattr__(self, key):
     if key not in self._flags:
         raise AttributeError("flag {} is not registered".format(key))
     return Bit(self._flags.index(key))
Beispiel #6
0
 def __getattr__(self, key):
     if key not in self._flags:
         raise AttributeError
     return Bit(self._flags.index(key))
Beispiel #7
0
class Notification(models.Model):
    """
    ENG: A task executed on receiving a signal.
    RUS: Диспетчер сигналов. Задача выполняется при получении сигнала.
    """
    MODES = {
        0: ('email', _('By Email')),
        1: ('push', _('By Push Notification')),
        2: ('telegram', _('By Telegram'))
    }

    RECIPIENTS_EMPTY_CHOICES_VALUE = ('0', _("Nobody"))

    """
        "1":  "owner",
        "2": "moderate_person",
        "3": "responsible_person",
        "4": "private_person",
        "5": "auditory_persons",
        "6": "regional_persons"
    """
    RECIPIENTS_ROLES_CHOICES = (
        RECIPIENTS_EMPTY_CHOICES_VALUE,

    )


    SPLIT_CHARSET = ','

    name = models.CharField(max_length=255, verbose_name=_("Name"))
    transition = MultiSelectField(verbose_name=_('Transition'), max_length=400,
                                       dinamic_choices_model_attr='get_transition_choices', blank=True)

    notify_to_roles = MultiSelectField(verbose_name=_('Notify to roles'), max_length=255, default='0',
                                       dinamic_choices_model_attr='get_notification_recipients_roles_choices')

    copy_to = models.ManyToManyField('CustomerProxy', blank=True, limit_choices_to={'is_staff':  True})
    template = models.ForeignKey(EmailTemplate, verbose_name=_("Template"),
                                 limit_choices_to=Q(language__isnull=True) | Q(language=''))

    mode = BitField(flags=MODES, verbose_name=_('Mode'), default=Bit(0).mask)
    active = models.BooleanField(verbose_name=_("Active"), default=True)

    class Meta:
        app_label = APP_LABEL
        verbose_name = _("Notification")
        verbose_name_plural = _("Notifications")
        ordering = ('transition',)

    def __str__(self):
        return self.name

    @staticmethod
    def get_senders_objects():
        """
        RUS: Получение списка моделей для которых можно отправлять уведомления
        :return: [ список моделей, ... ]
        """

        return NotificationMixin._notification_classes.values()

    @staticmethod
    def get_transition_name(object_model, source, target):
        """
        RUS: Получение форматированного наименования состояния

        :param object_model: модель объекта
        :param source: наименование начального состояния
        :param target: наименование конечного состояния
        :return: форматированную строку модель:начальное состояние: конечное состояние
        """

        return '{}:{}:{}'.format(object_model.__name__.lower(), source, target)

    @classmethod
    def get_notification_recipients_roles_choices(cls):
        """
        RUS: Получение типов получателей доступных для уведомления для всех моделей
        :return: ((id:  _("title")), ...)
        """
        choices = list(cls.RECIPIENTS_ROLES_CHOICES)
        senders_objects = cls.get_senders_objects()

        for sender in senders_objects:
            for obj in sender.get_notification_recipients_roles_choices():
                if obj not in choices:
                    choices.append(obj)

        return choices

    @classmethod
    def get_transition_choices(cls):
        """
        RUS: Получение доступных вариантов переходов состояний для всех моделей
        :return:
            [(ransition_choice_name, transition_choice_title),...]
        """
        choices = {}
        for clazz in cls.get_senders_objects():
            for transition in clazz.get_notification_transitions():
                transition_choice_name = cls.get_transition_name(clazz, transition.source, transition.target)
                transition_choice_title = "{}: {} - {} ({} - {})".format(
                    clazz._meta.verbose_name,
                    clazz.get_transition_name(transition.source),
                    clazz.get_transition_name(transition.target),
                    transition.source,
                    transition.target
                )
                choices[transition_choice_name] = transition_choice_title

        return sorted(choices.items(), key=lambda item: item[1])

    @classmethod
    def get_avalible_recipients_roles_for_notifications(cls):
        """
        RUS: Получение всех доступных ролей для состояниий
        :return: словарь {'модель:начальное состояние:конечное состояние': [ ид роли, ...]}
        """
        roles_dict = {}
        senders_objects = cls.get_senders_objects()
        empty_value = cls.RECIPIENTS_EMPTY_CHOICES_VALUE[0]
        for sender in senders_objects:
            sender_name = sender.__name__.lower()
            for key, value in sender.get_avalible_recipients_roles_for_notifications().items():
                if not empty_value in value:
                    value.append(empty_value)
                roles_dict["%s:%s" % (sender_name, key)] = value

        return roles_dict

    @classmethod
    def send_notification(cls, object, source, target, **kwargs):
        """
        RUS: Главная функция для подписки на сигналы
        Отправка уведомлений по подписанным событиям
        :param object: объект уведомления
        :param source: начальное состояние
        :param target: конечное состояние
        :param kwargs:

        """

        transition_name = cls.get_transition_name(type(object), source, target)

        for n in Notification.objects.filter(transition__contains=transition_name, active=True):
            if n.mode.email:
                n.notify_by_email(object, source, target)
            if n.mode.push:
                n.notify_by_push(object, source, target)
            if n.mode.telegram:
                n.notify_by_telegram(object, source, target)

    def get_notify_recipients_roles(self):
        """
        RUS: Получение списка ролей персон для уведомления из модели
        :return: [Идентивикатор роли,...]
        """

        return [recipient_id for recipient_id in self.notify_to_roles if recipient_id!=self.RECIPIENTS_ROLES_CHOICES[0][0]]

    def notify_by_email(self, object, source, target, **kwargs):
        """
        RUS: Отправка уведомлениий по email
        :param object: Объект уведомления Entity
        :param source - имя начального состояния
        :param target - имя конечного состояния

        recipients - список сосотящий из (email, пользователя, класс сериализации пользователя)
        example: [([email protected], customer_object, CustomerSerializer), ...]
        """

        recipients = [] #  - list of tuples (recipient_email, recipient_object, recipient_serialaizer_cls)
        if self.copy_to:
            recipients.extend(object.get_email_notification_recipients(self.copy_to.all()))

        recipients_roles = self.get_notify_recipients_roles()
        if recipients_roles:
            recipients.extend(object.get_email_notification_recipients_by_roles(recipients_roles))

        if recipients:
            self.notify(recipients, object, source, target, 'email')

    def notify_by_push(self, object, source, target, **kwargs):
        """
        RUS: Отправка push уведомлениий
        :param object: Объект уведомления Entity
        :param source - имя начального состояния
        :param target - имя конечного состояния

        recipients - список сосотящий из (id пользователя, пользователя, класс сериализации пользователя)
        example: [(key, customer_object, CustomerSerializer), ...]
        """

        if push is not None:
            recipients = []  # - list of tuples (recipient_key, recipient_object, recipient_serialaizer_cls)
            if self.copy_to:
                recipients.extend(object.get_push_notification_recipients(self.copy_to.all()))

            recipients_roles = self.get_notify_recipients_roles()
            if recipients_roles:
                recipients.extend(object.get_push_notification_recipients_by_roles(recipients_roles))

            if recipients:
                self.notify(recipients, object, source, target, 'push')

    def notify(self, recipients, object, source, target, mode='email', **kwargs):
        """
        RUS: Подготовка и отправка сообщений по списку получателей

        :param recipients: список сосотящий из [(email или push_id, пользователь, класс сериализации пользователя),...]
        :param object: объект уведомления
        :param source - имя начального состояния
        :param target - имя конечного состояния
        :param mode: 'email' или 'push'

        """

        # подготовка общего контекста
        stored_request = object.stored_request[0] if isinstance(
            object.stored_request, (tuple, list)) else object.stored_request

        emulated_request = EmulateHttpRequest(object.customer, stored_request)
        authenticators = [auth() for auth in api_settings.DEFAULT_AUTHENTICATION_CLASSES]

        serialaizer_cls = object.get_serialaizer_class()
        entity_serializer = serialaizer_cls(
            object,
            context={'request': Request(emulated_request, authenticators=authenticators)}
        )

        language = stored_request.get('language')
        translation.activate(language)

        try:
            template = self.template.translated_templates.get(language=language)
        except EmailTemplate.DoesNotExist:
            template = self.template
        attachments = {}
        for notiatt in self.notificationattachment_set.all():
            attachments[notiatt.attachment.original_filename] = notiatt.attachment.file.file

        context = {
            'data': entity_serializer.data,
            'ABSOLUTE_BASE_URI': emulated_request.build_absolute_uri().rstrip('/'),
            'render_language': language,
            'transition': {
                'source': {
                    'name': source,
                    'title': object.get_transition_name(source)
                },
                'target': {
                    'name': target,
                    'title': object.get_transition_name(target)
                },
            }
        }

        # отправка уведомления пользователям
        if mode == 'email':
            sender = get_from_address()
            for recipient in recipients:
                try:
                    email_validator(recipient[0])
                except ValidationError:
                    pass
                else:
                    # подготовка контекста получателя
                    recipient_serialaizer_cls = recipient[2]
                    context['recipient'] = recipient_serialaizer_cls(recipient[1]).data

                    mail.send(recipient[0], sender=sender, template=template, context=context,
                              attachments=attachments, render_on_delivery=True)

        elif mode == 'push' and push is not None:

            for recipient in recipients:
                # подготовка контекста получателя
                recipient_serialaizer_cls = recipient[2]
                context['recipient'] = recipient_serialaizer_cls(recipient[1]).data

                push.send(recipient[0], template=template, context=context, render_on_delivery=True)

        elif mode == 'telegram' and tg is not None:
            for recipient in recipients:
                tg.send(recipient[0], template=template, context=context, render_on_delivery=True)

    def notify_by_telegram(self, object, source, target):
        if tg is not None:
            recipients = []
            recipients_roles = self.get_notify_recipients_roles()
            if recipients_roles:
                recipients.extend(object.get_telegram_notification_recipients_by_roles(recipients_roles))

            self.notify(recipients, object, source, target, 'telegram')