Ejemplo n.º 1
0
class EventPage(Page):
    date_from = models.DateField("Start date", null=True)
    date_to = models.DateField(
        "End date",
        null=True,
        blank=True,
        help_text="Not required if event is on a single day")
    time_from = models.TimeField("Start time", null=True, blank=True)
    time_to = models.TimeField("End time", null=True, blank=True)
    audience = models.CharField(max_length=255, choices=EVENT_AUDIENCE_CHOICES)
    location = models.CharField(max_length=255)
    body = RichTextField(blank=True)
    cost = models.CharField(max_length=255)
    signup_link = models.URLField(blank=True)
    feed_image = models.ForeignKey('tuiuiuimages.Image',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')
    categories = ParentalManyToManyField(EventCategory, blank=True)

    search_fields = [
        index.SearchField('get_audience_display'),
        index.SearchField('location'),
        index.SearchField('body'),
    ]

    password_required_template = 'tests/event_page_password_required.html'
    base_form_class = EventPageForm
Ejemplo n.º 2
0
    def test_overriding(self):
        # If there are two fields with the same type and name
        # the last one should override all the previous ones. This ensures that the
        # standard convention of:
        #
        #     class SpecificPageType(Page):
        #         search_fields = Page.search_fields + [some_other_definitions]
        #
        # ...causes the definitions in some_other_definitions to override Page.search_fields
        # as intended.
        cls = self.make_dummy_type([
            index.SearchField('test', boost=100, partial_match=False),
            index.SearchField('test', partial_match=True),
        ])

        self.assertEqual(len(cls.get_search_fields()), 1)
        self.assertEqual(len(cls.get_searchable_search_fields()), 1)
        self.assertEqual(len(cls.get_filterable_search_fields()), 0)

        field = cls.get_search_fields()[0]
        self.assertIsInstance(field, index.SearchField)

        # Boost should be reset to the default if it's not specified by the override
        self.assertIsNone(field.boost)

        # Check that the partial match was overridden
        self.assertTrue(field.partial_match)
Ejemplo n.º 3
0
class AbstractDocument(CollectionMember, index.Indexed, models.Model):
    title = models.CharField(max_length=255, verbose_name=_('title'))
    file = models.FileField(upload_to='documents', verbose_name=_('file'))
    created_at = models.DateTimeField(verbose_name=_('created at'), auto_now_add=True)
    uploaded_by_user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        verbose_name=_('uploaded by user'),
        null=True,
        blank=True,
        editable=False,
        on_delete=models.SET_NULL
    )

    tags = TaggableManager(help_text=None, blank=True, verbose_name=_('tags'))

    objects = DocumentQuerySet.as_manager()

    search_fields = CollectionMember.search_fields + [
        index.SearchField('title', partial_match=True, boost=10),
        index.RelatedFields('tags', [
            index.SearchField('name', partial_match=True, boost=10),
        ]),
        index.FilterField('uploaded_by_user'),
    ]

    def __str__(self):
        return self.title

    @property
    def filename(self):
        return os.path.basename(self.file.name)

    @property
    def file_extension(self):
        return os.path.splitext(self.filename)[1][1:]

    @property
    def url(self):
        return reverse('tuiuiudocs_serve', args=[self.id, self.filename])

    def get_usage(self):
        return get_object_usage(self)

    @property
    def usage_url(self):
        return reverse('tuiuiudocs:document_usage',
                       args=(self.id,))

    def is_editable_by_user(self, user):
        from tuiuiu.tuiuiudocs.permissions import permission_policy
        return permission_policy.user_has_permission_for_instance(user, 'change', self)

    class Meta:
        abstract = True
        verbose_name = _('document')
