<commit_before># -*- coding: utf-8 -*-
"""
Copyright 2013-2014 <NAME> <<EMAIL>>.

This file is part of the 1flow project.

1flow is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.

1flow is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public
License along with 1flow.  If not, see http://www.gnu.org/licenses/

"""

import uuid
import logging

from django.db import models
from django.db.models.signals import pre_save
from django.utils.translation import ugettext_lazy as _
from django.utils.text import slugify

from sparks.django.models.mixins import DiffMixin

from mptt.models import MPTTModel, TreeForeignKey

from oneflow.base.fields import IntRedisDescriptor
from oneflow.base.utils import register_task_method

from common import lowername, DjangoUser as User


LOGGER = logging.getLogger(__name__)


__all__ = [
    'Folder',

    'folder_all_items_count_default',
    'folder_starred_items_count_default',
    'folder_unread_items_count_default',
    'folder_archived_items_count_default',
    'folder_bookmarked_items_count_default',
]


def qs_order_by_lower_name(queryset, field=None):
    """ Order a QuerySet by lowercase(field); field is “name” by default. """

    return queryset.extra(
        select={'_lower_order_by_field_': 'lower({0})'.format(field or 'name')}
    ).order_by('_lower_order_by_field_')


def get_folder_image_upload_path(instance, filename):

    if not filename.strip():
        filename = uuid.uuid4()

    # The filename will be used in a shell command later. In case the
    # user/admin forgets the '"' in the configuration, avoid problems.
    filename = filename.replace(u' ', u'_')

    if instance:
        return 'user/{0}/folder/{1}/images/{2}'.format(
            instance.user.username, instance.id, filename)

    return u'images/%Y/%m/%d/{0}'.format(filename)


# ——————————————————————————————————————————————————————————————— Redis Helpers


def folder_all_items_count_default(folder, *args, **kwargs):

    return folder.reads.all().count()


def folder_starred_items_count_default(folder, *args, **kwargs):

    return folder.reads.filter(is_starred=True).count()


def folder_unread_items_count_default(folder, *args, **kwargs):

    return folder.reads.filter(is_read=False).count()


def folder_archived_items_count_default(folder, *args, **kwargs):

    return folder.reads.filter(is_archived=True).count()


def folder_bookmarked_items_count_default(folder, *args, **kwargs):

    return folder.reads.filter(is_bookmarked=True).count()


# —————————————————————————————————————————————————————————————————————— Models


class Folder(MPTTModel, DiffMixin):

    """ A simple folder structure. """

    class Meta:
        app_label = 'core'
        verbose_name = _(u'Folder')
        verbose_name_plural = _(u'Folders')
        unique_together = ('name', 'user', 'parent', )

    user = models.ForeignKey(User, verbose_name=_(u'Owner'),
                             related_name='folders')

    name = models.CharField(verbose_name=_(u'Name'), max_length=255)
    slug = models.CharField(verbose_name=_(u'slug'), max_length=255,
                            null=True, blank=True)

    parent = TreeForeignKey('self', null=True, blank=True,
                            related_name='children')

    date_created = models.DateTimeField(auto_now_add=True, blank=True,
                                        verbose_name=_(u'Date created'))

    # Allow the user to also customize the visual of his/her subscription.
    image = models.ImageField(
        verbose_name=_(u'Image'), null=True, blank=True,
        upload_to=get_folder_image_upload_path, max_length=256,
        help_text=_(u'A custom image for the folder. Takes precedence over '
                    u'image_url if both are filled.'))

    image_url = models.URLField(
        verbose_name=_(u'Image URL'), null=True, blank=True, max_length=384,
        help_text=_(u'A full URL of an online image, if you prefer hosting '
                    u'it outside of 1flow.'))

    # —————————————————————————————————————————————————————— Cached descriptors

    all_items_count = IntRedisDescriptor(
        attr_name='f.aa_c', default=folder_all_items_count_default,
        set_default=True)

    unread_items_count = IntRedisDescriptor(
        attr_name='f.ua_c', default=folder_unread_items_count_default,
        set_default=True)

    starred_items_count = IntRedisDescriptor(
        attr_name='f.sa_c', default=folder_starred_items_count_default,
        set_default=True)

    archived_items_count = IntRedisDescriptor(
        attr_name='f.ra_c',
        default=folder_archived_items_count_default,
        set_default=True, min_value=0)

    bookmarked_items_count = IntRedisDescriptor(
        attr_name='f.ba_c', default=folder_bookmarked_items_count_default,
        set_default=True)

    # ————————————————————————————————————————————————————————— Django & Python

    def __unicode__(self):
        """ Wake up, pep257. That's just the unicode method. """

        return _(u'{0} (#{1}, level {2}) for user {3}{4}{5}').format(
            self.name, self.id, self.level, self.user,
            _(u', parent: {0} (#{1})').format(self.parent.name, self.parent.id)
            if self.parent else u'',
            _(u', children: {0}').format(u', '.join(
                _(u'{0} (#{1})').format(child.name, child.id)
                for child in self.children.all()))
            if self.children != [] else u'')

    # —————————————————————————————————————————————————————————————— Properties

    @property
    def has_content(self):

        # PERF: it's faster to test for children than query for subscriptions.
        return self.children or self.subscriptions
<commit_msg>new `Folder.is_root` property.<commit_after># -*- coding: utf-8 -*-
"""
Copyright 2013-2014 <NAME> <<EMAIL>>.

This file is part of the 1flow project.

1flow is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.

1flow is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public
License along with 1flow.  If not, see http://www.gnu.org/licenses/

"""

