Beispiel #1
0
class Category(models.Model):
    name = models.CharField(max_length=100)
    top_level = models.BooleanField(default=False)
    slug = AutoSlugField(populate_from='name',
                         max_length=100,
                         blank=True,
                         null=True)
    order_by = models.IntegerField(default=0)

    categories = CategoryQuerySet.as_manager()
    objects = CategoryQuerySet.as_manager()

    class Meta:
        verbose_name = _('Category')
        verbose_name_plural = _('Categories')
        ordering = ('name', )

    def __unicode__(self):
        return self.name

    @classmethod
    def api_translation_fields(cls):
        """Returns name field's translation field names"""
        return [
            f.name for f in cls._meta.get_fields()
            if f.name.startswith('name') and f.name != 'name'
        ]
Beispiel #2
0
class Collection(TimestampBase):
    PUBLIC = 'public'
    PRIVATE = 'private'
    VISIBILITY_TYPES = (
        (PUBLIC, _('Public')),
        (PRIVATE, _('Private')),
    )
    title = models.TextField(blank=False,
                            null=False,
                            help_text=_("A title for the collection"))
    description = models.TextField(blank=True,
                                   null=True,
                                   default=None,
                                   help_text=_("A description of the collection"))
    visibility = models.CharField(
                            max_length=50,
                            choices=VISIBILITY_TYPES,
                            default=PRIVATE)
    image = models.ImageField(
        upload_to='collection/%Y/%m/%d', null=True, blank=True)
    slug = AutoSlugField(populate_from='title', max_length=255, blank=True, null=True)

    class Meta:
        verbose_name = _('Collection')
        verbose_name_plural = _('Collections')
        ordering = ('title',)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('orb_collection', args=[self.slug])

    def image_filename(self):
        return os.path.basename(self.image.name)
Beispiel #3
0
class Tag(TimestampBase):
    category = models.ForeignKey(Category)
    parent_tag = models.ForeignKey('self',
                                   blank=True,
                                   null=True,
                                   default=None,
                                   related_name="children")
    name = models.CharField(max_length=100)
    create_user = models.ForeignKey(settings.AUTH_USER_MODEL,
                                    related_name='tag_create_user',
                                    blank=True,
                                    null=True,
                                    default=None,
                                    on_delete=models.SET_NULL)
    update_user = models.ForeignKey(settings.AUTH_USER_MODEL,
                                    related_name='tag_update_user',
                                    blank=True,
                                    null=True,
                                    default=None,
                                    on_delete=models.SET_NULL)
    image = models.ImageField(upload_to='tag/%Y/%m/%d', null=True, blank=True)
    slug = AutoSlugField(populate_from='name',
                         max_length=100,
                         blank=True,
                         null=True)
    order_by = models.IntegerField(default=0)
    external_url = models.URLField(blank=True,
                                   null=True,
                                   default=None,
                                   max_length=500)
    description = models.TextField(blank=True, null=True, default=None)
    summary = models.CharField(blank=True, null=True, max_length=100)
    contact_email = models.CharField(blank=True, null=True, max_length=100)
    published = models.BooleanField(
        default=True, help_text=_(u"Used to toggle status of health domains."))

    tags = TagQuerySet.as_manager()
    objects = TagQuerySet.as_manager()

    # objects = tags  # backwards compatibility

    class Meta:
        verbose_name = _('Tag')
        verbose_name_plural = _('Tags')
        ordering = ('name', )
        unique_together = ('name', 'category')

    def __unicode__(self):
        return self.name

    def get_absolute_url(self):
        return urlresolvers.reverse('orb_tags', args=[self.slug])

    def save(self, *args, **kwargs):

        if self.image and (self.image.name.startswith("http://")
                           or self.image.name.startswith("https://")):
            remote_image_file = self.image.name
        else:
            remote_image_file = None

        # add generic geography icon if not specified
        if self.category.slug == 'geography' and not self.image:
            self.image = 'tag/geography_default.png'

        # add generic language icon if not specified
        if self.category.slug == 'language' and not self.image:
            self.image = 'tag/language_default.png'

        # add generic organization icon if not specified
        if self.category.slug == 'organisation' and not self.image:
            self.image = 'tag/organisation_default.png'

        # add generic other icon if not specified
        if self.category.slug == 'other' and not self.image:
            self.image = 'tag/other_default.png'

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

        if remote_image_file:
            image_cleaner(self, url=remote_image_file)

        return self

    def image_filename(self):
        return os.path.basename(self.image.name)

    def get_property(self, name):
        props = TagProperty.objects.filter(tag=self, name=name)
        return props

    def merge(self, other):
        """
        Merges another tag into this one

        In doing so it 'merges' relations by ensuring tagged resources are maintained

        Args:
            other: the 'loser' Tag instance

        Returns:
            None

        """
        # The following is a more sensible query-based way of updating, however
        # due to a limitation in MySQL this is not possible (MySQL Error 1093):
        # >>> other.resourcetag.exclude(resource__resourcetag__tag=self).update(tag=self)

        for resource_tag in other.resourcetag.exclude(
                resource__resourcetag__tag=self):
            resource_tag.tag = self
            resource_tag.save()

        other.tracker.all().update(tag=self)
        other.delete()