Ejemplo n.º 4
0
class EventPage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    AUDIENCE_CHOICES = (
        ('public', "Public"),
        ('private', "Private"),
    )

    date_from = models.DateField("Start date")
    date_to = models.DateField(
        "End date",
        null=True,
        blank=True,
        help_text="Not required if event is on a single day"
    )
    time_from = models.TimeField("Start time", null=True, blank=True)
    time_to = models.TimeField("End time", null=True, blank=True)
    audience = models.CharField(max_length=255, choices=AUDIENCE_CHOICES)
    location = models.CharField(max_length=255)
    body = RichTextField(blank=True)
    cost = models.CharField(max_length=255)
    signup_link = models.URLField(blank=True)
    feed_image = models.ForeignKey(
        'tuiuiuimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        'date_from',
        'date_to',
        'time_from',
        'time_to',
        'audience',
        'location',
        'body',
        'cost',
        'signup_link',
        'feed_image',
        'carousel_items',
        'related_links',
        'speakers',
    )

    search_fields = Page.search_fields + [
        index.SearchField('get_audience_display'),
        index.SearchField('location'),
        index.SearchField('body'),
    ]

    def get_event_index(self):
        # Find closest ancestor which is an event index
        return EventIndexPage.objects.ancester_of(self).last()
Ejemplo n.º 5
0
class SearchTest(index.Indexed, models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    live = models.BooleanField(default=False)
    published_date = models.DateField(null=True)
    tags = TaggableManager()

    search_fields = [
        index.SearchField('title', partial_match=True),
        index.RelatedFields('tags', [
            index.SearchField('name', partial_match=True),
            index.FilterField('slug'),
        ]),
        index.RelatedFields('subobjects', [
            index.SearchField('name', partial_match=True),
        ]),
        index.SearchField('content'),
        index.SearchField('callable_indexed_field'),
        index.FilterField('title'),
        index.FilterField('live'),
        index.FilterField('published_date'),
    ]

    def callable_indexed_field(self):
        return "Callable"

    @classmethod
    def get_indexed_objects(cls):
        indexed_objects = super(SearchTest, cls).get_indexed_objects()

        # Exclude SearchTests that have a SearchTestChild to stop update_index creating duplicates
        if cls is SearchTest:
            indexed_objects = indexed_objects.exclude(
                id__in=SearchTestChild.objects.all().values_list(
                    'searchtest_ptr_id', flat=True))

        # Exclude SearchTests that have the title "Don't index me!"
        indexed_objects = indexed_objects.exclude(title="Don't index me!")

        return indexed_objects

    def get_indexed_instance(self):
        # Check if there is a SearchTestChild that descends from this
        child = SearchTestChild.objects.filter(
            searchtest_ptr_id=self.id).first()

        # Return the child if there is one, otherwise return self
        return child or self

    def __str__(self):
        return self.title
Ejemplo n.º 6
0
class EventIndexPage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    intro = RichTextField(blank=True)

    api_fields = (
        'intro',
        'related_links',
    )

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
    ]

    def get_events(self):
        # Get list of live event pages that are descendants of this page
        events = EventPage.objects.descendant_of(self).live()

        # Filter events list to get ones that are either
        # running now or start in the future
        events = events.filter(date_from__gte=date.today())

        # Order by date
        events = events.order_by('date_from')

        return events
Ejemplo n.º 7
0
class BlogEntryPage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    body = RichTextField()
    tags = ClusterTaggableManager(through='BlogEntryPageTag', blank=True)
    date = models.DateField("Post date")
    feed_image = models.ForeignKey(
        'tuiuiuimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        APIField('body'),
        APIField('tags'),
        APIField('date'),
        APIField('feed_image'),
        APIField('feed_image_thumbnail', serializer=ImageRenditionField('fill-300x300', source='feed_image')),
        APIField('carousel_items'),
        APIField('related_links'),
    )

    search_fields = Page.search_fields + [
        index.SearchField('body'),
    ]

    def get_blog_index(self):
        # Find closest ancestor which is a blog index
        return BlogIndexPage.ancestor_of(self).last()
