コード例 #1
0
 class Drinker(Document):
     drink = GenericReferenceField()
コード例 #2
0
ファイル: subscription.py プロジェクト: dolanor-galaxy/1flow
class Subscription(Document, DocumentHelperMixin):

    # BIG DB migration 20141028
    bigmig_migrated = BooleanField(default=False)
    bigmig_reassigned = BooleanField(default=False)
    # END BIG DB migration

    feed = ReferenceField('Feed', reverse_delete_rule=CASCADE)
    user = ReferenceField('User', unique_with='feed',
                          reverse_delete_rule=CASCADE)

    # allow the user to rename the field in its own subscription
    name = StringField(verbose_name=_(u'Name'))

    # TODO: convert to UserTag to use ReferenceField and reverse_delete_rule.
    tags = ListField(GenericReferenceField(),
                     default=list, verbose_name=_(u'Tags'),
                     help_text=_(u'Tags that will be applied to new reads in '
                                 u'this subscription.'))

    folders = ListField(ReferenceField(Folder, reverse_delete_rule=PULL),
                        verbose_name=_(u'Folders'), default=list,
                        help_text=_(u'Folder(s) in which this subscription '
                                    u'appears.'))

    all_articles_count = IntRedisDescriptor(
        attr_name='s.aa_c', default=subscription_all_articles_count_default,
        set_default=True, min_value=0)

    unread_articles_count = IntRedisDescriptor(
        attr_name='s.ua_c', default=subscription_unread_articles_count_default,
        set_default=True, min_value=0)

    starred_articles_count = IntRedisDescriptor(
        attr_name='s.sa_c', default=subscription_starred_articles_count_default,
        set_default=True, min_value=0)

    archived_articles_count = IntRedisDescriptor(
        attr_name='s.ra_c',
        default=subscription_archived_articles_count_default,
        set_default=True, min_value=0)

    bookmarked_articles_count = IntRedisDescriptor(
        attr_name='s.ba_c',
        default=subscription_bookmarked_articles_count_default,
        set_default=True, min_value=0)

    meta = {
        'indexes': [
            'user',
            'feed',
            'folders',
        ]
    }

    def __unicode__(self):
        return _(u'{0}+{1} (#{2})').format(
            self.user.username, self.feed.name, self.id)

    @classmethod
    def signal_post_save_handler(cls, sender, document,
                                 created=False, **kwargs):

        subscription = document

        if created:
            if subscription._db_name != settings.MONGODB_NAME_ARCHIVE:
                # HEADS UP: this task name will be registered later
                # by the register_task_method() call.
                subscription_post_create_task.delay(subscription.id)  # NOQA

        # else:
        #     if subscription.feed.is_mailfeed:
        #         mailfeed = MailFeed.get_from_stream_url(subscription.feed.url)
        #
        #         if subscription.user.django == mailfeed.user:
        #             # HEADS UP: we use save() to forward the
        #             # name change to the Feed instance without
        #             # duplicating the code here.
        #             mailfeed.name = subscription.name
        #             mailfeed.save()

    def post_create_task(self):
        """ Method meant to be run from a celery task. """

        # The content of this method is done in subscribe_user_to_feed()
        # to avoid more-than-needed write operations on the database.
        pass

    @classmethod
    def signal_post_delete_handler(cls, sender, document, **kwargs):

        subscription = document

        if subscription._db_name != settings.MONGODB_NAME_ARCHIVE:

            # HEADS UP: we don't pass an ID, else the .get() fails
            # in the task for a good reason: the subscription doesn't
            # exist anymore.
            subscription_post_delete_task.delay(subscription)

    @classmethod
    def subscribe_user_to_feed(cls, user, feed, name=None,
                               force=False, background=False):

        try:
            subscription = cls(user=user, feed=feed).save()

        except (NotUniqueError, DuplicateKeyError):
            if not force:
                LOGGER.info(u'User %s already subscribed to feed %s.',
                            user, feed)
                return cls.objects.get(user=user, feed=feed)

        else:
            subscription.name = name or feed.name
            subscription.tags = feed.tags[:]
            subscription.save()

        if background:
            # HEADS UP: this task name will be registered later
            # by the register_task_method() call.
            # 'True' is for the 'force' argument.
            subscription_check_reads_task.delay(subscription.id, True)

        else:
            subscription.check_reads(force=True)

        LOGGER.info(u'Subscribed %s to %s via %s.', user, feed, subscription)

        return subscription

    @property
    def has_unread(self):

        # We need a boolean value for accurate template caching.
        return self.unread_articles_count != 0

    @property
    def is_closed(self):

        return self.feed.closed

    def mark_all_read(self, latest_displayed_read=None):

        if self.unread_articles_count == 0:
            return

        # count = self.unread_articles_count

        # self.unread_articles_count = 0

        # for folder in self.folders:
        #     folder.unread_articles_count -= count

        # self.user.unread_articles_count -= count

        # Marking all read is not a database-friendly operation,
        # thus it's run via a task to be able to return now immediately,
        # with cache numbers updated.
        #
        # HEADS UP: this task name will be registered later
        # by the register_task_method() call.
        subscription_mark_all_read_in_database_task.delay(
            self.id, now() if latest_displayed_read is None
            #
            # TRICK: we use self.user.reads for 2 reasons:
            #       - avoid importing `Read`, which would create a loop.
            #       - in case of a folder/global initiated mark_all_read(),
            #         the ID can be one of a read in another subscription
            #         and in this case, self.reads.get() will fail.
            #
            else latest_displayed_read.date_created)

    def mark_all_read_in_database(self, prior_datetime):
        """ To avoid marking read the reads that could have been created
            between the task call and the moment it is effectively run,
            we define what to exactly mark as read with the datetime when
            the operation was done by the user.

            Also available as a task for background execution.

            .. note:: the archived reads stay archived, whatever their
                read status is. No need to test this attribute.
        """

        # We touch only unread. This avoid altering the auto_read attribute
        # on reads that have been manually marked read by the user.
        params = {'is_read__ne': True, 'date_created__lte': prior_datetime}

        if self.user.preferences.read.bookmarked_marks_unread:
            # Let bookmarked reads stay unread.
            params['is_bookmarked__ne'] = True

        impacted_unread = self.reads.filter(**params)
        impacted_count  = impacted_unread.count()

        impacted_unread.update(set__is_read=True,
                               set__is_auto_read=True,
                               set__date_read=prior_datetime,
                               set__date_auto_read=prior_datetime)

        # If our caches are correctly computed, doing
        # one more full query just for this is too much.
        #
        # self.compute_cached_descriptors(unread=True)

        self.unread_articles_count -= impacted_count

        for folder in self.folders:
            folder.unread_articles_count -= impacted_count

        self.user.unread_articles_count -= impacted_count

    def check_reads(self, articles=None, force=False, extended_check=False):
        """ Also available as a task for background execution. """

        if not force:
            LOGGER.info(u'Subscription.check_reads() is very costy and should '
                        u'not be needed in normal conditions. Call it with '
                        u'`force=True` if you are sure you want to run it.')
            return

        yesterday = combine(today() - timedelta(days=1), time(0, 0, 0))
        is_older  = False
        my_now    = now()
        reads     = 0
        unreads   = 0
        failed    = 0
        missing   = 0
        rechecked = 0

        # See generic_check_subscriptions_method() for comment about this.
        if articles is None:
            articles = self.feed.good_articles.order_by('-id')

        for article in articles:
            #
            # NOTE: Checking `article.is_good()` is done at a lower
            #       level in the individual `self.create_read()`.
            #       It has nothing to do with the dates-only checks
            #       that we do here.
            #

            params = {}

            if is_older or article.date_published is None:
                params = {
                    'is_read':        True,
                    'is_auto_read':   True,
                    'date_read':      my_now,
                    'date_auto_read': my_now,
                }

            else:
                # As they are ordered by date, switching is_older to True will
                # avoid more date comparisons. MongoDB already did the job.
                if article.date_published < yesterday:

                    is_older = True

                    params = {
                        'is_read':        True,
                        'is_auto_read':   True,
                        'date_read':      my_now,
                        'date_auto_read': my_now,
                    }

                # implicit: else: pass
                # No params == all by default == is_read is False

            # The `create_read()` methods is defined
            # in `nonrel/read.py` to avoid an import loop.
            _, created = self.create_read(article, False, **params)

            if created:
                missing += 1

                if params.get('is_read', False):
                    reads += 1

                else:
                    unreads += 1

            elif created is False:
                rechecked += 1

                if extended_check:
                    try:
                        article.activate_reads()

                    except:
                        LOGGER.exception(u'Problem while activating reads '
                                         u'of Article #%s in Subscription '
                                         u'#%s.check_reads(), continuing '
                                         u'check.', article.id, self.id)

            else:
                failed += 1

        if missing or rechecked:
            #
            # TODO: don't recompute everything, just
            #    add or subscribe the changed counts.
            #
            self.compute_cached_descriptors(all=True, unread=True)

            for folder in self.folders:
                folder.compute_cached_descriptors(all=True, unread=True)

        LOGGER.info(u'Checked subscription #%s. '
                    u'%s/%s non-existing/re-checked, '
                    u'%s/%s read/unread and %s not created.',
                    self.id, missing, rechecked,
                    reads, unreads, failed)

        return missing, rechecked, reads, unreads, failed
