Esempio n. 1
0
class Video(File):
    # the icon it will use
    _icon = "video"

    # declare the file_type for the list template
    file_type = 'Video'

    categories = CategoryManyToManyField()
    metadata = models.CharField(_('metadata'),
                                max_length=255,
                                null=True,
                                blank=True)

    @classmethod
    def matches_file_type(cls, iname, ifile, request):
        # the extensions we'll recognise for this file type
        filename_extensions = [
            '.dv',
            '.mov',
            '.mp4',
            '.avi',
            'mpeg',
            '.wmv',
            '.flv',
        ]
        ext = os.path.splitext(iname)[1].lower()
        return ext in filename_extensions

    class Meta:
        app_label = 'filer'
Esempio n. 2
0
class Course(models.Model):
    session_id = models.ForeignKey('Session')
    course_name = models.CharField(blank=False, null=False, max_length=250)
    slug = models.SlugField(unique=True)
    description = models.TextField(blank=False, null=False)
    is_workshop = models.BooleanField(default=False)
    all_day = models.BooleanField(default=False)
    start_time = models.TimeField(blank=True, null=True)
    end_time = models.TimeField(blank=True, null=True)
    days_of_week = models.CharField(max_length=1, choices=DOW, blank=True)
    custom_start_date = models.DateField(blank=True, null=True)
    custom_end_date = models.DateField(blank=True, null=True)
    is_private = models.BooleanField(default=False)
    spaces = models.IntegerField(default=15)
    instructor = models.ForeignKey('auth.User')
    image = FilerImageField(null=True, blank=True)
    price = models.IntegerField(blank=True, null=True)
    location = models.ForeignKey('location.Location', null=True)
    category = CategoryManyToManyField()
    dropins = models.BooleanField(default=True)
    dropin_price = models.IntegerField(default=20)
    ticket_link = models.CharField(blank=True, max_length=250)
    create_date = models.DateTimeField(blank=True, null=False, auto_now=True)
    publish_date = models.DateField(blank=False, default=datetime.datetime.now)
    is_active = models.BooleanField(default=True)

    def __str__(self):
        return self.course_name
Esempio n. 3
0
class NewsBlogCMSPlugin(CMSPlugin):
    """AppHookConfig aware abstract CMSPlugin class for Aldryn Newsblog"""
    # avoid reverse relation name clashes by not adding a related_name
    # to the parent plugin
    cmsplugin_ptr = models.OneToOneField(
        CMSPlugin, related_name='+', parent_link=True)

    app_config = models.ForeignKey(NewsBlogConfig, verbose_name=_('Apphook configuration'))

    dynamic_category_config = models.BooleanField(default=False, verbose_name=_('Use the ALDRYN_NEWSBLOG_CATEGORIES_FROM_REQUEST setting instead of category configuration.'))
    category_config = CategoryManyToManyField(Category, blank=True, verbose_name=_('Category configuration'), help_text=_('Will be applied if you do not use the ALDRYN_NEWSBLOG_CATEGORIES_FROM_REQUEST setting. If left empty, it will not use any filter on categories.'))

    class Meta:
        abstract = True

    def copy_relations(self, old_instance):
        self.app_config = old_instance.app_config
Esempio n. 4
0
class NewsBlogLatestArticleByCategory(NewsBlogCMSPlugin):
    latest_articles = models.IntegerField(
        default=5,
        help_text=_('The maximum number of latest articles to display.'))
    categories = CategoryManyToManyField('aldryn_categories.Category',
                                         verbose_name=_('categories'),
                                         blank=True)

    # copy manytomany and foreignkey relationship from edited plugin to published plugin
    def copy_relations(self, oldinstance):
        self.categories = oldinstance.categories.all()

    def get_articles(self, request):
        """
        Returns a queryset of the latest N articles in one or more of the M categories.
        N is the plugin setting: latest_articles.
        M is the plugin setting: categories
        """
        queryset = Article.objects.published()
        queryset = queryset.all().filter(categories__in=self.categories.all())

        if not self.latest_articles:
            return Article.objects.none()
        languages = get_valid_languages_from_request(self.app_config.namespace,
                                                     request)
        if self.language not in languages:
            return queryset.none()
        queryset = queryset.translated(*languages).filter(
            app_config=self.app_config)

        return queryset[:self.latest_articles]

    def __str__(self):
        return ugettext(
            '%(app_title)s latest articles by category: %(latest_articles)s'
        ) % {
            'app_title': self.app_config.get_app_title(),
            'latest_articles': self.latest_articles,
        }
Esempio n. 5
0
class NewsBlogCategoryRelatedPlugin(PluginEditModeMixin, PluginStyleMixin,
                                    AdjustableCacheModelMixin, CMSPlugin):
    # NOTE: This one does NOT subclass NewsBlogCMSPlugin. This is because this
    # plugin can really only be placed on the article detail view in an apphook.

    exclude_categories = CategoryManyToManyField(
        verbose_name=_('excluded categories'), blank=True)
    article_count = models.PositiveIntegerField(
        default=10,
        help_text=_(
            'The maximum number of related articles to display (0 for all).'),
    )

    def get_queryset(self, article, request):
        """
        Returns a queryset of articles that have common categories with the given article.
        """
        languages = get_valid_languages_from_request(
            article.app_config.namespace, request)
        if self.language not in languages:
            return Article.objects.none()
        filter_categories = article.categories.exclude(
            pk__in=models.Subquery(self.exclude_categories.values('pk')))
        queryset = Article.objects.filter(
            categories__in=filter_categories).exclude(
                pk=article.pk).translated(*languages)
        if not self.get_edit_mode(request):
            queryset = queryset.published()
        return queryset.distinct()

    def get_articles(self, article, request):
        queryset = self.get_queryset(article, request)
        if self.article_count > 0:
            queryset = queryset[:self.article_count]
        return queryset

    def __str__(self):
        return ugettext('Category-related articles')