Ejemplo n.º 8
0
class SearchTestChild(SearchTest):
    subtitle = models.CharField(max_length=255, null=True, blank=True)
    extra_content = models.TextField()
    page = models.ForeignKey('tuiuiucore.Page',
                             null=True,
                             blank=True,
                             on_delete=models.SET_NULL)

    search_fields = SearchTest.search_fields + [
        index.SearchField('subtitle', partial_match=True),
        index.SearchField('extra_content'),
        index.RelatedFields('page', [
            index.SearchField('title', partial_match=True),
            index.SearchField('search_description'),
            index.FilterField('live'),
        ]),
    ]
Ejemplo n.º 9
0
class AnotherSearchTestChild(SearchTest):
    # Checks that having the same field name in two child models with different
    # search configuration doesn't give an error
    subtitle = models.CharField(max_length=255, null=True, blank=True)

    search_fields = SearchTest.search_fields + [
        index.SearchField('subtitle', boost=10),
    ]
Ejemplo n.º 10
0
class SearchableSnippet(index.Indexed, models.Model):
    text = models.CharField(max_length=255)

    search_fields = [
        index.SearchField('text'),
    ]

    def __str__(self):
        return self.text
Ejemplo n.º 11
0
    def test_basic(self):
        cls = self.make_dummy_type([
            index.SearchField('test', boost=100, partial_match=False),
            index.FilterField('filter_test'),
        ])

        self.assertEqual(len(cls.get_search_fields()), 2)
        self.assertEqual(len(cls.get_searchable_search_fields()), 1)
        self.assertEqual(len(cls.get_filterable_search_fields()), 1)
Ejemplo n.º 12
0
    def test_select_on_queryset_with_taggable_manager(self):
        fields = index.RelatedFields('tags', [
            index.SearchField('name'),
        ])

        queryset = fields.select_on_queryset(SearchTestChild.objects.all())

        # Tags should be prefetch_related
        self.assertIn('tags', queryset._prefetch_related_lookups)
        self.assertFalse(queryset.query.select_related)
Ejemplo n.º 13
0
    def test_select_on_queryset_with_reverse_one_to_one(self):
        fields = index.RelatedFields('searchtestchild', [
            index.SearchField('subtitle'),
        ])

        queryset = fields.select_on_queryset(SearchTest.objects.all())

        # reverse OneToOneField should be select_related
        self.assertFalse(queryset._prefetch_related_lookups)
        self.assertIn('searchtestchild', queryset.query.select_related)
Ejemplo n.º 14
0
    def test_select_on_queryset_with_reverse_foreign_key(self):
        fields = index.RelatedFields(
            'categories',
            [index.RelatedFields('category', [index.SearchField('name')])])

        queryset = fields.select_on_queryset(ManyToManyBlogPage.objects.all())

        # reverse ForeignKey should be prefetch_related
        self.assertIn('categories', queryset._prefetch_related_lookups)
        self.assertFalse(queryset.query.select_related)
Ejemplo n.º 15
0
    def test_select_on_queryset_with_many_to_many(self):
        fields = index.RelatedFields('adverts', [
            index.SearchField('title'),
        ])

        queryset = fields.select_on_queryset(ManyToManyBlogPage.objects.all())

        # ManyToManyField should be prefetch_related
        self.assertIn('adverts', queryset._prefetch_related_lookups)
        self.assertFalse(queryset.query.select_related)
Ejemplo n.º 16
0
    def test_select_on_queryset_with_foreign_key(self):
        fields = index.RelatedFields('page', [
            index.SearchField('title'),
        ])

        queryset = fields.select_on_queryset(SearchTestChild.objects.all())

        # ForeignKey should be select_related
        self.assertFalse(queryset._prefetch_related_lookups)
        self.assertIn('page', queryset.query.select_related)
