Example #1
0
class TreeLeaf(EntityBase):
    parent=ForeignKey('self', on_delete=SET_NULL, blank=True, null=True)
    path=PathField(unique=True)
    name = CharField(max_length=255)

    class Meta:
        verbose_name_plural = "TreeLeaves"

    def __str__(self):
        return str(self.path)

    def depth(self):
        return TreeLeaf.objects.annotate(depth=Depth('path')).get(pk=self.pk).depth

    def subcategories(self, minLevel=1):
        return TreeLeaf.objects.select_related('parent').filter(
            path__descendants=self.path,
            path__depth__gte=self.depth()+minLevel
        )

    def computePath(self):
        """ Returns the string representing the path element """
        pathStr=self.name.replace(" ","_").replace("-","_").replace("(","_").replace(")","_")
        if self.parent:
            pathStr=self.parent.computePath()+"."+pathStr
        elif self.project:
            projName=self.project.name.replace(" ","_").replace("-","_").replace("(","_").replace(")","_")
            pathStr=projName+"."+pathStr
        return pathStr
Example #2
0
class Category(models.Model):
    name = models.CharField(max_length=140)
    path = PathField(unique=True)

    class Meta:
        verbose_name_plural = "categories"

    def __str__(self):
        return self.path

    def subcategories(self):
        return Category.objects.filter(path__descendant=self.path,
                                       path__nlevel=NLevel(self.path) + 1)
Example #3
0
def test_registered_lookups():
    registered_lookups = PathField.get_lookups()

    assert "ancestors" in registered_lookups, "Missing 'ancestors' in lookups"
    assert registered_lookups["ancestors"] is lookups.AncestorLookup

    assert "descendants" in registered_lookups, "Missing 'descendants' in lookups"
    assert registered_lookups["descendants"] is lookups.DescendantLookup

    assert "match" in registered_lookups, "Missing 'match' in lookups"
    assert registered_lookups["match"] is lookups.MatchLookup

    assert "depth" in registered_lookups, "Missing 'depth' in lookups"
    assert registered_lookups["depth"] is functions.NLevel

    assert "contains" in registered_lookups, "Missing 'contains' in lookups"
    assert registered_lookups["contains"] is lookups.ContainsLookup