Beispiel #4
0
class Resource(TimestampBase):
    REJECTED = 'rejected'
    APPROVED = 'approved'
    PENDING = 'pending'
    ARCHIVED = 'archived'

    STATUS_TYPES = (
        (APPROVED, _('Approved')),
        (PENDING, _('Pending')),
        (REJECTED, _('Rejected')),
        (ARCHIVED, _('Archived')),
    )

    MINS = 'mins'
    HOURS = 'hours'
    DAYS = 'days'
    WEEKS = 'weeks'
    STUDY_TIME_UNITS = (
        (MINS, _('Mins')),
        (HOURS, _('Hours')),
        (DAYS, _('Days')),
        (WEEKS, _('Weeks')),
    )

    guid = models.UUIDField(null=True,
                            default=uuid.uuid4,
                            unique=True,
                            editable=False)
    title = models.TextField(blank=False, null=False)
    description = models.TextField(blank=False, null=False)
    image = models.ImageField(upload_to='resourceimage/%Y/%m/%d',
                              max_length=200,
                              blank=True,
                              null=True)
    status = models.CharField(max_length=50,
                              choices=STATUS_TYPES,
                              default=PENDING)
    create_user = models.ForeignKey(settings.AUTH_USER_MODEL,
                                    related_name='resource_create_user',
                                    blank=True,
                                    null=True,
                                    default=None,
                                    on_delete=models.SET_NULL)
    update_user = models.ForeignKey(settings.AUTH_USER_MODEL,
                                    related_name='resource_update_user',
                                    blank=True,
                                    null=True,
                                    default=None,
                                    on_delete=models.SET_NULL)
    slug = AutoSlugField(populate_from='title',
                         max_length=100,
                         blank=True,
                         null=True)
    study_time_number = models.IntegerField(default=0, null=True, blank=True)
    study_time_unit = models.CharField(max_length=10,
                                       choices=STUDY_TIME_UNITS,
                                       blank=True,
                                       null=True)
    born_on = models.DateTimeField(blank=True, null=True, default=None)
    attribution = models.TextField(blank=True, null=True, default=None)

    # Tracking fields
    source_url = models.URLField(null=True,
                                 blank=True,
                                 help_text=_(u"Original resource URL."))
    source_name = models.CharField(
        null=True,
        blank=True,
        max_length=200,
        help_text=_(
            u"Name of the source ORB instance where resource was sourced."))
    source_host = models.URLField(
        null=True,
        blank=True,
        help_text=_(
            u"Host URL of the original ORB instance where resource was sourced."
        ))
    source_peer = models.ForeignKey(
        'peers.Peer',
        null=True,
        blank=True,
        related_name="resources",
        help_text=_(u"The peer ORB from which the resource was downloaded."))
    tags = models.ManyToManyField('Tag', through='ResourceTag', blank=True)

    resources = ResourceQueryset.as_manager()
    objects = ResourceQueryset.as_manager()
    # objects = resources  # alias

    # Fields to strip from API data
    API_EXCLUDED_FIELDS = ['id', 'guid']

    class Meta:
        verbose_name = _(u'Resource')
        verbose_name_plural = _(u'Resources')
        ordering = ('title', )

    def __unicode__(self):
        return self.title

    def save(self, **kwargs):
        """Cleans API submitted images"""
        if self.image and (self.image.name.startswith("http://")
                           or self.image.name.startswith("https://")):
            remote_image_file = self.image.name
        else:
            remote_image_file = None

        super(Resource, self).save(**kwargs)

        if remote_image_file:
            image_cleaner(self, url=remote_image_file)

        return self

    def get_absolute_url(self):
        return urlresolvers.reverse('orb_resource', args=[self.slug])

    def update_from_api(self, api_data):
        """
        Conditionally updates a resource based on API data.

        Args:
            api_data: serialized data from the ORB API describing a resource in detail

        Returns:
            boolean for whether the resource needed updating

        """
        if self.is_local():
            raise LookupError(
                "Cannot update a locally created resource from API data")

        if api_data['guid'] != str(self.guid):
            raise LookupError(
                "API GUID {} does not match local GUID {}".format(
                    api_data['guid'], str(self.guid)))

        updated_time, result = cal.parseDT(api_data.pop('update_date'))
        created_time, result = cal.parseDT(api_data.pop('create_date'))

        if updated_time.date <= self.create_date.date:
            return False

        resource_files = api_data.pop('files', [])
        languages = api_data.pop('languages', [])
        tags = api_data.pop('tags', [])
        resource_urls = api_data.pop('urls', [])
        resource_uri = api_data.pop('resource_uri')
        url = api_data.pop('url')

        for field in self.API_EXCLUDED_FIELDS:
            api_data.pop(field, None)

        import_user = get_import_user()

        cleaned_api_data = clean_api_data(api_data, 'attribution',
                                          'description', 'title')

        for attr, value in cleaned_api_data.iteritems():
            setattr(self, attr, value)

        self.update_user = import_user
        self.save()

        return True

    @classmethod
    def create_from_api(cls, api_data, peer=None):
        """
        Creates a new Resource object and its suite of related content based
        on a dictionary of data as returned from the ORB API

        Args:
            api_data: serialized data from the ORB API describing a resource in detail

        Returns:
            the Resource object created

        """

        resource_files = api_data.pop('files', [])
        languages = api_data.pop('languages', [])
        tags = api_data.pop('tags', [])
        resource_urls = api_data.pop('urls', [])
        resource_uri = api_data.pop('resource_uri')
        url = api_data.pop('url')

        import_user = get_import_user()

        cleaned_api_data = clean_api_data(api_data, 'attribution',
                                          'description', 'title')

        resource = cls.resources.create(source_peer=peer,
                                        create_user=import_user,
                                        update_user=import_user,
                                        **cleaned_api_data)

        ResourceURL.objects.bulk_create([
            ResourceURL.from_url_data(
                resource,
                clean_api_data(resource_url_data, "description", "title"),
                import_user,
            ) for resource_url_data in resource_urls
        ] + [
            ResourceURL.from_file_data(
                resource,
                clean_api_data(resource_file_data, "description", "title"),
                import_user,
            ) for resource_file_data in resource_files
        ])

        for tag_data in tags:
            ResourceTag.create_from_api_data(
                resource,
                clean_api_data(tag_data['tag'], "category", "description",
                               "name", "summary"),
                user=import_user,
            )

        return resource

    def approve(self):
        self.status = self.APPROVED
        self.content_reviews.all().update(status=self.APPROVED)
        signals.resource_approved.send(sender=self, resource=self)

    def reject(self):
        self.status = self.REJECTED
        self.content_reviews.all().update(status=self.REJECTED)
        signals.resource_rejected.send(sender=self, resource=self)

    def is_pending(self):
        return self.status not in [self.REJECTED, self.APPROVED]

    def is_local(self):
        return not bool(self.source_peer)

    def has_assignments(self):
        """Returns whether there are *any* reivew assignments"""
        return self.content_reviews.all().exists()

    def get_organisations(self):
        return Tag.objects.filter(resourcetag__resource=self,
                                  category__slug='organisation')

    def get_files(self):
        return ResourceFile.objects.filter(resource=self).order_by('order_by')

    def get_urls(self):
        return ResourceURL.objects.filter(resource=self).order_by('order_by')

    def get_categories(self):
        categories = Category.objects.filter(
            tag__resourcetag__resource=self).distinct().order_by('order_by')
        for c in categories:
            c.tags = Tag.objects.filter(resourcetag__resource=self, category=c)
        return categories

    def get_display_categories(self):
        categories = Category.objects.filter(
            tag__resourcetag__resource=self).exclude(
                slug='license').distinct().order_by('order_by')
        for c in categories:
            c.tags = Tag.tags.filter(category=c).by_resource(self)
        return categories

    def get_category(self, category_slug):
        tags = Tag.objects.filter(resourcetag__resource=self,
                                  category__slug=category_slug)
        return tags

    def get_type_tags(self):
        tags = Tag.objects.filter(resourcetag__resource=self,
                                  category__slug='type')
        return tags

    def get_no_hits(self):
        anon = ResourceTracker.objects.filter(
            resource=self,
            user=None).values_list('ip', flat=True).distinct().count()
        identified = ResourceTracker.objects.filter(resource=self).exclude(
            user=None).values_list('user', flat=True).distinct().count()
        return anon + identified

    def get_geographies(self):
        return Tag.tags.by_category('geography').by_resource(self)

    def get_devices(self):
        return Tag.tags.by_category('device').by_resource(self)

    def get_languages(self):
        return Tag.tags.by_category('language').by_resource(self)

    def get_license(self):
        # type: () -> Optional[Tag]
        """Returns the license tag or None

        There is expected to be one license for a resource but
        because there is no hard data restriction on this we
        check for the first match.
        """
        return Tag.tags.by_category('license').by_resource(self).first()

    def get_health_domains(self):
        return Tag.tags.by_category('health-domain').by_resource(self)

    def get_rating(self):
        rating = ResourceRating.objects.filter(resource=self).aggregate(
            rating=Avg('rating'), count=Count('rating'))
        if rating['rating']:
            rating['rating'] = round(rating['rating'], 0)
        return rating

    def available_languages(self):
        """
        Returns a list of site languages for which this resource has translations

        This is based on having both title and description for these fields.
        """
        field_names = {
            language[0]: [
                build_localized_fieldname(field, language[0])
                for field in ["title", "description"]
            ]
            for language in settings.LANGUAGES
        }

        return [
            language for language, fields in field_names.items()
            if all([getattr(self, field) for field in fields])
        ]

    def user_can_view(self, user):
        if self.status == Resource.APPROVED:
            return True
        elif user.is_anonymous():
            return False
        elif ((user.is_staff or user == self.create_user
               or user == self.update_user)
              or (user.userprofile and (user.userprofile.is_reviewer))):
            return True
        else:
            return False