コード例 #3
0
ファイル: tag.py プロジェクト: dolanor-galaxy/1flow
class Tag(Document, DocumentHelperMixin):

    # BIG DB migration 20141028
    bigmig_migrated = BooleanField(default=False)
    # END BIG DB migration

    name = StringField(verbose_name=_(u'name'), unique=True)
    slug = StringField(verbose_name=_(u'slug'))
    language = StringField(verbose_name=_(u'language'), default='')
    parents = ListField(ReferenceField('self', reverse_delete_rule=PULL),
                        default=list)
    children = ListField(ReferenceField('self', reverse_delete_rule=PULL),
                         default=list)
    # reverse_delete_rule=NULLIFY,
    origin = GenericReferenceField(verbose_name=_(u'Origin'),
                                   help_text=_(u'Initial origin from where '
                                               u'the tag was created from, '
                                               u'to eventually help '
                                               u'defining other attributes.'))
    duplicate_of = ReferenceField('Tag',
                                  reverse_delete_rule=NULLIFY,
                                  verbose_name=_(u'Duplicate of'),
                                  help_text=_(u'Put a "master" tag here to '
                                              u'help avoiding too much '
                                              u'different tags (eg. singular '
                                              u'and plurals) with the same '
                                              u'meaning and loss of '
                                              u'information.'))

    meta = {
        'indexes': [
            'name',
        ]
    }

    # See the `WordRelation` class before working on this.
    #
    # antonyms = ListField(ReferenceField('self'), verbose_name=_(u'Antonyms'),
    #                      help_text=_(u'Define an antonym tag to '
    #                      u'help search connectable but.'))

    def __unicode__(self):
        return _(u'{0} {1}⚐ (#{2})').format(self.name, self.language, self.id)

    def replace_duplicate_everywhere(self, duplicate_id, *args, **kwargs):

        duplicate = self.__class__.objects.get(id=duplicate_id)

        # This method is defined in nonrel.article to avoid an import loop.
        self.replace_duplicate_in_articles(duplicate, *args, **kwargs)
        #
        # TODO: do the same for feeds, reads (, subscriptions?) …
        #
        pass

    @classmethod
    def signal_post_save_handler(cls,
                                 sender,
                                 document,
                                 created=False,
                                 **kwargs):

        tag = document

        if created:
            if tag._db_name != settings.MONGODB_NAME_ARCHIVE:

                # HEADS UP: this task is declared by
                # the register_task_method call below.
                tag_post_create_task.delay(tag.id)  # NOQA

    def post_create_task(self):
        """ Method meant to be run from a celery task. """

        if not self.slug:
            self.slug = slugify(self.name)
            self.save()

            statsd.gauge('mongo.tags.counts.total', 1, delta=True)

    @classmethod
    def get_tags_set(cls, tags_names, origin=None):

        tags = set()

        for tag_name in tags_names:
            tag_name = tag_name.lower()

            try:
                tag = cls.objects.get(name=tag_name)

            except cls.DoesNotExist:
                try:
                    tag = cls(name=tag_name, origin=origin).save()

                except (NotUniqueError, DuplicateKeyError):
                    tag = cls.objects.get(name=tag_name)

            tags.add(tag.duplicate_of or tag)

        return tags

    def save(self, *args, **kwargs):
        """ This method will simply add the missing children/parents reverse
            links of the current Tag. This is needed when modifying tags from
            the Django admin, which doesn't know about the reverse-link
            existence.

            .. note:: sadly, we have no fast way to do the same for links
                removal.
        """

        super(Tag, self).save(*args, **kwargs)

        for parent in self.parents:
            if self in parent.children:
                continue

            try:
                parent.add_child(self,
                                 update_reverse_link=False,
                                 full_reload=False)
            except:
                LOGGER.exception(
                    u'Exception while reverse-adding '
                    u'child %s to parent %s', self, parent)

        for child in self.children:
            if self in child.parents:
                continue

            try:
                child.add_parent(self,
                                 update_reverse_link=False,
                                 full_reload=False)
            except:
                LOGGER.exception(
                    u'Exception while reverse-adding '
                    u'parent %s to child %s', self, child)

        return self

    def add_parent(self, parent, update_reverse_link=True, full_reload=True):

        self.update(add_to_set__parents=parent)

        if full_reload:
            self.safe_reload()

        if update_reverse_link:
            parent.add_child(self, update_reverse_link=False)

    def remove_parent(self,
                      parent,
                      update_reverse_link=True,
                      full_reload=True):

        if update_reverse_link:
            parent.remove_child(self, update_reverse_link=False)

        self.update(pull__parents=parent)

        if full_reload:
            self.safe_reload()

    def add_child(self, child, update_reverse_link=True, full_reload=True):
        self.update(add_to_set__children=child)

        if full_reload:
            self.safe_reload()

        if update_reverse_link:
            child.add_parent(self, update_reverse_link=False)

    def remove_child(self, child, update_reverse_link=True, full_reload=True):

        if update_reverse_link:
            child.remove_parent(self, update_reverse_link=False)

        self.update(pull__children=child)

        if full_reload:
            self.safe_reload()
コード例 #4
0
class ContactRecord(db.Document):
    user = GenericReferenceField()
    label = StringField(required=True, max_length=15)

    meta = {'allow_inheritance': True}