Example #4
0
class Contest(models.Model):
    resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
    title = models.CharField(max_length=2048)
    slug = models.CharField(max_length=2048, null=True, blank=True, db_index=True)
    title_path = PathField(null=True, blank=True, db_index=True)
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()
    duration_in_secs = models.IntegerField(null=False, blank=True)
    url = models.CharField(max_length=255)
    key = models.CharField(max_length=255)
    host = models.CharField(max_length=255)
    uid = models.CharField(max_length=100, null=True, blank=True)
    edit = models.CharField(max_length=100, null=True, blank=True)
    invisible = models.BooleanField(default=False)
    standings_url = models.CharField(max_length=2048, null=True, blank=True)
    calculate_time = models.BooleanField(default=False)
    info = JSONField(default=dict, blank=True)
    writers = models.ManyToManyField('ranking.Account', blank=True, related_name='writer_set')

    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    was_auto_added = models.BooleanField(default=False)

    objects = BaseManager()
    visible = VisibleContestManager()

    class Meta:
        unique_together = ('resource', 'key', )

        indexes = [
            models.Index(fields=['start_time']),
            models.Index(fields=['end_time']),
        ]

    def save(self, *args, **kwargs):
        if self.duration_in_secs is None:
            self.duration_in_secs = (self.end_time - self.start_time).total_seconds()
        self.slug = slug(self.title).strip('-')
        self.title_path = self.slug.replace('-', '.')
        return super(Contest, self).save(*args, **kwargs)

    def is_over(self):
        return self.end_time <= timezone.now()

    def is_running(self):
        return not self.is_over() and self.start_time < timezone.now()

    def is_coming(self):
        return timezone.now() < self.start_time

    @property
    def next_time(self):
        if self.is_over():
            return 0
        return int(round(
            ((
                self.end_time
                if self.is_running()
                else self.start_time
            ) - timezone.now()).total_seconds()
        ))

    def __str__(self):
        return "%s [%d]" % (self.title, self.id)

    @property
    def duration(self):
        return timedelta(seconds=self.duration_in_secs)
        # Fix for virtual contest
        # return self.end_time - self.start_time

    @property
    def hr_duration(self):
        duration = self.duration
        if duration > timedelta(days=999):
            return "%d years" % (duration.days // 364)
        elif duration > timedelta(days=3):
            return "%d days" % duration.days
        else:
            total = duration.total_seconds()
            return "%02d:%02d" % ((total + 1e-9) // 3600, (total + 1e-9) % 3600 // 60)

    @classmethod
    def month_regex(cls):
        if not hasattr(cls, '_month_regex'):
            months = itertools.chain(calendar.month_name, calendar.month_abbr)
            regex = '|'.join([f'[{m[0]}{m[0].lower()}]{m[1:]}' for m in months if m])
            cls._month_regex = rf'\b(?:{regex})\b'
        return cls._month_regex

    @staticmethod
    def title_neighbors_(title, deep, viewed):
        viewed.add(title)
        if deep == 0:
            return

        for match in re.finditer(rf'([0-9]+|[A-Z]\b|{Contest.month_regex()})', title):
            for delta in (-1, 1):
                base_title = title
                value = match.group(0)
                values = []
                if value.isdigit():
                    value = str(int(value) + delta)
                elif len(value) == 1:
                    value = chr(ord(value) + delta)
                else:
                    mformat = '%b' if len(value) == 3 else '%B'
                    index = datetime.strptime(value.title(), mformat).month
                    mformats = ['%b', '%B'] if index == 5 else [mformat]
                    if not (1 <= index + delta <= 12):
                        ym = re.search(r'\b[0-9]{4}\b', base_title)
                        if ym:
                            year = str(int(ym.group()) + delta)
                            base_title = base_title[:ym.start()] + year + base_title[ym.end():]
                    index = (index - 1 + delta) % 12 + 1
                    for mformat in mformats:
                        values.append(datetime.strptime(str(index), '%m').strftime(mformat))
                values = values or [value]
                for value in values:
                    new_title = base_title[:match.start()] + value + base_title[match.end():]
                    if new_title in viewed:
                        continue
                    Contest.title_neighbors_(new_title, deep=deep - 1, viewed=viewed)

    def neighbors(self):
        viewed = set()
        Contest.title_neighbors_(self.title, deep=1, viewed=viewed)

        cond = Q()
        for title in viewed:
            cond |= Q(title=title)

        resource_contests = Contest.objects.filter(resource=self.resource_id)
        resource_contests = resource_contests.annotate(has_statistics=Exists('statistics')).filter(has_statistics=True)

        for query, order in (
            (Q(end_time__lt=self.start_time), '-end_time'),
            (Q(start_time__gt=self.end_time), 'start_time'),
        ):
            c = resource_contests.filter(query).order_by(order).first()
            if c:
                cond |= Q(pk=c.pk)

            if self.title_path is not None:
                qs = resource_contests.filter(query).exclude(title=self.title)
                qs = qs.extra(select={'lcp': f'''nlevel(lca(title_path, '{self.title_path}'))'''})
                qs = qs.order_by('-lcp', order)
                c = qs.first()
                if c and c.lcp:
                    cond |= Q(pk=c.pk)

        qs = resource_contests.filter(cond).exclude(pk=self.pk).order_by('end_time')

        return qs
Example #5
0
class Leaf(Model):
    project = ForeignKey(Project,
                         on_delete=SET_NULL,
                         null=True,
                         blank=True,
                         db_column='project')
    meta = ForeignKey(LeafType,
                      on_delete=SET_NULL,
                      null=True,
                      blank=True,
                      db_column='meta')
    """ Meta points to the defintion of the attribute field. That is
        a handful of AttributeTypes are associated to a given EntityType
        that is pointed to by this value. That set describes the `attribute`
        field of this structure. """
    attributes = JSONField(null=True, blank=True)
    """ Values of user defined attributes. """
    created_datetime = DateTimeField(auto_now_add=True, null=True, blank=True)
    created_by = ForeignKey(User,
                            on_delete=SET_NULL,
                            null=True,
                            blank=True,
                            related_name='leaf_created_by',
                            db_column='created_by')
    modified_datetime = DateTimeField(auto_now=True, null=True, blank=True)
    modified_by = ForeignKey(User,
                             on_delete=SET_NULL,
                             null=True,
                             blank=True,
                             related_name='leaf_modified_by',
                             db_column='modified_by')
    parent = ForeignKey('self',
                        on_delete=SET_NULL,
                        blank=True,
                        null=True,
                        db_column='parent')
    path = PathField(unique=True)
    name = CharField(max_length=255)

    class Meta:
        verbose_name_plural = "Leaves"

    def __str__(self):
        return str(self.path)

    def depth(self):
        return Leaf.objects.annotate(depth=Depth('path')).get(pk=self.pk).depth

    def subcategories(self, minLevel=1):
        return Leaf.objects.select_related('parent').filter(
            path__descendants=self.path,
            path__depth__gte=self.depth() + minLevel)

    def computePath(self):
        """ Returns the string representing the path element """
        pathStr = self.name.replace(" ", "_").replace("-", "_").replace(
            "(", "_").replace(")", "_")
        if self.parent:
            pathStr = self.parent.computePath() + "." + pathStr
        elif self.project:
            projName = self.project.name.replace(" ", "_").replace(
                "-", "_").replace("(", "_").replace(")", "_")
            pathStr = projName + "." + pathStr
        return pathStr
Example #6
0
import uuid

# Load the main.view logger
logger = logging.getLogger(__name__)


class Depth(Transform):
    lookup_name = "depth"
    function = "nlevel"

    @property
    def output_field(self):
        return IntegerField()


PathField.register_lookup(Depth)

FileFormat = [('mp4', 'mp4'), ('webm', 'webm'), ('mov', 'mov')]
ImageFileFormat = [('jpg', 'jpg'), ('png', 'png'), ('bmp', 'bmp'),
                   ('raw', 'raw')]

## Describes different association models in the database
AssociationTypes = [
    ('Media', 'Relates to one or more media items'),
    ('Frame', 'Relates to a specific frame in a video'
     ),  #Relates to one or more frames in a video
    ('Localization', 'Relates to localization(s)')
]  #Relates to one-to-many localizations


class MediaAccess(Enum):
Example #7
0
class OrgUnit(models.Model):
    VALIDATION_NEW = "NEW"
    VALIDATION_VALID = "VALID"
    VALIDATION_REJECTED = "REJECTED"

    VALIDATION_STATUS_CHOICES = (
        (VALIDATION_NEW, _("new")),
        (VALIDATION_VALID, _("valid")),
        (VALIDATION_REJECTED, _("rejected")),
    )

    name = models.CharField(max_length=255)
    uuid = models.TextField(null=True, blank=True, db_index=True)
    custom = models.BooleanField(default=False)
    validated = models.BooleanField(
        default=True, db_index=True)  # TO DO : remove in a later migration
    validation_status = models.CharField(max_length=25,
                                         choices=VALIDATION_STATUS_CHOICES,
                                         default=VALIDATION_NEW)
    version = models.ForeignKey("SourceVersion",
                                null=True,
                                blank=True,
                                on_delete=models.CASCADE)
    parent = models.ForeignKey("OrgUnit",
                               on_delete=models.CASCADE,
                               null=True,
                               blank=True)
    path = PathField(null=True, blank=True, unique=True)
    aliases = ArrayField(CITextField(max_length=255, blank=True),
                         size=100,
                         null=True,
                         blank=True)

    org_unit_type = models.ForeignKey(OrgUnitType,
                                      on_delete=models.CASCADE,
                                      null=True,
                                      blank=True)

    sub_source = models.TextField(
        null=True,
        blank=True)  # sometimes, in a given source, there are sub sources
    source_ref = models.TextField(null=True, blank=True, db_index=True)
    geom = MultiPolygonField(null=True, blank=True, srid=4326, geography=True)
    simplified_geom = MultiPolygonField(null=True,
                                        blank=True,
                                        srid=4326,
                                        geography=True)
    catchment = MultiPolygonField(null=True,
                                  blank=True,
                                  srid=4326,
                                  geography=True)
    geom_ref = models.IntegerField(null=True, blank=True)

    gps_source = models.TextField(null=True, blank=True)
    location = PointField(null=True,
                          blank=True,
                          geography=True,
                          dim=3,
                          srid=4326)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    creator = models.ForeignKey(User,
                                null=True,
                                blank=True,
                                on_delete=models.SET_NULL)

    objects = OrgUnitManager.from_queryset(OrgUnitQuerySet)()

    class Meta:
        indexes = [GistIndex(fields=["path"], buffering=True)]

    def save(self, *args, skip_calculate_path: bool = False, **kwargs):
        """Override default save() to make sure that the path property is calculated and saved,
        for this org unit and its children.

        :param skip_calculate_path: use with caution - can be useful in scripts where the extra transactions
                                    would be a burden, but the path needs to be set afterwards
        """

        if skip_calculate_path:
            super().save(*args, **kwargs)
        else:
            with transaction.atomic():
                super().save(*args, **kwargs)
                OrgUnit.objects.bulk_update(self.calculate_paths(), ["path"])

    def calculate_paths(self,
                        force_recalculate: bool = False
                        ) -> typing.List["OrgUnit"]:
        """Calculate the path for this org unit and all its children.

        This method will check if this org unit path should change. If it is the case (or if force_recalculate is
        True), it will update the path property for the org unit and its children, and return all the modified
        records.

        Please note that this method does not save the modified records. Instead, they are updated in bulk in the
        save() method.

        :param force_recalculate: calculate path for all descendants, even if this org unit path does not change
        """

        # For now, we will skip org units that have a parent without a path.
        # The idea is that a management command (set_org_unit_path) will handle the initial seeding of the
        # path field, starting at the top of the pyramid. Once this script has been run and the field is filled for
        # all org units, this should not happen anymore.
        # TODO: remove condition below
        if self.parent is not None and self.parent.path is None:
            return []

        # keep track of updated records
        updated_records = []

        base_path = [] if self.parent is None else list(self.parent.path)
        new_path = [*base_path, str(self.pk)]
        path_has_changed = new_path != self.path

        if path_has_changed:
            self.path = new_path
            updated_records += [self]

        if path_has_changed or force_recalculate:
            for child in self.orgunit_set.all():
                updated_records += child.calculate_paths(force_recalculate)

        return updated_records

    def __str__(self):
        return "%s %s %d" % (self.org_unit_type, self.name,
                             self.id if self.id else -1)

    def as_dict_for_mobile_lite(self):
        return {
            "n": self.name,
            "id": self.id,
            "p": self.parent_id,
            "out": self.org_unit_type_id,
            "c_a": self.created_at.timestamp() if self.created_at else None,
            "lat": self.location.y if self.location else None,
            "lon": self.location.x if self.location else None,
            "alt": self.location.z if self.location else None,
        }

    def as_dict_for_mobile(self):
        return {
            "name":
            self.name,
            "id":
            self.id,
            "parent_id":
            self.parent_id,
            "org_unit_type_id":
            self.org_unit_type_id,
            "org_unit_type_name":
            self.org_unit_type.name if self.org_unit_type else None,
            "validation_status":
            self.validation_status if self.org_unit_type else None,
            "created_at":
            self.created_at.timestamp() if self.created_at else None,
            "updated_at":
            self.updated_at.timestamp() if self.updated_at else None,
            "latitude":
            self.location.y if self.location else None,
            "longitude":
            self.location.x if self.location else None,
            "altitude":
            self.location.z if self.location else None,
        }

    def as_dict(self, with_groups=True):
        res = {
            "name":
            self.name,
            "short_name":
            self.name,
            "id":
            self.id,
            "source":
            self.version.data_source.name if self.version else None,
            "source_ref":
            self.source_ref,
            "parent_id":
            self.parent_id,
            "org_unit_type_id":
            self.org_unit_type_id,
            "org_unit_type_name":
            self.org_unit_type.name if self.org_unit_type else None,
            "created_at":
            self.created_at.timestamp() if self.created_at else None,
            "updated_at":
            self.updated_at.timestamp() if self.updated_at else None,
            "aliases":
            self.aliases,
            "validation_status":
            self.validation_status,
            "latitude":
            self.location.y if self.location else None,
            "longitude":
            self.location.x if self.location else None,
            "altitude":
            self.location.z if self.location else None,
            "has_geo_json":
            True if self.simplified_geom else False,
            "version":
            self.version.number if self.version else None,
        }

        if hasattr(self, "search_index"):
            res["search_index"] = self.search_index
        return res

    def as_dict_with_parents(self, light=False, light_parents=True):
        res = {
            "name":
            self.name,
            "short_name":
            self.name,
            "id":
            self.id,
            "sub_source":
            self.sub_source,
            "sub_source_id":
            self.sub_source,
            "source_ref":
            self.source_ref,
            "source_url":
            self.version.data_source.credentials.url
            if self.version and self.version.data_source
            and self.version.data_source.credentials else None,
            "parent_id":
            self.parent_id,
            "validation_status":
            self.validation_status,
            "parent_name":
            self.parent.name if self.parent else None,
            "parent":
            self.parent.as_dict_with_parents(light=light_parents,
                                             light_parents=light_parents)
            if self.parent else None,
            "org_unit_type_id":
            self.org_unit_type_id,
            "created_at":
            self.created_at.timestamp() if self.created_at else None,
            "updated_at":
            self.updated_at.timestamp() if self.updated_at else None,
            "aliases":
            self.aliases,
            "latitude":
            self.location.y if self.location else None,
            "longitude":
            self.location.x if self.location else None,
            "altitude":
            self.location.z if self.location else None,
            "has_geo_json":
            True if self.simplified_geom else False,
        }
        if not light:  # avoiding joins here
            res["groups"] = [
                group.as_dict(with_counts=False)
                for group in self.groups.all()
            ]
            res["org_unit_type_name"] = self.org_unit_type.name if self.org_unit_type else None
            res["org_unit_type"] = self.org_unit_type.as_dict(
            ) if self.org_unit_type else None
            res["source"] = self.version.data_source.name if self.version else None
            res["source_id"] = self.version.data_source.id if self.version else None
            res["version"] = self.version.number if self.version else None
        if hasattr(self, "search_index"):
            res["search_index"] = self.search_index
        return res

    def as_small_dict(self):
        res = {
            "name":
            self.name,
            "id":
            self.id,
            "parent_id":
            self.parent_id,
            "validation_status":
            self.validation_status,
            "parent_name":
            self.parent.name if self.parent else None,
            "source":
            self.version.data_source.name if self.version else None,
            "source_ref":
            self.source_ref,
            "parent":
            self.parent.as_small_dict() if self.parent else None,
            "org_unit_type_name":
            self.org_unit_type.name if self.org_unit_type else None,
        }
        if hasattr(self, "search_index"):
            res["search_index"] = self.search_index
        return res

    def as_dict_for_csv(self):
        return {
            "name": self.name,
            "id": self.id,
            "source_ref": self.source_ref,
            "parent_id": self.parent_id,
            "org_unit_type": self.org_unit_type.name,
        }

    def as_location(self):
        res = {
            "id":
            self.id,
            "name":
            self.name,
            "short_name":
            self.name,
            "latitude":
            self.location.y if self.location else None,
            "longitude":
            self.location.x if self.location else None,
            "altitude":
            self.location.z if self.location else None,
            "has_geo_json":
            True if self.simplified_geom else False,
            "org_unit_type":
            self.org_unit_type.name if self.org_unit_type else None,
            "org_unit_type_depth":
            self.org_unit_type.depth if self.org_unit_type else None,
            "source_id":
            self.version.data_source.id if self.version else None,
            "source_name":
            self.version.data_source.name if self.version else None,
        }
        if hasattr(self, "search_index"):
            res["search_index"] = self.search_index
        return res

    def source_path(self):
        """DHIS2-friendly path built using source refs"""

        path_components = []
        cur = self
        while cur:
            if cur.source_ref:
                path_components.insert(0, cur.source_ref)
            cur = cur.parent
        if len(path_components) > 0:
            return "/" + ("/".join(path_components))
        return None