Ejemplo n.º 17
0
    def test_select_on_queryset_with_reverse_many_to_many(self):
        fields = index.RelatedFields('manytomanyblogpage', [
            index.SearchField('title'),
        ])

        queryset = fields.select_on_queryset(Advert.objects.all())

        # reverse ManyToManyField should be prefetch_related
        self.assertIn('manytomanyblogpage', queryset._prefetch_related_lookups)
        self.assertFalse(queryset.query.select_related)
Ejemplo n.º 18
0
    def test_different_field_types_dont_override(self):
        # A search and filter field with the same name should be able to coexist
        cls = self.make_dummy_type([
            index.SearchField('test', boost=100, partial_match=False),
            index.FilterField('test'),
        ])

        self.assertEqual(len(cls.get_search_fields()), 2)
        self.assertEqual(len(cls.get_searchable_search_fields()), 1)
        self.assertEqual(len(cls.get_filterable_search_fields()), 1)
Ejemplo n.º 19
0
 def test_checking_search_fields(self):
     with patch_search_fields(
             models.SearchTest,
             models.SearchTest.search_fields + [index.SearchField('foo')]):
         expected_errors = [
             checks.Warning(
                 "SearchTest.search_fields contains field 'foo' but it doesn't exist",
                 obj=models.SearchTest)
         ]
         errors = models.SearchTest.check()
         self.assertEqual(errors, expected_errors)
Ejemplo n.º 20
0
class PersonPage(Page, ContactFieldsMixin):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    intro = RichTextField(blank=True)
    biography = RichTextField(blank=True)
    image = models.ForeignKey(
        'tuiuiuimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    feed_image = models.ForeignKey(
        'tuiuiuimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        'first_name',
        'last_name',
        'intro',
        'biography',
        'image',
        'feed_image',
        'related_links',
    ) + ContactFieldsMixin.api_fields

    search_fields = Page.search_fields + [
        index.SearchField('first_name'),
        index.SearchField('last_name'),
        index.SearchField('intro'),
        index.SearchField('biography'),
    ]
Ejemplo n.º 21
0
class StandardPage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    intro = RichTextField(blank=True)
    body = RichTextField(blank=True)
    feed_image = models.ForeignKey(
        'tuiuiuimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        'intro',
        'body',
        'feed_image',
        'carousel_items',
        'related_links',
    )

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
        index.SearchField('body'),
    ]
Ejemplo n.º 22
0
class HomePage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    body = RichTextField(blank=True)

    api_fields = (
        'body',
        'carousel_items',
        'related_links',
    )

    search_fields = Page.search_fields + [
        index.SearchField('body'),
    ]

    class Meta:
        verbose_name = "homepage"
Ejemplo n.º 23
0
class ContactPage(Page, ContactFieldsMixin):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    body = RichTextField(blank=True)
    feed_image = models.ForeignKey(
        'tuiuiuimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    api_fields = (
        'body',
        'feed_image',
    ) + ContactFieldsMixin.api_fields

    search_fields = Page.search_fields + [
        index.SearchField('body'),
    ]
Ejemplo n.º 24
0
class BlogIndexPage(Page):
    page_ptr = models.OneToOneField(Page, parent_link=True, related_name='+', on_delete=models.CASCADE)
    intro = RichTextField(blank=True)

    api_fields = (
        'intro',
        'related_links',
    )

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
    ]

    def get_blog_entries(self):
        # Get list of live blog pages that are descendants of this page
        entries = BlogEntryPage.objects.descendant_of(self).live()

        # Order by most recent date first
        entries = entries.order_by('-date')

        return entries

    def get_context(self, request):
        # Get blog entries
        entries = self.get_blog_entries()

        # Filter by tag
        tag = request.GET.get('tag')
        if tag:
            entries = entries.filter(tags__name=tag)

        paginator, entries = paginate(request, entries, page_key='page', per_page=10)

        # Update template context
        context = super(BlogIndexPage, self).get_context(request)
        context['entries'] = entries
        return context