import uuid
import logging

from django.db import models
from django.db.models.signals import pre_save
from django.utils.translation import ugettext_lazy as _
from django.utils.text import slugify

from sparks.django.models.mixins import DiffMixin

from mptt.models import MPTTModel, TreeForeignKey

from oneflow.base.fields import IntRedisDescriptor
from oneflow.base.utils import register_task_method

from common import lowername, DjangoUser as User


LOGGER = logging.getLogger(__name__)


__all__ = [
    'Folder',

    'folder_all_items_count_default',
    'folder_starred_items_count_default',
    'folder_unread_items_count_default',
    'folder_archived_items_count_default',
    'folder_bookmarked_items_count_default',
]


def qs_order_by_lower_name(queryset, field=None):
    """ Order a QuerySet by lowercase(field); field is “name” by default. """

    return queryset.extra(
        select={'_lower_order_by_field_': 'lower({0})'.format(field or 'name')}
    ).order_by('_lower_order_by_field_')


def get_folder_image_upload_path(instance, filename):

    if not filename.strip():
        filename = uuid.uuid4()

    # The filename will be used in a shell command later. In case the
    # user/admin forgets the '"' in the configuration, avoid problems.
    filename = filename.replace(u' ', u'_')

    if instance:
        return 'user/{0}/folder/{1}/images/{2}'.format(
            instance.user.username, instance.id, filename)

    return u'images/%Y/%m/%d/{0}'.format(filename)


# ——————————————————————————————————————————————————————————————— Redis Helpers


def folder_all_items_count_default(folder, *args, **kwargs):

    return folder.reads.all().count()


def folder_starred_items_count_default(folder, *args, **kwargs):

    return folder.reads.filter(is_starred=True).count()


def folder_unread_items_count_default(folder, *args, **kwargs):

    return folder.reads.filter(is_read=False).count()


def folder_archived_items_count_default(folder, *args, **kwargs):

    return folder.reads.filter(is_archived=True).count()


def folder_bookmarked_items_count_default(folder, *args, **kwargs):

    return folder.reads.filter(is_bookmarked=True).count()


# —————————————————————————————————————————————————————————————————————— Models


class Folder(MPTTModel, DiffMixin):

    """ A simple folder structure. """

    class Meta:
        app_label = 'core'
        verbose_name = _(u'Folder')
        verbose_name_plural = _(u'Folders')
        unique_together = ('name', 'user', 'parent', )

    user = models.ForeignKey(User, verbose_name=_(u'Owner'),
                             related_name='folders')

    name = models.CharField(verbose_name=_(u'Name'), max_length=255)
    slug = models.CharField(verbose_name=_(u'slug'), max_length=255,
                            null=True, blank=True)

    parent = TreeForeignKey('self', null=True, blank=True,
                            related_name='children')

    date_created = models.DateTimeField(auto_now_add=True, blank=True,
                                        verbose_name=_(u'Date created'))

    # Allow the user to also customize the visual of his/her subscription.
    image = models.ImageField(
        verbose_name=_(u'Image'), null=True, blank=True,
        upload_to=get_folder_image_upload_path, max_length=256,
        help_text=_(u'A custom image for the folder. Takes precedence over '
                    u'image_url if both are filled.'))

    image_url = models.URLField(
        verbose_name=_(u'Image URL'), null=True, blank=True, max_length=384,
        help_text=_(u'A full URL of an online image, if you prefer hosting '
                    u'it outside of 1flow.'))

    # —————————————————————————————————————————————————————— Cached descriptors

    all_items_count = IntRedisDescriptor(
        attr_name='f.aa_c', default=folder_all_items_count_default,
        set_default=True)

    unread_items_count = IntRedisDescriptor(
        attr_name='f.ua_c', default=folder_unread_items_count_default,
        set_default=True)

    starred_items_count = IntRedisDescriptor(
        attr_name='f.sa_c', default=folder_starred_items_count_default,
        set_default=True)

    archived_items_count = IntRedisDescriptor(
        attr_name='f.ra_c',
        default=folder_archived_items_count_default,
        set_default=True, min_value=0)

    bookmarked_items_count = IntRedisDescriptor(
        attr_name='f.ba_c', default=folder_bookmarked_items_count_default,
        set_default=True)

    # ————————————————————————————————————————————————————————— Django & Python

    def __unicode__(self):
        """ Wake up, pep257. That's just the unicode method. """

        return _(u'{0} (#{1}, level {2}) for user {3}{4}{5}').format(
            self.name, self.id, self.level, self.user,
            _(u', parent: {0} (#{1})').format(self.parent.name, self.parent.id)
            if self.parent else u'',
            _(u', children: {0}').format(u', '.join(
                _(u'{0} (#{1})').format(child.name, child.id)
                for child in self.children.all()))
            if self.children != [] else u'')

    # —————————————————————————————————————————————————————————————— Properties

    @property
    def is_root(self):
        """ Return True if current folder is a root for a user. """

        return self.name == u'__root__' and self.parent is None

    @property
    def has_content(self):

        # PERF: it's faster to test for children than query for subscriptions.
        return self.children or self.subscriptions

{'fraction_non_alphanumeric': 0.15059370988446727, 'fraction_numerical': 0.007702182284980745, 'mean_word_length': 3.5525931336742147, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4314487', 'n_tokens_mistral': 3883, 'n_tokens_neox': 3620, 'n_words': 937}