Esempio n. 6
0
class Article(TranslatedAutoSlugifyMixin, TranslationHelperMixin,
              TranslatableModel):

    # TranslatedAutoSlugifyMixin options
    slug_source_field_name = 'title'
    slug_default = _('untitled-article')
    # when True, updates the article's search_data field
    # whenever the article is saved or a plugin is saved
    # on the article's content placeholder.
    update_search_on_save = getattr(
        settings, 'ALDRYN_NEWSBLOG_UPDATE_SEARCH_DATA_ON_SAVE', False)

    translations = TranslatedFields(
        title=models.CharField(_('title'), max_length=234),
        slug=models.SlugField(
            verbose_name=_('slug'),
            max_length=255,
            db_index=True,
            blank=True,
            help_text=_('Used in the URL. If changed, the URL will change. '
                        'Clear it to have it re-created automatically.'),
        ),
        lead_in=HTMLField(
            verbose_name=_('lead'),
            default='',
            help_text=_(
                'The lead gives the reader the main idea of the story, this '
                'is useful in overviews, lists or as an introduction to your '
                'article.'),
            blank=True,
        ),
        meta_title=models.CharField(max_length=255,
                                    verbose_name=_('meta title'),
                                    blank=True,
                                    default=''),
        meta_description=models.TextField(verbose_name=_('meta description'),
                                          blank=True,
                                          default=''),
        meta_keywords=models.TextField(verbose_name=_('meta keywords'),
                                       blank=True,
                                       default=''),
        meta={'unique_together': ((
            'language_code',
            'slug',
        ), )},
        search_data=models.TextField(blank=True, editable=False))

    content = PlaceholderField('newsblog_article_content',
                               related_name='newsblog_article_content')
    author = models.ForeignKey(Person,
                               null=True,
                               blank=True,
                               verbose_name=_('author'))
    owner = models.ForeignKey(settings.AUTH_USER_MODEL,
                              verbose_name=_('owner'))
    app_config = AppHookConfigField(NewsBlogConfig,
                                    verbose_name=_('Apphook configuration'))
    categories = CategoryManyToManyField('aldryn_categories.Category',
                                         verbose_name=_('categories'),
                                         blank=True)
    publishing_date = models.DateTimeField(_('publishing date'), default=now)
    is_published = models.BooleanField(_('is published'),
                                       default=False,
                                       db_index=True)
    is_featured = models.BooleanField(_('is featured'),
                                      default=False,
                                      db_index=True)
    featured_image = FilerImageField(
        verbose_name=_('featured image'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    tags = TaggableManager(blank=True)

    # Setting "symmetrical" to False since it's a bit unexpected that if you
    # set "B relates to A" you immediately have also "A relates to B". It have
    # to be forced to False because by default it's True if rel.to is "self":
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L2144
    #
    # which in the end causes to add reversed releted-to entry as well:
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L977
    related = SortedManyToManyField('self',
                                    verbose_name=_('related articles'),
                                    blank=True,
                                    symmetrical=False)

    objects = RelatedManager()

    class Meta:
        ordering = ['-publishing_date']

    @property
    def published(self):
        """
        Returns True only if the article (is_published == True) AND has a
        published_date that has passed.
        """
        return (self.is_published and self.publishing_date <= now())

    @property
    def future(self):
        """
        Returns True if the article is published but is scheduled for a
        future date/time.
        """
        return (self.is_published and self.publishing_date > now())

    def get_absolute_url(self, language=None):
        """Returns the url for this Article in the selected permalink format."""
        if not language:
            language = get_current_language()
        kwargs = {}
        permalink_type = self.app_config.permalink_type
        if 'y' in permalink_type:
            kwargs.update(year=self.publishing_date.year)
        if 'm' in permalink_type:
            kwargs.update(month="%02d" % self.publishing_date.month)
        if 'd' in permalink_type:
            kwargs.update(day="%02d" % self.publishing_date.day)
        if 'i' in permalink_type:
            kwargs.update(pk=self.pk)
        if 's' in permalink_type:
            slug, lang = self.known_translation_getter('slug',
                                                       default=None,
                                                       language_code=language)
            if slug and lang:
                site_id = getattr(settings, 'SITE_ID', None)
                if get_redirect_on_fallback(language, site_id):
                    language = lang
                kwargs.update(slug=slug)

        if self.app_config and self.app_config.namespace:
            namespace = '{0}:'.format(self.app_config.namespace)
        else:
            namespace = ''

        with override(language):
            return reverse('{0}article-detail'.format(namespace),
                           kwargs=kwargs)

    def get_search_data(self, language=None, request=None):
        """
        Provides an index for use with Haystack, or, for populating
        Article.translations.search_data.
        """
        if not self.pk:
            return ''
        if language is None:
            language = get_current_language()
        if request is None:
            request = get_request(language=language)
        description = self.safe_translation_getter('lead_in', '')
        text_bits = [strip_tags(description)]
        for category in self.categories.all():
            text_bits.append(
                force_unicode(category.safe_translation_getter('name')))
        for tag in self.tags.all():
            text_bits.append(force_unicode(tag.name))
        if self.content:
            plugins = self.content.cmsplugin_set.filter(language=language)
            for base_plugin in plugins:
                plugin_text_content = ' '.join(
                    get_plugin_index_data(base_plugin, request))
                text_bits.append(plugin_text_content)
        return ' '.join(text_bits)

    def save(self, *args, **kwargs):
        # Update the search index
        if self.update_search_on_save:
            self.search_data = self.get_search_data()

        # Ensure there is an owner.
        if self.app_config.create_authors and self.author is None:
            self.author = Person.objects.get_or_create(
                user=self.owner,
                defaults={
                    'name':
                    u' '.join((self.owner.first_name, self.owner.last_name))
                })[0]
        # slug would be generated by TranslatedAutoSlugifyMixin
        super(Article, self).save(*args, **kwargs)

    def __str__(self):
        return self.safe_translation_getter('title', any_language=True)
Esempio n. 7
0
class Service(CustomServiceMixin,
              TranslatedAutoSlugifyMixin,
              TranslationHelperMixin,
              TranslatableModel):

    # TranslatedAutoSlugifyMixin options
    slug_source_field_name = 'title'
    slug_default = _('untitled-service')
    # when True, updates the service's search_data field
    # whenever the service is saved or a plugin is saved
    # on the service's content placeholder.
    update_search_on_save = getattr(
        settings,
        'SERVICES_UPDATE_SEARCH_DATA_ON_SAVE',
        False
    )

    translations = TranslatedFields(
        title=models.CharField(_('title'), max_length=234),
        slug=models.SlugField(
            verbose_name=_('slug'),
            max_length=255,
            db_index=True,
            blank=True,
            help_text=_(
                'Used in the URL. If changed, the URL will change. '
                'Clear it to have it re-created automatically.'),
        ),
        lead_in=HTMLField(
            verbose_name=_('Summary'), default='',
            help_text=_(
                'The Summary gives the reader the main idea of the story, this '
                'is useful in overviews, lists or as an introduction to your '
                'service.'
            ),
            blank=True,
        ),
        meta_title=models.CharField(
            max_length=255, verbose_name=_('meta title'),
            blank=True, default=''),
        meta_description=models.TextField(
            verbose_name=_('meta description'), blank=True, default=''),
        meta_keywords=models.TextField(
            verbose_name=_('meta keywords'), blank=True, default=''),
        meta={'unique_together': (('language_code', 'slug', ), )},
        search_data=models.TextField(blank=True, editable=False),
        is_published_trans = models.BooleanField(_('is published'),
            default=False, db_index=True),
        is_featured_trans = models.BooleanField(_('is featured'),
            default=False, db_index=True),
        layout_trans = models.CharField(
            blank=True,
            default='',
            max_length=60,
            verbose_name=_('layout')
        ),
    )

    content = PlaceholderField('service_content',
                               related_name='service_content')
    sidebar = PlaceholderField('service_sidebar',
                               related_name='service_sidebar')
    related_articles_placeholder = PlaceholderField('service_related_articles',
                               related_name='service_related_articles')
    banner = PlaceholderField('service_banner',
                               related_name='service_banner')
    sections = models.ManyToManyField(
        ServicesConfig,
        verbose_name=_('Sections'),
        related_name='services',
    )
    categories = CategoryManyToManyField(Category,
                                         verbose_name=_('categories'),
                                         blank=True)
    publishing_date = models.DateTimeField(_('publishing date'),
                                           default=now)
    is_published = models.BooleanField(_('is published'), default=False,
                                       db_index=True)
    is_featured = models.BooleanField(_('is featured'), default=False,
                                      db_index=True)
    featured_image = FilerImageField(
        verbose_name=_('featured image'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    show_on_sitemap = models.BooleanField(_('Show on sitemap'), null=False, default=True)
    show_on_xml_sitemap = models.BooleanField(_('Show on xml sitemap'), null=False, default=True)
    noindex = models.BooleanField(_('noindex'), null=False, default=False)
    nofollow = models.BooleanField(_('nofollow'), null=False, default=False)
    canonical_url = models.CharField(
        blank=True,
        null=True,
        max_length=255,
        verbose_name=_('Canonical URL')
    )
    layout = models.CharField(
        blank=True,
        default='',
        max_length=60,
        verbose_name=_('layout')
    )
    custom_fields = JSONField(blank=True, null=True)

    # Setting "symmetrical" to False since it's a bit unexpected that if you
    # set "B relates to A" you immediately have also "A relates to B". It have
    # to be forced to False because by default it's True if rel.to is "self":
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L2144
    #
    # which in the end causes to add reversed releted-to entry as well:
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L977
    related = SortedManyToManyField('self', verbose_name=_('related services'),
                                    blank=True, symmetrical=True)

    objects = RelatedManager()

    class Meta:
        ordering = ['-publishing_date']

    def get_class(self):
        '''Return class name'''
        return self.__class__.__name__

    @cached_property
    def app_config(self):
        app_config = ServicesConfig.objects.get(namespace=ServicesConfig.default_namespace)
        if self.pk:
            for section in self.sections.all():
                try:
                    reverse('{0}:service-list'.format(section.namespace))
                except:
                    pass
                else:
                    app_config = section
                    break
        return app_config

    @property
    def type(self):
        '''Service Type / Section.'''
        return self.app_config

    @property
    def type_slug(self):
        '''Service Type / Section Machine Name'''
        return self.app_config.namespace

    @property
    def published(self):
        """
        Returns True only if the service (is_published == True) AND has a
        published_date that has passed.
        """
        language = get_current_language()
        return self.published_for_language(language)

    def published_for_language(self, language):
        is_published = self.is_published
        if TRANSLATE_IS_PUBLISHED:
            is_published = self.safe_translation_getter('is_published_trans', language_code=language, any_language=False) or False
        if SERVICES_ENABLE_PUBDATE:
            return (is_published and self.publishing_date <= now())
        return is_published

    @property
    def future(self):
        """
        Returns True if the service is published but is scheduled for a
        future date/time.
        """
        return (self.is_published and self.publishing_date > now())

    def get_absolute_url(self, language=None):
        """Returns the url for this Service in the selected permalink format."""
        if not language:
            language = get_current_language()
        kwargs = {}
        permalink_type = self.app_config.permalink_type
        if 'y' in permalink_type:
            kwargs.update(year=self.publishing_date.year)
        if 'm' in permalink_type:
            kwargs.update(month="%02d" % self.publishing_date.month)
        if 'd' in permalink_type:
            kwargs.update(day="%02d" % self.publishing_date.day)
        if 'i' in permalink_type:
            kwargs.update(pk=self.pk)
        if 's' in permalink_type:
            slug, lang = self.known_translation_getter(
                'slug', default=None, language_code=language)
            if slug and lang:
                site_id = getattr(settings, 'SITE_ID', None)
                if get_redirect_on_fallback(language, site_id):
                    language = lang
                kwargs.update(slug=slug)

        if self.app_config and self.app_config.namespace:
            namespace = '{0}:'.format(self.app_config.namespace)
        else:
            namespace = ServicesConfig.default_namespace

        with override(language):
            return reverse('{0}service-detail'.format(namespace), kwargs=kwargs)

    def get_public_url(self, language=None):
        if not language:
            language = get_current_language()
        if not TRANSLATE_IS_PUBLISHED and self.published:
            return self.get_absolute_url(language)
        if (TRANSLATE_IS_PUBLISHED and \
                (self.safe_translation_getter('is_published_trans', language_code=language, any_language=False) or False) and \
                self.publishing_date <= now()):
            return self.get_absolute_url(language)
        return ''

    def get_search_data(self, language=None, request=None):
        """
        Provides an index for use with Haystack, or, for populating
        Service.translations.search_data.
        """
        if not self.pk:
            return ''
        if language is None:
            language = get_current_language()
        if request is None:
            request = get_request(language=language)
        title = self.safe_translation_getter('title', '')
        description = self.safe_translation_getter('lead_in', '')
        text_bits = [title, strip_tags(description)]
        if ADD_CATEGORIES_TO_SEARCH_DATA:
            for category in self.categories.all():
                text_bits.append(
                    force_unicode(category.safe_translation_getter('name')))
        if self.content:
            plugins = self.content.cmsplugin_set.filter(language=language)
            for base_plugin in plugins:
                plugin_text_content = ' '.join(
                    get_plugin_index_data(base_plugin, request))
                text_bits.append(plugin_text_content)
        return ' '.join(text_bits)

    def save(self, *args, **kwargs):
        # Update the search index
        if self.update_search_on_save:
            self.search_data = self.get_search_data()
        if hasattr(self, 'app_config'):
            delattr(self, 'app_config')
        # slug would be generated by TranslatedAutoSlugifyMixin
        super(Service, self).save(*args, **kwargs)

    def __str__(self):
        return self.safe_translation_getter('title', any_language=True) if self.pk else ''

    def get_placeholders(self):
        return [
            self.content,
            self.sidebar,
            self.related_articles_placeholder,
            self.banner,
        ]

    def services_by_category(self, category=None):
        if category:
            categories = Category.objects.filter(translations__slug=category)
            return Service.objects.published().filter(categories__in=categories[0].get_descendants()).distinct().exclude(pk=self.pk) if categories.count() else []
        return Service.objects.published().exclude(pk=self.pk)

    def related_categories(self, category=None):
        if category:
            categories = Category.objects.filter(translations__slug=category)
            return categories[0].get_descendants().filter(pk__in=self.categories.all()) if categories.count() else []
        return self.categories.all()

    def related_articles(self, article_category=None):
        articles = self.article_set.published()
        if article_category:
            return articles.namespace(article_category)
        return articles

    def services(self, service_category=None):
        if service_category:
            return Service.objects.published().filter(sections__namespace=service_category)
        return Service.objects.published()

    def people(self):
        return Person.objects.published().filter(services=self)

    def related_people(self):
        return Person.objects.published().filter(services__in=self.related.all()).distinct()


    def __getattr__(cls, name):
        if not hasattr(Service, name):
            if name.startswith('related_articles_'):
                category = name.split('related_articles_')[1].replace('_', '-')
                def wrapper(self):
                    return self.related_articles(category)
                setattr(Service, name, wrapper)
                return getattr(cls, name)
            elif name.startswith('services_by_category_'):
                category = name.split('services_by_category_')[1].replace('_', '-')
                def wrapper(self):
                    return self.services_by_category(category)
                setattr(Service, name, wrapper)
                return getattr(cls, name)
            elif name.startswith('related_categories_'):
                category = name.split('related_categories_')[1].replace('_', '-')
                def wrapper(self):
                    return self.related_categories(category)
                setattr(Service, name, wrapper)
                return getattr(cls, name)
            elif name.startswith('services_'):
                category = name.split('services_')[1].replace('_', '-')
                def wrapper(self):
                    return self.services(category)
                setattr(Service, name, wrapper)
                return getattr(cls, name)
        raise AttributeError
Esempio n. 8
0
class Article(CustomArticleMixin, TranslatedAutoSlugifyMixin,
              TranslationHelperMixin, TranslatableModel):

    # TranslatedAutoSlugifyMixin options
    slug_source_field_name = 'title'
    slug_default = _('untitled-article')
    # when True, updates the article's search_data field
    # whenever the article is saved or a plugin is saved
    # on the article's content placeholder.
    update_search_on_save = getattr(
        settings,
        'ALDRYN_NEWSBLOG_UPDATE_SEARCH_DATA_ON_SAVE', False) or getattr(
            settings, 'ALDRYN_NEWSBLOG_AUTO_CALCULATE_READ_TIME', False)

    translations = TranslatedFields(
        title=models.CharField(_('title'), max_length=234),
        slug=models.SlugField(
            verbose_name=_('slug'),
            max_length=255,
            db_index=True,
            blank=True,
            help_text=_('Used in the URL. If changed, the URL will change. '
                        'Clear it to have it re-created automatically.'),
        ),
        lead_in=HTMLField(
            verbose_name=_('Summary'),
            default='',
            help_text=_(
                'The Summary gives the reader the main idea of the story, this '
                'is useful in overviews, lists or as an introduction to your '
                'article.'),
            blank=True,
        ),
        read_time=models.CharField(max_length=255,
                                   verbose_name=_('Read time'),
                                   blank=True,
                                   default=''),
        meta_title=models.CharField(max_length=255,
                                    verbose_name=_('meta title'),
                                    blank=True,
                                    default=''),
        meta_description=models.TextField(verbose_name=_('meta description'),
                                          blank=True,
                                          default=''),
        meta_keywords=models.TextField(verbose_name=_('meta keywords'),
                                       blank=True,
                                       default=''),
        meta={'unique_together': ((
            'language_code',
            'slug',
        ), )},
        search_data=models.TextField(blank=True, editable=False),
        author_trans=models.ForeignKey(Person,
                                       on_delete=models.SET_NULL,
                                       related_name='articles_trans',
                                       null=True,
                                       blank=True,
                                       verbose_name=_('author')),
        author_2_trans=models.ForeignKey(Person,
                                         on_delete=models.SET_NULL,
                                         related_name='articles_trans_2',
                                         null=True,
                                         blank=True,
                                         verbose_name=_('second author')),
        author_3_trans=models.ForeignKey(Person,
                                         on_delete=models.SET_NULL,
                                         related_name='articles_trans_3',
                                         null=True,
                                         blank=True,
                                         verbose_name=_('third author')),
        is_published_trans=models.BooleanField(_('is published'),
                                               default=False,
                                               db_index=True),
        is_featured_trans=models.BooleanField(_('is featured'),
                                              default=False,
                                              db_index=True),
    )

    content = PlaceholderField('newsblog_article_content',
                               related_name='newsblog_article_content')
    related_articles = PlaceholderField(
        'newsblog_related_articles', related_name='newsblog_related_articles')
    article_carousel = PlaceholderField(
        'newsblog_article_carousel', related_name='newsblog_article_carousel')
    article_sidebar = PlaceholderField('newsblog_article_sidebar',
                                       related_name='newsblog_article_sidebar')
    hide_authors = models.BooleanField(
        _('Hide Authors'),
        default=False,
    )
    author = models.ForeignKey(Person,
                               on_delete=models.SET_NULL,
                               null=True,
                               blank=True,
                               verbose_name=_('author'))
    author_2 = models.ForeignKey(Person,
                                 on_delete=models.SET_NULL,
                                 related_name='author_2',
                                 null=True,
                                 blank=True,
                                 verbose_name=_('second author'))
    author_3 = models.ForeignKey(Person,
                                 on_delete=models.SET_NULL,
                                 related_name='author_3',
                                 null=True,
                                 blank=True,
                                 verbose_name=_('third author'))
    owner = models.ForeignKey(settings.AUTH_USER_MODEL,
                              on_delete=models.SET_NULL,
                              verbose_name=_('owner'),
                              null=True,
                              blank=True)
    app_config = AppHookConfigField(
        NewsBlogConfig,
        on_delete=models.CASCADE,
        verbose_name=_('Section'),
        help_text='',
    )
    locations = SortedManyToManyField('js_locations.location',
                                      verbose_name=_('locations'),
                                      blank=True)
    categories = CategoryManyToManyField('aldryn_categories.Category',
                                         verbose_name=_('categories'),
                                         blank=True)
    services = SortedManyToManyField('js_services.Service',
                                     verbose_name=_('services'),
                                     blank=True)
    feeds = models.ManyToManyField(NewsBlogFeed,
                                   verbose_name=_('feeds'),
                                   blank=True)
    publishing_date = models.DateTimeField(_('publishing date'), default=now)
    is_published = models.BooleanField(_('is published'),
                                       default=False,
                                       db_index=True)
    is_featured = models.BooleanField(_('is featured'),
                                      default=False,
                                      db_index=True)
    featured_image = FilerImageField(
        verbose_name=_('featured image'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    share_image = FilerImageField(
        verbose_name=_('social share image'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        help_text=
        'This image will only be shown on social channels. Minimum size: 1200x630px',
        related_name='+')
    logo_image = FilerImageField(verbose_name=_('logo image'),
                                 null=True,
                                 blank=True,
                                 on_delete=models.SET_NULL,
                                 related_name='+')
    svg_image = FilerFileField(
        verbose_name=_('logo SVG'),
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    medium = models.ForeignKey(ArticleMedium,
                               on_delete=models.SET_NULL,
                               verbose_name=_('medium'),
                               null=True,
                               blank=True)

    show_on_sitemap = models.BooleanField(_('Show on sitemap'),
                                          null=False,
                                          default=True)
    show_on_xml_sitemap = models.BooleanField(_('Show on xml sitemap'),
                                              null=False,
                                              default=True)
    noindex = models.BooleanField(_('noindex'), null=False, default=False)
    nofollow = models.BooleanField(_('nofollow'), null=False, default=False)
    canonical_url = models.CharField(blank=True,
                                     null=True,
                                     max_length=255,
                                     verbose_name=_('Canonical URL'))

    layout = models.CharField(blank=True,
                              default='',
                              max_length=60,
                              verbose_name=_('layout'))
    custom_fields = JSONField(blank=True, null=True)
    # Setting "symmetrical" to False since it's a bit unexpected that if you
    # set "B relates to A" you immediately have also "A relates to B". It have
    # to be forced to False because by default it's True if rel.to is "self":
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L2144
    #
    # which in the end causes to add reversed releted-to entry as well:
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L977
    related = SortedManyToManyField('self',
                                    verbose_name=_('specific articles'),
                                    blank=True,
                                    symmetrical=False)

    objects = RelatedManager()
    all_objects = AllManager()
    search_objects = SearchManager()

    class Meta:
        ordering = ['-publishing_date']

    def get_class(self):
        '''Return class name'''
        return self.__class__.__name__

    @property
    def type(self):
        '''Article Type / Section.'''
        return self.app_config

    @cached_property
    def cached_type(self):
        '''Article Type / Section.'''
        return self.app_config

    @property
    def type_slug(self):
        '''Article Type / Section Machine Name'''
        return self.app_config.namespace

    @property
    def published(self):
        """
        Returns True only if the article (is_published == True) AND has a
        published_date that has passed.
        """
        language = get_current_language()
        return self.published_for_language(language)

    def published_for_language(self, language):
        if TRANSLATE_IS_PUBLISHED:
            return ((self.safe_translation_getter('is_published_trans',
                                                  language_code=language,
                                                  any_language=False) or False)
                    and self.publishing_date <= now())
        return (self.is_published and self.publishing_date <= now())

    @property
    def authors(self):
        authors = []
        if TRANSLATE_AUTHORS:
            if self.author_trans and self.author_trans.published:
                authors.append(self.author_trans)
            if self.author_2_trans and self.author_2_trans.published:
                authors.append(self.author_2_trans)
            if self.author_3_trans and self.author_3_trans.published:
                authors.append(self.author_3_trans)
        else:
            if self.author and self.author.published:
                authors.append(self.author)
            if self.author_2 and self.author_2.published:
                authors.append(self.author_2)
            if self.author_3 and self.author_3.published:
                authors.append(self.author_3)
        return authors

    @property
    def future(self):
        """
        Returns True if the article is published but is scheduled for a
        future date/time.
        """
        return (self.is_published and self.publishing_date > now())

    def get_absolute_url(self, language=None):
        """Returns the url for this Article in the selected permalink format."""
        if not language:
            language = get_current_language()
        kwargs = {}
        permalink_type = self.cached_type.permalink_type
        if 'y' in permalink_type:
            kwargs.update(year=self.publishing_date.year)
        if 'm' in permalink_type:
            kwargs.update(month="%02d" % self.publishing_date.month)
        if 'd' in permalink_type:
            kwargs.update(day="%02d" % self.publishing_date.day)
        if 'i' in permalink_type:
            kwargs.update(pk=self.pk)
        if 's' in permalink_type:
            slug, lang = self.known_translation_getter('slug',
                                                       default=None,
                                                       language_code=language)
            if slug and lang:
                site_id = getattr(settings, 'SITE_ID', None)
                if get_redirect_on_fallback(language, site_id):
                    language = lang
                kwargs.update(slug=slug)

        if self.cached_type.namespace:
            namespace = self.cached_type.namespace
            try:
                reverse('{0}:article-list'.format(namespace))
            except:
                namespace = NewsBlogConfig.default_namespace
        else:
            namespace = NewsBlogConfig.default_namespace

        with override(language):
            return reverse('{0}:article-detail'.format(namespace),
                           kwargs=kwargs)

    def get_public_url(self, language=None):
        if not language:
            language = get_current_language()
        if not TRANSLATE_IS_PUBLISHED and self.published:
            return self.get_absolute_url(language)
        if (TRANSLATE_IS_PUBLISHED and \
                (self.safe_translation_getter('is_published_trans', language_code=language, any_language=False) or False) and \
                self.publishing_date <= now()):
            return self.get_absolute_url(language)
        return ''

    def get_search_data(self, language=None, request=None):
        """
        Provides an index for use with Haystack, or, for populating
        Article.translations.search_data.
        """
        if not self.pk:
            return ''
        if language is None:
            language = get_current_language()
        if request is None:
            request = get_request(language=language)
        title = self.safe_translation_getter('title', '')
        description = self.safe_translation_getter('lead_in', '')
        text_bits = [title, strip_tags(description)]
        for category in self.categories.all():
            text_bits.append(
                force_unicode(category.safe_translation_getter('name')))
        for service in self.services.all():
            text_bits.append(
                force_unicode(service.safe_translation_getter('title')))
        text_bits.append('=c=o=n=t=e=n=t=')
        if self.content:
            plugins = self.content.cmsplugin_set.filter(language=language)
            for base_plugin in plugins:
                plugin_text_content = ' '.join(
                    get_plugin_index_data(base_plugin, request))
                text_bits.append(plugin_text_content)
        return ' '.join(text_bits)

    def save(self, *args, **kwargs):
        # Update the search index
        if self.update_search_on_save:
            self.search_data = self.get_search_data()
            auto_read_time = getattr(
                settings, 'ALDRYN_NEWSBLOG_AUTO_CALCULATE_READ_TIME', False)
            if callable(auto_read_time):
                auto_read_time = auto_read_time(self)
            if auto_read_time and self.app_config.auto_read_time:
                read_time = self.get_read_time()
                if read_time:
                    self.read_time = read_time
        # Ensure there is an owner.
        if self.app_config.create_authors and self.owner and self.author is None:
            if hasattr(Person, 'first_name') and hasattr(Person, 'last_name'):
                defaults = {
                    'first_name': self.owner.first_name,
                    'last_name': self.owner.last_name,
                }
            else:
                defaults = {
                    'name':
                    ' '.join((
                        self.owner.first_name,
                        self.owner.last_name,
                    )),
                }
            self.author = Person.objects.get_or_create(user=self.owner,
                                                       defaults=defaults)[0]
        # slug would be generated by TranslatedAutoSlugifyMixin
        #if not self.medium:
        #self.medium = ArticleMedium.objects.first()
        super(Article, self).save(*args, **kwargs)

    def get_read_time(self):
        if '=c=o=n=t=e=n=t=' in self.search_data:
            read_time_function = getattr(
                settings, 'ALDRYN_NEWSBLOG_READ_TIME_FUNCTION',
                lambda x: x // 200 + (0 if x % 200 == 0 else 1))
            return read_time_function(
                len(self.search_data.split('=c=o=n=t=e=n=t=')[1].split()))

    def get_placeholders(self):
        return [
            self.content, self.related_articles, self.article_carousel,
            self.article_sidebar
        ]

    def __str__(self):
        return self.safe_translation_getter('title', any_language=True)

    def get_related_articles_by_services(self, article_category=None):
        articles = self.__class__.objects.published().filter(
            services__in=self.services.all()).distinct().exclude(id=self.id)
        if article_category:
            return articles.namespace(article_category)
        return articles

    def get_related_articles_by_categories(self, article_category=None):
        articles = self.__class__.objects.published().filter(
            categories__in=self.categories.all()).distinct().exclude(
                id=self.id)
        if article_category:
            return articles.namespace(article_category)
        return articles

    def related_articles_by_services(self):
        return self.get_related_articles_by_services()

    def related_articles_by_categories(self):
        return self.get_related_articles_by_categories()

    def related_articles_same_type_by_services(self):
        return self.get_related_articles_by_services(self.app_config.namespace)

    def related_articles_same_type_by_categories(self):
        return self.get_related_articles_by_categories(
            self.app_config.namespace)
Esempio n. 9
0
class Event(CustomEventMixin, TranslatedAutoSlugifyMixin,
            TranslationHelperMixin, TranslatableModel):

    # TranslatedAutoSlugifyMixin options
    slug_source_field_name = 'title'
    slug_default = _('untitled-event')
    # when True, updates the event's search_data field
    # whenever the event is saved or a plugin is saved
    # on the event's content placeholder.
    update_search_on_save = getattr(settings,
                                    'EVENTS_UPDATE_SEARCH_DATA_ON_SAVE', False)

    translations = TranslatedFields(
        title=models.CharField(_('title'), max_length=234),
        slug=models.SlugField(
            verbose_name=_('slug'),
            max_length=255,
            db_index=True,
            blank=True,
            help_text=_('Used in the URL. If changed, the URL will change. '
                        'Clear it to have it re-created automatically.'),
        ),
        lead_in=HTMLField(
            verbose_name=_('Summary'),
            default='',
            help_text=_(
                'The Summary gives the reader the main idea of the story, this '
                'is useful in overviews, lists or as an introduction to your '
                'event.'),
            blank=True,
        ),
        location=HTMLField(
            verbose_name=_('Location'),
            default='',
            blank=True,
        ),
        display_location=models.CharField(
            _('Display Location'),
            max_length=255,
            null=True,
            blank=True,
        ),
        meta_title=models.CharField(max_length=255,
                                    verbose_name=_('meta title'),
                                    blank=True,
                                    default=''),
        meta_description=models.TextField(verbose_name=_('meta description'),
                                          blank=True,
                                          default=''),
        meta_keywords=models.TextField(verbose_name=_('meta keywords'),
                                       blank=True,
                                       default=''),
        meta={'unique_together': ((
            'language_code',
            'slug',
        ), )},
        search_data=models.TextField(blank=True, editable=False),
        is_published_trans=models.BooleanField(_('is published'),
                                               default=False,
                                               db_index=True),
        is_featured_trans=models.BooleanField(_('is featured'),
                                              default=False,
                                              db_index=True),
    )

    price = models.CharField(max_length=255,
                             verbose_name=_('Event Price'),
                             blank=True,
                             default='')
    cpd_points = models.CharField(max_length=255,
                                  verbose_name=_('CPD Points'),
                                  blank=True,
                                  default='')
    event_start = models.DateTimeField(_('Event start'), default=now)
    event_end = models.DateTimeField(_('Event end'), null=True, blank=True)
    latitude = models.DecimalField(max_digits=8,
                                   decimal_places=5,
                                   verbose_name=_('Event latitude'),
                                   blank=True,
                                   null=True)
    longitude = models.DecimalField(max_digits=8,
                                    decimal_places=5,
                                    verbose_name=_('Event longitude'),
                                    blank=True,
                                    null=True)
    host = models.ForeignKey(Person,
                             on_delete=models.SET_NULL,
                             null=True,
                             blank=True,
                             verbose_name=_('host'))
    host_2 = models.ForeignKey(Person,
                               on_delete=models.SET_NULL,
                               related_name='host_2',
                               null=True,
                               blank=True,
                               verbose_name=_('second host'))
    host_3 = models.ForeignKey(Person,
                               on_delete=models.SET_NULL,
                               related_name='host_3',
                               null=True,
                               blank=True,
                               verbose_name=_('third host'))
    registration_until = models.DateTimeField(_('Allow registration until'),
                                              blank=True,
                                              null=True)
    registration_content = PlaceholderField(
        'Hide After Happened',
        related_name='events_event_registration_content')
    sidebar = PlaceholderField('Event Sidebar',
                               related_name='events_event_sidebar')
    registration_link = models.CharField(
        max_length=255,
        verbose_name=_('Registration link'),
        blank=True,
        default='',
        help_text=_('link to an external registration system'),
    )
    external_link = models.CharField(
        max_length=255,
        verbose_name=_('External link'),
        blank=True,
        default='',
        help_text=_('link to an external registration system'),
    )
    link_text = models.CharField(
        max_length=255,
        verbose_name=_('Link Text'),
        blank=True,
        default='',
        help_text=_(
            'Text to appear on either the Registration Link or External Link'),
    )
    redirect_url = models.CharField(
        max_length=255,
        verbose_name=_('Redirect URL'),
        blank=True,
        default='',
        help_text=
        _('when this value is filled in the Event page does not load, it redirects to the entered url'
          ),
    )

    content = PlaceholderField('Event Content',
                               related_name='events_event_content')
    app_config = AppHookConfigField(
        EventsConfig,
        verbose_name=_('Section'),
        help_text='',
    )
    template = models.CharField(
        max_length=255,
        verbose_name=_('Event template'),
        blank=True,
        default='',
    )
    channel = models.ForeignKey(Channel,
                                on_delete=models.SET_NULL,
                                null=True,
                                blank=True,
                                verbose_name=_('channel'))
    categories = CategoryManyToManyField(Category,
                                         verbose_name=_('categories'),
                                         blank=True)
    publishing_date = models.DateTimeField(_('publishing date'), default=now)
    is_published = models.BooleanField(_('is published'),
                                       default=False,
                                       db_index=True)
    is_featured = models.BooleanField(_('is featured'),
                                      default=False,
                                      db_index=True)
    hero_event = models.BooleanField(_('Hero Event'),
                                     default=False,
                                     db_index=True)
    featured_image = FilerImageField(
        verbose_name=_('featured image'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    share_image = FilerImageField(
        verbose_name=_('social share image'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        help_text=
        'This image will only be shown on social channels. Minimum size: 1200x630px',
        related_name='+')

    show_on_sitemap = models.BooleanField(_('Show on sitemap'),
                                          null=False,
                                          default=True)
    show_on_xml_sitemap = models.BooleanField(_('Show on xml sitemap'),
                                              null=False,
                                              default=True)
    noindex = models.BooleanField(_('noindex'), null=False, default=False)
    nofollow = models.BooleanField(_('nofollow'), null=False, default=False)
    canonical_url = models.CharField(blank=True,
                                     null=True,
                                     max_length=255,
                                     verbose_name=_('Canonical URL'))

    custom_fields = JSONField(blank=True, null=True)
    # Setting "symmetrical" to False since it's a bit unexpected that if you
    # set "B relates to A" you immediately have also "A relates to B". It have
    # to be forced to False because by default it's True if rel.to is "self":
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L2144
    #
    # which in the end causes to add reversed releted-to entry as well:
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L977
    services = SortedManyToManyField('js_services.Service',
                                     verbose_name=_('services'),
                                     blank=True)
    locations = SortedManyToManyField('js_locations.location',
                                      verbose_name=_('locations'),
                                      blank=True)

    objects = RelatedManager()
    all_objects = AllManager()
    search_objects = SearchManager()

    class Meta:
        ordering = ['-event_start']

    def get_class(self):
        '''Return class name'''
        return self.__class__.__name__

    @property
    def type(self):
        '''Event Type / Section.'''
        return self.app_config

    @property
    def type_slug(self):
        '''Event Type / Section Machine Name'''
        return self.app_config.namespace

    @property
    def published(self):
        """
        Returns True only if the event (is_published == True) AND has a
        published_date that has passed.
        """
        language = get_current_language()
        return self.published_for_language(language)

    def published_for_language(self, language):
        if TRANSLATE_IS_PUBLISHED:
            return ((self.safe_translation_getter('is_published_trans',
                                                  language_code=language,
                                                  any_language=False) or False)
                    and self.publishing_date <= now())
        return (self.is_published and self.publishing_date <= now())

    @property
    def future(self):
        """
        Returns True if the event is published but is scheduled for a
        future date/time.
        """
        if TRANSLATE_IS_PUBLISHED:
            return ((self.safe_translation_getter('is_published_trans',
                                                  language_code=language,
                                                  any_language=False) or False)
                    and self.publishing_date > now())
        return (self.is_published and self.publishing_date > now())

    @property
    def upcoming(self):
        return self.event_start > now()

    @property
    def past(self):
        return self.event_start > now()

    @property
    def show_registration_content(self):
        return (self.registration_until or self.event_start) > now()

    @property
    def start_date(self):
        return self.event_start.date()

    @property
    def start_time(self):
        return self.event_start.time()

    @property
    def end_date(self):
        if self.event_end:
            return self.event_end.date()

    @property
    def end_time(self):
        if self.event_end:
            return self.event_end.time()

    @property
    def hosts(self):
        hosts = []
        if self.host and self.host.published:
            hosts.append(self.host)
        if self.host_2 and self.host_2.published:
            hosts.append(self.host_2)
        if self.host_3 and self.host_3.published:
            hosts.append(self.host_3)
        return hosts

    def get_absolute_url(self, language=None):
        """Returns the url for this Event in the selected permalink format."""
        if not language:
            language = get_current_language()
        kwargs = {}
        permalink_type = self.app_config.permalink_type
        if 'y' in permalink_type:
            kwargs.update(year=self.publishing_date.year)
        if 'm' in permalink_type:
            kwargs.update(month="%02d" % self.publishing_date.month)
        if 'd' in permalink_type:
            kwargs.update(day="%02d" % self.publishing_date.day)
        if 'i' in permalink_type:
            kwargs.update(pk=self.pk)
        if 's' in permalink_type:
            slug, lang = self.known_translation_getter('slug',
                                                       default=None,
                                                       language_code=language)
            if slug and lang:
                site_id = getattr(settings, 'SITE_ID', None)
                if get_redirect_on_fallback(language, site_id):
                    language = lang
                kwargs.update(slug=slug)

        if self.app_config and self.app_config.namespace:
            namespace = '{0}:'.format(self.app_config.namespace)
        else:
            namespace = EventsConfig.default_namespace

        with override(language):
            return reverse('{0}event-detail'.format(namespace), kwargs=kwargs)

    def get_public_url(self, language=None):
        if not language:
            language = get_current_language()
        if not TRANSLATE_IS_PUBLISHED and self.published:
            return self.get_absolute_url(language)
        if (TRANSLATE_IS_PUBLISHED and \
                (self.safe_translation_getter('is_published_trans', language_code=language, any_language=False) or False) and \
                self.publishing_date <= now()):
            return self.get_absolute_url(language)
        return ''

    def get_search_data(self, language=None, request=None):
        """
        Provides an index for use with Haystack, or, for populating
        Event.translations.search_data.
        """
        if not self.pk:
            return ''
        if language is None:
            language = get_current_language()
        if request is None:
            request = get_request(language=language)
        title = self.safe_translation_getter('title', '')
        description = self.safe_translation_getter('lead_in', '')
        location = self.safe_translation_getter('location', '')
        text_bits = [title, strip_tags(description), strip_tags(location)]
        for category in self.categories.all():
            text_bits.append(
                force_unicode(category.safe_translation_getter('name')))
        for service in self.services.all():
            text_bits.append(
                force_unicode(service.safe_translation_getter('title')))
        if self.content:
            plugins = self.content.cmsplugin_set.filter(language=language)
            for base_plugin in plugins:
                plugin_text_content = ' '.join(
                    get_plugin_index_data(base_plugin, request))
                text_bits.append(plugin_text_content)
        return ' '.join(text_bits)

    def save(self, *args, **kwargs):
        # Update the search index
        if self.update_search_on_save:
            self.search_data = self.get_search_data()

        # slug would be generated by TranslatedAutoSlugifyMixin
        super(Event, self).save(*args, **kwargs)

    def __str__(self):
        return self.safe_translation_getter('title', any_language=True)

    def get_placeholders(self):
        return [
            self.content,
            self.registration_content,
            self.sidebar,
        ]

    def _get_related_qs(self, queryset):
        queryset = queryset.exclude(pk=self.pk).order_by('-event_start')
        if self.services.exists():
            return queryset.filter(services__in=self.services.all()).distinct()
        elif self.categories.exists():
            return queryset.filter(
                categories__in=self.categories.all()).distinct()
        else:
            return queryset.filter(app_config=self.app_config)

    def related_events(self):
        return self._get_related_qs(Event.objects.published())

    def related_upcoming_events(self):
        return self._get_related_qs(Event.objects.upcoming())

    def related_past_events(self):
        return self._get_related_qs(Event.objects.past())

    cached_related_events = cached_property(related_events,
                                            name='cached_related_events')

    cached_related_upcoming_events = cached_property(
        related_upcoming_events, name='cached_related_upcoming_events')

    cached_related_past_events = cached_property(
        related_past_events, name='cached_related_past_events')
Esempio n. 10
0
class Person(CustomPersonMixin,
             TranslationHelperMixin,
             TranslatedAutoSlugifyMixin,
             TranslatableModel):
    update_search_on_save = UPDATE_SEARCH_DATA_ON_SAVE

    translations = TranslatedFields(
        first_name_trans=models.CharField(
            _('first name'), max_length=255, blank=False,
            default='', help_text=_("Provide this person's first name.")),
        last_name_trans=models.CharField(
            _('last name'), max_length=255, blank=False,
            default='', help_text=_("Provide this person's last name.")),
        suffix=models.CharField(
            _('suffix'), max_length=60, blank=True,
            default='', help_text=_("Provide this person's suffix.")),
        slug=models.SlugField(
            _('unique slug'), max_length=255, blank=True,
            default='',
            help_text=_("Leave blank to auto-generate a unique slug.")),
        function=models.CharField(_('role'), max_length=255, blank=True, default=''),
        description=HTMLField(_('description'), blank=True, default=''),
        search_data=models.TextField(blank=True, editable=False),
        is_published_trans=models.BooleanField(
            verbose_name=_('show on website'), default=True)
    )
    first_name = models.CharField(
        _('first name'), max_length=255, blank=False,
        default='', help_text=_("Provide this person's first name."))
    last_name = models.CharField(
        _('last name'), max_length=255, blank=False,
        default='', help_text=_("Provide this person's last name."))
    phone = models.CharField(
        verbose_name=_('phone'), null=True, blank=True, max_length=100)
    second_phone = models.CharField(
        verbose_name=_('secondary phone'), null=True, blank=True, max_length=100)
    mobile = models.CharField(
        verbose_name=_('mobile'), null=True, blank=True, max_length=100)
    fax = models.CharField(
        verbose_name=_('fax'), null=True, blank=True, max_length=100)
    email = models.EmailField(
        verbose_name=_("email"), blank=True, default='')
    facebook = models.URLField(
        verbose_name=_('facebook'), null=True, blank=True, max_length=200)
    twitter = models.CharField(
        verbose_name=_('twitter'), null=True, blank=True, max_length=100)
    linkedin = models.URLField(
        verbose_name=_('linkedin'), null=True, blank=True, max_length=200)
    xing = models.URLField(
        verbose_name=_('xing'), null=True, blank=True, max_length=200)
    location = models.ForeignKey('js_locations.Location',
        on_delete=models.SET_NULL, verbose_name=_('location'), null=True, blank=True)
    website = models.URLField(
        verbose_name=_('website'), null=True, blank=True)
    groups = SortedManyToManyField(
        'aldryn_people.Group', default=None, blank=True, related_name='people',
        help_text=_('Choose and order the groups for this person, the first '
                    'will be the "primary group".'))
    visual = FilerImageField(
        null=True, blank=True, default=None, on_delete=models.SET_NULL)
    second_visual = FilerImageField(
        null=True, blank=True, default=None, on_delete=models.SET_NULL,
        related_name='second_person_visual')
    vcard_enabled = models.BooleanField(
        verbose_name=_('enable vCard download'), default=True)
    details_enabled = models.BooleanField(
        verbose_name=_('enable details'), default=True)
    is_published = models.BooleanField(
        verbose_name=_('show on website'), default=True)
    user = models.OneToOneField(
        getattr(settings, 'AUTH_USER_MODEL', 'auth.User'),
        on_delete=models.SET_NULL,
        null=True, blank=True, related_name='persons')
    categories = CategoryManyToManyField('aldryn_categories.Category',
         verbose_name=_('categories'), blank=True)
    services = SortedManyToManyField('js_services.Service',
         verbose_name=_('services'), blank=True)
    content = PlaceholderField('content',
        related_name='person_content')
    placeholder_sidebar = PlaceholderField('sidebar')
    banner = PlaceholderField('person_banner',
        related_name='person_banner')

    show_on_sitemap = models.BooleanField(_('Show on sitemap'), null=False, default=True)
    show_on_xml_sitemap = models.BooleanField(_('Show on xml sitemap'), null=False, default=True)
    noindex = models.BooleanField(_('noindex'), null=False, default=False)
    nofollow = models.BooleanField(_('nofollow'), null=False, default=False)
    canonical_url = models.CharField(
        blank=True,
        null=True,
        max_length=255,
        verbose_name=_('Canonical URL')
    )
    custom_fields = JSONField(blank=True, null=True)

    objects = PeopleManager()

    class Meta:
        verbose_name = _('Person')
        verbose_name_plural = _('People')

    def __str__(self):
        pkstr = str(self.pk)

        if six.PY2:
            pkstr = six.u(pkstr)
        # name = ' '.join((
        #     self.safe_translation_getter(
        #         'first_name',
        #         default='',
        #         any_language=True
        #     ),
        #     self.safe_translation_getter(
        #         'last_name',
        #         default='',
        #         any_language=True
        #     )
        # )).strip()
        # name = 'TESTING'  #DEBUG
        name = ' '.join((self.first_name, self.last_name)).strip()
        return name if len(name) > 0 else pkstr

    @property
    def primary_group(self):
        """Simply returns the first in `groups`, if any, else None."""
        return self.groups.first()

    @property
    def primary_company(self):
        if IS_THERE_COMPANIES:
            return self.companies.first()

    @property
    def comment(self):
        return self.safe_translation_getter('description', '')

    @property
    def sorted_companies(self):
        if IS_THERE_COMPANIES:
            return [obj.company for obj in self.companies.through.objects.filter(person=self).order_by('sort_value')]
        else:
            []

    @property
    def published(self):
        """
        Returns True only if the article (is_published == True) AND has a
        published_date that has passed.
        """
        language = get_current_language()
        if TRANSLATE_IS_PUBLISHED:
            return self.safe_translation_getter('is_published_trans', language_code=language, any_language=False) or False
        return self.is_published

    def get_search_data(self, language=None, request=None):
        """
        Provides an index for use with Haystack, or, for populating
        Event.translations.search_data.
        """
        if not self.pk:
            return ''
        if language is None:
            language = get_current_language()
        if request is None:
            request = get_request(language=language)
        text_bits = [self.first_name]
        text_bits.append(self.last_name)
        text_bits.append(self.safe_translation_getter('function', ''))
        description = self.safe_translation_getter('description', '')
        text_bits.append(strip_tags(description))
        for category in self.categories.all():
            text_bits.append(
                force_unicode(category.safe_translation_getter('name')))
        for service in self.services.all():
            text_bits.append(
                force_unicode(service.safe_translation_getter('title')))
        if self.content:
            plugins = self.content.cmsplugin_set.filter(language=language)
            for base_plugin in plugins:
                plugin_text_content = ' '.join(
                    get_plugin_index_data(base_plugin, request))
                text_bits.append(plugin_text_content)
        return ' '.join(text_bits)

    def save(self, *args, **kwargs):
        if self.update_search_on_save:
            self.search_data = self.get_search_data()
        super(Person, self).save(*args, **kwargs)

    def get_absolute_url(self, language=None):
        if not language:
            language = get_current_language()
        slug, language = self.known_translation_getter(
            'slug', None, language_code=language)
        if slug:
            kwargs = {'slug': slug}
        else:
            kwargs = {'pk': self.pk}
        with override(language):
            # do not fail with 500 error so that if detail view can't be
            # resolved we still can use plugins.
            try:
                url = reverse('%s:person-detail' % DEFAULT_APP_NAMESPACE, kwargs=kwargs)
            except NoReverseMatch:
                url = ''
        return url

    def get_public_url(self, language=None):
        if not language:
            language = get_current_language()
        if not TRANSLATE_IS_PUBLISHED and self.is_published:
            return self.get_absolute_url(language)
        if (TRANSLATE_IS_PUBLISHED and \
                (self.safe_translation_getter('is_published_trans', language_code=language, any_language=False) or False)):
            return self.get_absolute_url(language)
        return ''


    def get_vcard_url(self, language=None):
        if not language:
            language = get_current_language()
        slug = self.safe_translation_getter(
            'slug', None, language_code=language, any_language=False)
        if slug:
            kwargs = {'slug': slug}
        else:
            kwargs = {'pk': self.pk}
        with override(language):
            return reverse('%s:download_vcard' % DEFAULT_APP_NAMESPACE, kwargs=kwargs)

    def get_vcard(self, request=None):
        vcard = Vcard()
        function = self.safe_translation_getter('function')

        safe_name = self.name()
        vcard.add_line('FN', safe_name)
        vcard.add_line('N', [None, safe_name, None, None, None])

        if self.visual:
            ext = self.visual.extension.upper()
            try:
                with open(self.visual.path, 'rb') as f:
                    data = force_text(base64.b64encode(f.read()))
                    vcard.add_line('PHOTO', data, TYPE=ext, ENCODING='b')
            except IOError:
                if request:
                    url = urlparse.urljoin(request.build_absolute_uri(),
                                           self.visual.url),
                    vcard.add_line('PHOTO', url, TYPE=ext)

        if self.email:
            vcard.add_line('EMAIL', self.email)

        if function:
            vcard.add_line('TITLE', self.function)

        if self.phone:
            vcard.add_line('TEL', self.phone, TYPE='WORK')
        if self.mobile:
            vcard.add_line('TEL', self.mobile, TYPE='CELL')

        if self.fax:
            vcard.add_line('TEL', self.fax, TYPE='FAX')
        if self.website:
            vcard.add_line('URL', self.website)

        if self.primary_company:
            vcard.add_line('ORG', self.primary_company.name)

        if self.primary_group:
            group_name = self.primary_group.safe_translation_getter(
                'name', default="Group: {0}".format(self.primary_group.pk))
            if group_name and not self.primary_company:
                vcard.add_line('ORG', group_name)
            if (self.primary_group.address or self.primary_group.city or
                    self.primary_group.postal_code):
                vcard.add_line('ADR', (
                    None, None,
                    self.primary_group.address,
                    self.primary_group.city,
                    None,
                    self.primary_group.postal_code,
                    None,
                ), TYPE='WORK')

            if self.primary_group.phone:
                vcard.add_line('TEL', self.primary_group.phone, TYPE='WORK')
            if self.primary_group.fax:
                vcard.add_line('TEL', self.primary_group.fax, TYPE='FAX')
            if self.primary_group.website:
                vcard.add_line('URL', self.primary_group.website)

        return six.b('{}'.format(vcard))

    def get_slug_source(self):
        return self.__str__()

    def name(self):
        return self.__str__()

    def get_placeholders(self):
        return [
            self.content,
            self.placeholder_sidebar,
            self.banner,
        ]

    def related_articles(self, article_category=None):
        qs = self.article_set.published() | self.author_2.published() | self.author_3.published()
        if article_category:
            return qs.filter(app_config__namespace=article_category).distinct()
        return qs.distinct()

    def related_events(self, event_category=None):
        if hasattr(self, 'event_set'):
            qs = self.event_set.published() | self.host_2.published() | self.host_3.published()
            if event_category:
                return qs.filter(app_config__namespace=event_category).distinct()
            return qs.distinct().order_by('-event_start')

    def related_upcoming_events(self, event_category=None):
        if hasattr(self, 'event_set'):
            qs = self.event_set.upcoming() | self.host_2.upcoming() | self.host_3.upcoming()
            if event_category:
                return qs.filter(app_config__namespace=event_category).distinct()
            return qs.distinct().order_by('event_start')

    def related_past_events(self, event_category=None):
        if hasattr(self, 'event_set'):
            qs = self.event_set.past() | self.host_2.past() | self.host_3.past()
            if event_category:
                return qs.filter(app_config__namespace=event_category).distinct()
            return qs.distinct().order_by('-event_start')

    def related_services(self, service_category=None):
        if service_category:
            return self.services.published().filter(sections__namespace=service_category)
        return self.services.published().all()
Esempio n. 11
0
class Article(TranslatableModel):
    translations = TranslatedFields(
        title=models.CharField(_('title'), max_length=234),
        slug=models.SlugField(
            verbose_name=_('slug'),
            max_length=255,
            db_index=True,
            blank=True,
            help_text=_('Used in the URL. If changed, the URL will change. '
                        'Clear it to have it re-created automatically.'),
        ),
        lead_in=HTMLField(
            verbose_name=_('Optional lead-in'),
            default='',
            help_text=_('Will be displayed in lists, and at the start of the '
                        'detail page (in bold)'),
            blank=True,
        ),
        meta_title=models.CharField(max_length=255,
                                    verbose_name=_('meta title'),
                                    blank=True,
                                    default=''),
        meta_description=models.TextField(verbose_name=_('meta description'),
                                          blank=True,
                                          default=''),
        meta_keywords=models.TextField(verbose_name=_('meta keywords'),
                                       blank=True,
                                       default=''),
        meta={'unique_together': ((
            'language_code',
            'slug',
        ), )},
        search_data=models.TextField(blank=True, editable=False))

    content = PlaceholderField('aldryn_newsblog_article_content',
                               related_name='aldryn_newsblog_articles',
                               unique=True)
    author = models.ForeignKey(Person,
                               null=True,
                               blank=True,
                               verbose_name=_('author'))
    owner = models.ForeignKey(User, verbose_name=_('owner'))
    app_config = models.ForeignKey(NewsBlogConfig,
                                   verbose_name=_('app. config'))
    categories = CategoryManyToManyField('aldryn_categories.Category',
                                         verbose_name=_('categories'),
                                         blank=True)
    publishing_date = models.DateTimeField(_('publishing date'),
                                           default=datetime.datetime.now)
    is_published = models.BooleanField(_('is published'),
                                       default=True,
                                       db_index=True)
    is_featured = models.BooleanField(_('is featured'),
                                      default=False,
                                      db_index=True)
    featured_image = FilerImageField(null=True, blank=True)

    tags = TaggableManager(blank=True)

    related = SortedManyToManyField('self',
                                    verbose_name=_('related articles'),
                                    blank=True)

    objects = RelatedManager()

    class Meta:
        ordering = ['-publishing_date']

    def __str__(self):
        return self.safe_translation_getter('title', any_language=True)

    def get_absolute_url(self):
        return reverse('{namespace}:article-detail'.format(
            namespace=self.app_config.namespace),
                       kwargs={
                           'slug':
                           self.safe_translation_getter('slug',
                                                        any_language=True)
                       })

    def slugify(self, source_text, i=None):
        slug = default_slugify(source_text)
        if i is not None:
            slug += "_%d" % i
        return slug

    def get_search_data(self, language, request=None):
        """
        Provides an index for use with Haystack, or, for populating
        Article.translations.search_data.
        """
        if request is None:
            request = get_request(language=language)
        description = self.safe_translation_getter('lead_in')
        text_bits = [strip_tags(description)]
        for category in self.categories.all():
            text_bits.append(
                force_unicode(category.safe_translation_getter('name')))
        for tag in self.tags.all():
            text_bits.append(force_unicode(tag.name))
        if self.content:
            for base_plugin in self.content.cmsplugin_set.filter(
                    language=language):
                plugin_text_content = ' '.join(
                    get_plugin_index_data(base_plugin, request))
                text_bits.append(plugin_text_content)
        return ' '.join(text_bits)

    def save(self, *args, **kwargs):
        # Ensure there is an owner.
        if self.app_config.create_authors and self.author is None:
            self.author = Person.objects.get_or_create(
                user=self.owner,
                defaults={
                    'name':
                    u' '.join((self.owner.first_name, self.owner.last_name))
                })[0]

        # Start with a naïve approach, if none provided.
        if not self.slug:
            self.slug = default_slugify(self.title)

        # Ensure we aren't colliding with an existing slug *for this language*.
        if Article.objects.translated(slug=self.slug).exclude(
                id=self.id).count() == 0:
            return super(Article, self).save(*args, **kwargs)

        for lang in LANGUAGE_CODES:
            #
            # We'd much rather just do something like:
            # Article.objects.translated(lang,
            # slug__startswith=self.slug)
            # But sadly, this isn't supported by Parler/Django, see:
            # http://django-parler.readthedocs.org/en/latest/api/\
            # parler.managers.html#the-translatablequeryset-class
            #
            slugs = []
            all_slugs = Article.objects.language(lang).exclude(
                id=self.id).values_list('translations__slug', flat=True)
            for slug in all_slugs:
                if slug and slug.startswith(self.slug):
                    slugs.append(slug)
            i = 1
            while True:
                slug = self.slugify(self.title, i)
                if slug not in slugs:
                    self.slug = slug
                    return super(Article, self).save(*args, **kwargs)
                i += 1
Esempio n. 12
0
 def test_category_many_to_many_field(self):
     field = CategoryManyToManyField(Category)
     form_field = field.formfield()
     self.assertTrue(isinstance(form_field, CategoryMultipleChoiceField))
     field_type = field.get_internal_type()
     self.assertEquals(field_type, 'ManyToManyField')
Esempio n. 13
0
 def test_category_many_to_many_field(self):
     field = CategoryManyToManyField(Category)
     form_field = field.formfield()
     self.assertTrue(isinstance(form_field, CategoryMultipleChoiceField))
     field_type = field.get_internal_type()
     self.assertEquals(field_type, 'ManyToManyField')
Esempio n. 14
0
class Article(TranslationHelperMixin, BaseModel, TranslatableModel):
    # TranslatedAutoSlugifyMixin options
    slug_source_field_name = 'title'
    slug_default = _('untitled-article')

    update_search_on_save = getattr(
        settings, 'ALDRYN_NEWSBLOG_UPDATE_SEARCH_DATA_ON_SAVE', False)

    # foreign keys
    created_by = models.ForeignKey(
        'users.User',
        verbose_name=_('created by'),
        null=True,
        on_delete=models.SET_NULL,
        related_name='articles',
    )
    translations = TranslatedFields(
        title=models.CharField(
            _('title'),
            max_length=234,
        ),
        slug=models.SlugField(
            verbose_name=_('slug'),
            max_length=255,
            db_index=True,
            blank=True,
            help_text=_('Used in the URL. If changed, the URL will change. '
                        'Clear it to have it re-created automatically.'),
        ),
        lead_in=HTMLField(
            verbose_name=_('lead'),
            default='',
            help_text=_(
                'The lead gives the reader the main idea of the story, this '
                'is useful in overviews, lists or as an introduction to your '
                'article.'),
            blank=True,
        ),
        meta_title=models.CharField(max_length=255,
                                    verbose_name=_('meta title'),
                                    blank=True,
                                    default=''),
        meta_description=models.TextField(verbose_name=_('meta description'),
                                          blank=True,
                                          default=''),
        meta_keywords=models.TextField(verbose_name=_('meta keywords'),
                                       blank=True,
                                       default=''),
        is_published=models.BooleanField(_('is published'),
                                         default=False,
                                         db_index=True),
        is_featured=models.BooleanField(_('is featured'),
                                        default=False,
                                        db_index=True),
        is_dirty=models.BooleanField(
            default=False,
            editable=False,
        ),
        draft=models.BooleanField(
            default=True,
            editable=False,
            db_index=True,
        ),
        meta={'unique_together': (('language_code', 'slug', 'draft'), )},
        search_data=models.TextField(blank=True, editable=False))
    content = PlaceholderField(
        'article_content',
        on_delete=models.SET_NULL,
        related_name='articles',
    )

    categories = CategoryManyToManyField(
        'aldryn_categories.Category',
        verbose_name=_('categories'),
        blank=True,
        related_name='articles',
    )
    publishing_date = models.DateTimeField(_('publishing date'),
                                           default=timezone.now)
    featured_image = FilerImageField(
        verbose_name=_('featured image'),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='articles',
    )
    tags = TaggableManager(
        blank=True,
        related_name='articles',
    )

    # Setting "symmetrical" to False since it's a bit unexpected that if you
    # set "B relates to A" you immediately have also "A relates to B". It have
    # to be forced to False because by default it's True if rel.to is "self":
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L2144
    #
    # which in the end causes to add reversed releted-to entry as well:
    #
    # https://github.com/django/django/blob/1.8.4/django/db/models/fields/related.py#L977
    related = SortedManyToManyField('self',
                                    verbose_name=_('related articles'),
                                    blank=True,
                                    symmetrical=False)
    is_draft = models.BooleanField(
        default=True,
        editable=False,
        db_index=True,
    )

    public = models.OneToOneField('self',
                                  related_name='draft_article',
                                  null=True,
                                  editable=False)

    objects = RelatedManager()

    exclude_on_on_delete_test = ('public', 'content')

    class Meta:
        verbose_name = _('article')
        verbose_name_plural = _('articles')
        ordering = ('-publishing_date', )
        permissions = (('view_article', _('Can view article')), )

    # private methods
    def __str__(self):
        return self.title

    def _copy_attributes(self, target, language):
        """
        Copy all page data to the target. This excludes parent and other values
        that are specific to an exact instance.
        :param target: The Article to copy the attributes to
        """
        translation = self.translations.get(language_code=language)

        # copy translations
        try:
            new_translation = ArticleTranslation.objects.get(
                master_id=target.pk,
                language_code=language,
            )
        except ArticleTranslation.DoesNotExist:
            ArticleTranslation.objects.create(
                master_id=target.pk,
                language_code=language,
                title=translation.title,
                slug=translation.slug,
                lead_in=translation.lead_in,
                meta_title=translation.meta_title,
                meta_description=translation.meta_description,
                meta_keywords=translation.meta_keywords,
                search_data=translation.search_data,
                draft=False,
                is_featured=translation.is_featured,
            )
        else:
            new_translation.title = translation.title
            new_translation.draft = False
            new_translation.slug = translation.slug
            new_translation.lead_in = translation.lead_in
            new_translation.meta_title = translation.meta_title
            new_translation.meta_description = translation.meta_description
            new_translation.meta_keywords = translation.meta_keywords
            new_translation.search_data = translation.search_data
            new_translation.is_featured = translation.is_featured
            new_translation.save()

        target.featured_image = self.featured_image
        target.publishing_date = self.publishing_date
        target.is_featured = self.is_featured

        target.tags.clear()

        for tag in self.tags.all():
            target.tags.add(tag.name)

    def _copy_contents(self, target, language):
        """
        Copy all the plugins to a new article.
        :param target: The page where the new content should be stored
        """
        # TODO: Make this into a "graceful" copy instead of deleting and
        # overwriting copy the placeholders
        # (and plugins on those placeholders!)

        plugins = CMSPlugin.objects.filter(
            placeholder=target.content, language=language).order_by('-depth')

        for plugin in plugins:
            instance, cls = plugin.get_plugin_instance()
            if instance and getattr(instance, 'cmsplugin_ptr_id', False):
                instance.cmsplugin_ptr = plugin
                instance.cmsplugin_ptr._no_reorder = True
                instance.delete(no_mp=True)
            else:
                plugin._no_reorder = True
                plugin.delete(no_mp=True)

        plugins = self.content.get_plugins_list(language)

        # update the page copy
        if plugins:
            copy_plugins_to(plugins, target.content)

    # django methods
    def clean(self):
        values = super().clean()

        # check for existing slugs
        if not self.slug:
            self.slug = slugify(self.title)

        not_this_article_list = Article.objects.exclude_article(self)

        if not_this_article_list.translated(slug=self.slug).exists():
            raise ValidationError({
                'slug':
                _('Slug "%s" is repeated') % self.slug,
            })

        return values

    def get_absolute_url(self):
        url = reverse('articles:article_detail', args=(self.slug, ))
        if self.is_draft:
            return url + '?edit'
        else:
            return url

    def get_index_url(self):
        return reverse('articles:article_detail', args=(self.slug, ))

    def save(self, *args, **kwargs):
        if self.is_draft:
            if not self.slug:
                self.slug = slugify(self.title)

        new = not self.id

        return_value = super(Article, self).save(*args, **kwargs)

        # Index if appropiate (if the document already exists it replaces it)
        # Otherwise delete the document indexed
        # Called after saving because the id is needed
        if not new:
            if self.is_published and not self.is_draft:
                self.index_in_elasticsearch(1)
            else:
                self.deindex_in_elasticsearch()

        return return_value

    # custom methods
    @property
    def draft_pk(self):
        if self.is_draft:
            return self.pk

        # the 'public' field is badly defined, since it's the draft when the
        # article is public
        return self.public_id

    def publish(self, language):
        """
        :returns: the publicated Article.
        """
        # Publish only be called on draft articles
        if not self.is_draft:
            raise PublicIsUnmodifiable(
                'The public instance cannot be published. Use draft.')

        if not self.pk:
            self.save()

        # be sure we have the newest data including tree information
        self.refresh_from_db()

        if self.public_id:
            # Ensure we have up to date mptt properties
            public_article = self.public
        else:
            public_article = Article(created_by=self.created_by)

        if not self.publishing_date:
            self.publishing_date = timezone.now()

        # we need to set relate this new public copy to its draft page (self)
        public_article.public = self
        public_article.is_draft = False

        public_article.save()

        # store the relationship between draft and public
        self.public = public_article
        self.save()

        self._copy_attributes(public_article, language)

        public_article.is_published = True
        public_article.save()

        # The target page now has a pk, so can be used as a target
        self._copy_contents(public_article, language)

        return public_article

    def get_elasticsearch_kwargs(self):
        kwargs = super(Article, self).get_elasticsearch_kwargs()

        if hasattr(self, 'lead_in'):
            kwargs['description'] = remove_tags(self.lead_in)
            kwargs['lead_in'] = remove_tags(self.lead_in)
        kwargs['url'] = self.get_index_url()
        kwargs['detail'] = date_format(self.publishing_date)
        if self.tags:
            tags = self.tags.values_list('name', flat=True)
            kwargs['tags'] = ', '.join(tags),
        if self.categories:
            categories = self.categories.values_list('translations__name',
                                                     flat=True)
            categories_slug = self.categories.values_list('translations__slug',
                                                          flat=True)
            kwargs['categories'] = ', '.join(categories),
            kwargs['categories_slug'] = ', '.join(categories_slug)

        return kwargs

    def unpublish(self, language):
        # Unpublish only be called on non draft articles
        if self.is_draft:
            raise PublicIsUnmodifiable(
                'The draft instance cannot be published. Use public.')

        self.is_published = False
        self.save()