Ejemplo n.º 25
0
class AbstractImage(CollectionMember, index.Indexed, models.Model):
    title = models.CharField(max_length=255, verbose_name=_('title'))
    file = models.ImageField(verbose_name=_('file'),
                             upload_to=get_upload_to,
                             width_field='width',
                             height_field='height')
    width = models.IntegerField(verbose_name=_('width'), editable=False)
    height = models.IntegerField(verbose_name=_('height'), editable=False)
    created_at = models.DateTimeField(verbose_name=_('created at'),
                                      auto_now_add=True,
                                      db_index=True)
    uploaded_by_user = models.ForeignKey(settings.AUTH_USER_MODEL,
                                         verbose_name=_('uploaded by user'),
                                         null=True,
                                         blank=True,
                                         editable=False,
                                         on_delete=models.SET_NULL)

    tags = TaggableManager(help_text=None, blank=True, verbose_name=_('tags'))

    focal_point_x = models.PositiveIntegerField(null=True, blank=True)
    focal_point_y = models.PositiveIntegerField(null=True, blank=True)
    focal_point_width = models.PositiveIntegerField(null=True, blank=True)
    focal_point_height = models.PositiveIntegerField(null=True, blank=True)

    file_size = models.PositiveIntegerField(null=True, editable=False)

    objects = ImageQuerySet.as_manager()

    def is_stored_locally(self):
        """
        Returns True if the image is hosted on the local filesystem
        """
        try:
            self.file.path

            return True
        except NotImplementedError:
            return False

    def get_file_size(self):
        if self.file_size is None:
            try:
                self.file_size = self.file.size
            except OSError:
                # File doesn't exist
                return

            self.save(update_fields=['file_size'])

        return self.file_size

    def get_upload_to(self, filename):
        folder_name = 'original_images'
        filename = self.file.field.storage.get_valid_name(filename)

        # do a unidecode in the filename and then
        # replace non-ascii characters in filename with _ , to sidestep issues with filesystem encoding
        filename = "".join(
            (i if ord(i) < 128 else '_') for i in unidecode(filename))

        # Truncate filename so it fits in the 100 character limit
        # https://code.djangoproject.com/ticket/9893
        full_path = os.path.join(folder_name, filename)
        if len(full_path) >= 95:
            chars_to_trim = len(full_path) - 94
            prefix, extension = os.path.splitext(filename)
            filename = prefix[:-chars_to_trim] + extension
            full_path = os.path.join(folder_name, filename)

        return full_path

    def get_usage(self):
        return get_object_usage(self)

    @property
    def usage_url(self):
        return reverse('tuiuiuimages:image_usage', args=(self.id, ))

    search_fields = CollectionMember.search_fields + [
        index.SearchField('title', partial_match=True, boost=10),
        index.RelatedFields('tags', [
            index.SearchField('name', partial_match=True, boost=10),
        ]),
        index.FilterField('uploaded_by_user'),
    ]

    def __str__(self):
        return self.title

    @contextmanager
    def get_willow_image(self):
        # Open file if it is closed
        close_file = False
        try:
            image_file = self.file

            if self.file.closed:
                # Reopen the file
                if self.is_stored_locally():
                    self.file.open('rb')
                else:
                    # Some external storage backends don't allow reopening
                    # the file. Get a fresh file instance. #1397
                    storage = self._meta.get_field('file').storage
                    image_file = storage.open(self.file.name, 'rb')

                close_file = True
        except IOError as e:
            # re-throw this as a SourceImageIOError so that calling code can distinguish
            # these from IOErrors elsewhere in the process
            raise SourceImageIOError(text_type(e))

        # Seek to beginning
        image_file.seek(0)

        try:
            yield WillowImage.open(image_file)
        finally:
            if close_file:
                image_file.close()

    def get_rect(self):
        return Rect(0, 0, self.width, self.height)

    def get_focal_point(self):
        if self.focal_point_x is not None and \
           self.focal_point_y is not None and \
           self.focal_point_width is not None and \
           self.focal_point_height is not None:
            return Rect.from_point(
                self.focal_point_x,
                self.focal_point_y,
                self.focal_point_width,
                self.focal_point_height,
            )

    def has_focal_point(self):
        return self.get_focal_point() is not None

    def set_focal_point(self, rect):
        if rect is not None:
            self.focal_point_x = rect.centroid_x
            self.focal_point_y = rect.centroid_y
            self.focal_point_width = rect.width
            self.focal_point_height = rect.height
        else:
            self.focal_point_x = None
            self.focal_point_y = None
            self.focal_point_width = None
            self.focal_point_height = None

    def get_suggested_focal_point(self):
        with self.get_willow_image() as willow:
            faces = willow.detect_faces()

            if faces:
                # Create a bounding box around all faces
                left = min(face[0] for face in faces)
                top = min(face[1] for face in faces)
                right = max(face[2] for face in faces)
                bottom = max(face[3] for face in faces)
                focal_point = Rect(left, top, right, bottom)
            else:
                features = willow.detect_features()
                if features:
                    # Create a bounding box around all features
                    left = min(feature[0] for feature in features)
                    top = min(feature[1] for feature in features)
                    right = max(feature[0] for feature in features)
                    bottom = max(feature[1] for feature in features)
                    focal_point = Rect(left, top, right, bottom)
                else:
                    return None

        # Add 20% to width and height and give it a minimum size
        x, y = focal_point.centroid
        width, height = focal_point.size

        width *= 1.20
        height *= 1.20

        width = max(width, 100)
        height = max(height, 100)

        return Rect.from_point(x, y, width, height)

    @classmethod
    def get_rendition_model(cls):
        """ Get the Rendition model for this Image model """
        if django.VERSION >= (1, 9):
            return cls.renditions.rel.related_model
        else:
            return cls.renditions.related.related_model

    def get_rendition(self, filter):
        if isinstance(filter, string_types):
            filter = Filter(spec=filter)

        cache_key = filter.get_cache_key(self)
        Rendition = self.get_rendition_model()

        try:
            rendition = self.renditions.get(
                filter_spec=filter.spec,
                focal_point_key=cache_key,
            )
        except Rendition.DoesNotExist:
            # Generate the rendition image
            generated_image = filter.run(self, BytesIO())

            # Generate filename
            input_filename = os.path.basename(self.file.name)
            input_filename_without_extension, input_extension = os.path.splitext(
                input_filename)

            # A mapping of image formats to extensions
            FORMAT_EXTENSIONS = {
                'jpeg': '.jpg',
                'png': '.png',
                'gif': '.gif',
            }

            output_extension = filter.spec.replace(
                '|', '.') + FORMAT_EXTENSIONS[generated_image.format_name]
            if cache_key:
                output_extension = cache_key + '.' + output_extension

            # Truncate filename to prevent it going over 60 chars
            output_filename_without_extension = input_filename_without_extension[:(
                59 - len(output_extension))]
            output_filename = output_filename_without_extension + '.' + output_extension

            rendition, created = self.renditions.get_or_create(
                filter_spec=filter.spec,
                focal_point_key=cache_key,
                defaults={
                    'file': File(generated_image.f, name=output_filename)
                })

        return rendition

    def is_portrait(self):
        return (self.width < self.height)

    def is_landscape(self):
        return (self.height < self.width)

    @property
    def filename(self):
        return os.path.basename(self.file.name)

    @property
    def default_alt_text(self):
        # by default the alt text field (used in rich text insertion) is populated
        # from the title. Subclasses might provide a separate alt field, and
        # override this
        return self.title

    def is_editable_by_user(self, user):
        from tuiuiu.tuiuiuimages.permissions import permission_policy
        return permission_policy.user_has_permission_for_instance(
            user, 'change', self)

    class Meta:
        abstract = True