Example #1
0
    def _filter_by_product(self, all_watchers):
        products = self.revision.document.get_products()
        product_hashes = [hash_to_unsigned(s.slug) for s in products]

        watchers_and_watches = []

        # Weed out the users that have a product filter that isn't one of the
        # document's products.
        for user, watches in all_watchers:
            for watch in watches:
                # Get the product filters for the watch, if any.
                prods = watch.filters.filter(name="product").values_list(
                    "value", flat=True
                )

                # If there are no product filters, they are watching them all.
                if len(prods) == 0:
                    watchers_and_watches.append((user, watches))
                    break

                # Otherwise, check if they are watching any of the document's
                # products.
                for prod in prods:
                    if prod in product_hashes:
                        watchers_and_watches.append((user, watches))
                        break

        return watchers_and_watches
Example #2
0
    def _filter_by_product(self, all_watchers):
        products = self.revision.document.get_products()
        product_hashes = [hash_to_unsigned(s.slug) for s in products]

        watchers_and_watches = []

        # Weed out the users that have a product filter that isn't one of the
        # document's products.
        for user, watches in all_watchers:
            for watch in watches:
                # Get the product filters for the watch, if any.
                prods = watch.filters.filter(
                    name='product').values_list('value', flat=True)

                # If there are no product filters, they are watching them all.
                if len(prods) == 0:
                    watchers_and_watches.append((user, watches))
                    break

                # Otherwise, check if they are watching any of the document's
                # products.
                for prod in prods:
                    if prod in product_hashes:
                        watchers_and_watches.append((user, watches))
                        break

        return watchers_and_watches
Example #3
0
    def _watches_belonging_to_user(cls, user_or_email, object_id=None,
                                   **filters):
        """Return a QuerySet of watches having the given user or email, having
        (only) the given filters, and having the event_type and content_type
        attrs of the class.

        Matched Watches may be either confirmed and unconfirmed. They may
        include duplicates if the get-then-create race condition in
        :meth:`notify()` allowed them to be created.

        If you pass an email, it will be matched against only the email
        addresses of anonymous watches. At the moment, the only integration
        point planned between anonymous and registered watches is the claiming
        of anonymous watches of the same email address on user registration
        confirmation.

        If you pass the AnonymousUser, this will return an empty QuerySet.

        """
        # If we have trouble distinguishing subsets and such, we could store a
        # number_of_filters on the Watch.
        cls._validate_filters(filters)

        if isinstance(user_or_email, basestring):
            user_condition = Q(email=user_or_email)
        elif not user_or_email.is_anonymous():
            user_condition = Q(user=user_or_email)
        else:
            return Watch.objects.none()

        # Filter by stuff in the Watch row:
        watches = getattr(Watch, 'uncached', Watch.objects).filter(
            user_condition,
            Q(content_type=ContentType.objects.get_for_model(cls.content_type))
                if cls.content_type
                else Q(),
            Q(object_id=object_id)
                if object_id
                else Q(),
            event_type=cls.event_type).extra(
                where=['(SELECT count(*) FROM tidings_watchfilter WHERE '
                       'tidings_watchfilter.watch_id='
                       'tidings_watch.id)=%s'],
                params=[len(filters)])
        # Optimization: If the subselect ends up being slow, store the number
        # of filters in each Watch row or try a GROUP BY.

        # Apply 1-to-many filters:
        for k, v in filters.iteritems():
            watches = watches.filter(filters__name=k,
                                     filters__value=hash_to_unsigned(v))

        return watches
Example #4
0
 def filter_conditions():
     """Return joins, WHERE conditions, and params to bind to them in
     order to check a notification against all the given filters."""
     # Not a one-liner. You're welcome. :-)
     self._validate_filters(filters)
     joins, wheres, join_params, where_params = [], [], [], []
     for n, (k, v) in enumerate(filters.iteritems()):
         joins.append(
             'LEFT JOIN tidings_watchfilter f{n} '
             'ON f{n}.watch_id=w.id '
                 'AND f{n}.name=%s'.format(n=n))
         join_params.append(k)
         wheres.append('(f{n}.value=%s '
                       'OR f{n}.value IS NULL)'.format(n=n))
         where_params.append(hash_to_unsigned(v))
     return joins, wheres, join_params + where_params
Example #5
0
    def notify(cls, user_or_email_, object_id=None, **filters):
        """Start notifying the given user or email address when this event
        occurs and meets the criteria given in ``filters``.

        Return the created (or the existing matching) Watch so you can call
        :meth:`~tidings.models.Watch.activate()` on it if you're so inclined.

        Implementations in subclasses may take different arguments; see the
        docstring of :meth:`is_notifying()`.

        Send an activation email if an anonymous watch is created and
        :data:`~django.conf.settings.TIDINGS_CONFIRM_ANONYMOUS_WATCHES` is
        ``True``. If the activation request fails, raise a
        ActivationRequestFailed exception.

        Calling :meth:`notify()` twice for an anonymous user will send the
        email each time.

        """
        # A test-for-existence-then-create race condition exists here, but it
        # doesn't matter: de-duplication on fire() and deletion of all matches
        # on stop_notifying() nullify its effects.
        try:
            # Pick 1 if >1 are returned:
            watch = cls._watches_belonging_to_user(
                user_or_email_,
                object_id=object_id,
                **filters)[0:1].get()
        except Watch.DoesNotExist:
            create_kwargs = {}
            if cls.content_type:
                create_kwargs['content_type'] = \
                    ContentType.objects.get_for_model(cls.content_type)
            create_kwargs['email' if isinstance(user_or_email_, basestring)
                          else 'user'] = user_or_email_
            # Letters that can't be mistaken for other letters or numbers in
            # most fonts, in case people try to type these:
            distinguishable_letters = \
                'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXYZ'
            secret = ''.join(random.choice(distinguishable_letters)
                             for x in xrange(10))
            # Registered users don't need to confirm, but anonymous users do.
            is_active = ('user' in create_kwargs or
                          not settings.TIDINGS_CONFIRM_ANONYMOUS_WATCHES)
            if object_id:
                create_kwargs['object_id'] = object_id
            watch = Watch.objects.create(
                secret=secret,
                is_active=is_active,
                event_type=cls.event_type,
                **create_kwargs)
            for k, v in filters.iteritems():
                WatchFilter.objects.create(watch=watch, name=k,
                                           value=hash_to_unsigned(v))
        # Send email for inactive watches.
        if not watch.is_active:
            email = watch.user.email if watch.user else watch.email
            message = cls._activation_email(watch, email)
            try:
                message.send()
            except SMTPException, e:
                watch.delete()
                raise ActivationRequestFailed(e.recipients)