Exemple #1
0
class Article(PageBase):
    """A news article."""

    objects = ArticleManager()

    news_feed = models.ForeignKey(
        NewsFeed,
        default=get_default_news_feed,
    )

    date = models.DateField(
        db_index=True,
        default=timezone.now,
    )

    image = ImageRefField(
        blank=True,
        null=True,
    )

    content = HtmlField(blank=True, )

    summary = HtmlField(blank=True, )

    categories = models.ManyToManyField(
        Category,
        blank=True,
    )

    authors = models.ManyToManyField(
        User,
        blank=True,
    )

    def _get_permalink_for_page(self, page):
        """Returns the URL of this article for the given news feed page."""
        return page.reverse("article_detail",
                            kwargs={
                                "year": self.date.year,
                                "month": self.date.strftime("%b").lower(),
                                "day": self.date.day,
                                "url_title": self.url_title,
                            })

    def get_absolute_url(self):
        """Returns the URL of the article."""
        return self._get_permalink_for_page(self.news_feed.page)

    class Meta:
        unique_together = ((
            "news_feed",
            "date",
            "url_title",
        ), )
        ordering = ("-date", )
class ContentSection(models.Model):
    # This is a model which will be registered inline so that you can edit it
    # directly from the page's admin screen.
    #
    # This ForeignKey to `pages.Page` is entirely necessary in order to make
    # inlines work. Note that it is to the *Page* itself and not the content
    # model!
    page = models.ForeignKey(
        'pages.Page',
        on_delete=models.CASCADE,
    )

    title = models.CharField(
        max_length=100,
    )

    # HtmlField is explained in more depth over in the news app.
    text = HtmlField(
        null=True,
        blank=True,
    )

    order = models.PositiveIntegerField(
        default=0,
    )

    def __str__(self):
        return self.title

    class Meta:
        ordering = ['order']
Exemple #3
0
class Faq(SearchMetaBase):
    """ An FAQ """
    page = models.ForeignKey(Faqs)

    question = models.CharField(max_length=256)

    answer = HtmlField()

    url_title = models.CharField(max_length=256, unique=True)

    order = models.PositiveIntegerField(default=0)

    def __unicode__(self):
        return self.question

    class Meta:
        verbose_name = "faq"
        verbose_name_plural = "faqs"
        ordering = ['order', 'id', 'question']

    def get_absolute_url(self):
        """ Gets the url of a Faq

            Returns:
                url of Person

        """
        return "{}{}/".format(self.page.page.get_absolute_url(),
                              self.url_title)
Exemple #4
0
class Career(PageBase):

    page = models.ForeignKey(
        Careers
    )

    location = models.CharField(
        max_length=256,
        blank=True,
        null=True
    )

    summary = models.TextField(
        blank=True,
        null=True
    )

    description = HtmlField()

    email_address = models.EmailField()

    order = models.PositiveIntegerField(
        default=0
    )

    class Meta:
        ordering = ['order']

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return self.page.page.reverse('career_detail', kwargs={
            'slug': self.slug,
        })
Exemple #5
0
class SectionBase(models.Model):

    page = models.ForeignKey(Page, )

    type = models.CharField(
        choices=get_section_type_choices(SECTION_TYPES),
        max_length=100,
    )

    title = models.CharField(
        max_length=140,
        blank=True,
        null=True,
    )

    text = models.TextField(
        blank=True,
        null=True,
    )

    content = HtmlField(
        blank=True,
        null=True,
    )

    image = ImageRefField(
        blank=True,
        null=True,
    )

    button_text = models.CharField(
        max_length=100,
        blank=True,
        null=True,
    )

    button_url = models.CharField(
        'button URL',
        max_length=200,
        blank=True,
        null=True,
    )

    order = models.PositiveIntegerField(
        default=0,
        help_text='Order which the section will be displayed',
    )

    class Meta:
        abstract = True
        ordering = ['order']

    def __str__(self):
        return dict(SECTION_TYPES)[self.type]['name']

    @property
    def template(self):
        return f'{self.type}.html'
Exemple #6
0
class NewsFeed(ContentBase):
    """A stream of news articles."""

    icon = "news/img/news-feed.png"

    # The heading that the admin places this content under.
    classifier = "syndication"

    # The urlconf used to power this content's views.
    urlconf = "cms.apps.news.urls"

    content_primary = HtmlField("primary content", blank=True)

    per_page = models.IntegerField(
        "articles per page",
        default=5,
        blank=True,
        null=True,
    )
Exemple #7
0
class Event(PageBase):

    page = models.ForeignKey(Events, )

    start_date = models.DateField()

    end_date = models.DateField()

    description = HtmlField()

    image = ImageRefField(
        null=True,
        blank=True,
    )

    class Meta:
        ordering = ['start_date']

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        if self.page:
            return self.page.page.reverse('event_detail',
                                          kwargs={
                                              'slug': self.slug,
                                          })

    def get_summary(self):
        return self.summary

    @property
    def date(self):
        date_string = '{}'.format(date(self.start_date, 'j F Y'))

        if self.start_date != self.end_date:
            date_string += ' - {}'.format(date(self.end_date, 'j F Y'))

        return date_string
Exemple #8
0
class Category(PageBase):
    """A category for news articles."""

    content_primary = HtmlField("primary content", blank=True)

    def _get_permalink_for_page(self, page):
        """Returns the URL for this category for the given page."""
        return page.reverse("article_category_archive",
                            kwargs={
                                "url_title": self.url_title,
                            })

    def _get_permalinks(self):
        """Returns a dictionary of all permalinks for the given category."""
        pages = Page.objects.filter(id__in=Article.objects.filter(
            categories=self).values_list("news_feed_id", flat=True))
        return dict((u"page_{id}".format(id=page.id),
                     self._get_permalink_for_page(page)) for page in pages)

    class Meta:
        verbose_name_plural = "categories"
        unique_together = (("url_title", ), )
        ordering = ("title", )
Exemple #9
0
class Article(PageBase):
    '''A simple news article.'''

    objects = ArticleManager()

    # We want to be able to have multiple types of news feed. For example,
    # we might want to have a page of articles called "News" (what your cat
    # did today) vs "Blog" (insights on cat behaviour). Because we have access
    # to the current page and its content object in our request, we can filter
    # only the news articles which have this ForeignKey set to the currently
    # active page - alternatively put, that "belong" to it.
    page = models.ForeignKey(
        'news.NewsFeed',
        on_delete=models.PROTECT,
        null=True,
        blank=False,
        verbose_name='News feed'
    )

    # ImageRefField is a ForeignKey to `media.File`, but it uses a raw ID
    # widget by default, and is constrained to only select files that appear
    # to be images (just a regex on the filename). You also have FileRefField
    # that doesn't do the "looks like an image" filtering, but does use the
    # raw ID widget.
    image = ImageRefField(
        null=True,
        blank=True,
        on_delete=models.PROTECT,
    )

    # HtmlField is like a TextField, but gives you a full-featured TinyMCE
    # WYSIWYG editor in your admin. This is just for your convenience.
    # HtmlField is not used internally in the CMS, and nothing about a
    # onespacemedia-cms project requires that you use it. You can use your own
    # favourite field with your own favourite editor here. (BUT YOU SHOULD USE
    # OURS REALLY)
    content = HtmlField()

    # Some more standard Django fields :)
    date = models.DateTimeField(
        default=now,
    )

    summary = models.TextField(
        blank=True,
    )

    class Meta:
        # Not a CMS thing, but if we have two articles assigned to the same
        # page (as above) they'll both have the same URL, and the
        # queryset.get(...) will throw MultipleObjectsReturned. Let's stop
        # that from happening.
        unique_together = [['page', 'slug']]
        ordering = ['-date']

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        # OK, so once we have our urlconf on our content object, whereever we
        # we have access to that content, we reverse those URLs almost exactly
        # as we use django's standard reverse.
        #
        # self.page here is our NewsFeed (content model), and self.page.page
        # is the page to which our content model is attached.
        return self.page.page.reverse('article_detail', kwargs={
            'slug': self.slug,
        })
Exemple #10
0
class Article(PageBase):
    """A news article."""

    objects = ArticleManager()

    news_feed = models.ForeignKey(
        'news.NewsFeed',
        null=True,
        blank=False,
    )

    featured = models.BooleanField(
        default=False,
    )

    date = models.DateTimeField(
        db_index=True,
        default=timezone.now,
    )

    image = ImageRefField(
        blank=True,
        null=True,
    )

    card_image = ImageRefField(
        blank=True,
        null=True,
        help_text="By default the card will try and use the main image, if it doesn't look right you can override it here.",
    )

    content = HtmlField()

    summary = models.TextField(
        blank=True,
    )

    call_to_action = models.ForeignKey(
        'components.CallToAction',
        blank=True,
        null=True,
        help_text="By default the call to action will be the same as the news feed. You can override it for a specific article here."
    )

    categories = models.ManyToManyField(
        'news.Category',
        blank=True,
    )

    status = models.CharField(
        max_length=100,
        choices=STATUS_CHOICES,
        default='draft',
    )

    class Meta:
        unique_together = [['news_feed', 'date', 'slug']]
        ordering = ['-date']
        permissions = [
            ('can_approve_articles', 'Can approve articles'),
        ]

    def __str__(self):
        return self.short_title or self.title

    def _get_permalink_for_page(self, page):
        """Returns the URL of this article for the given news feed page."""
        return page.reverse('article_detail', kwargs={
            'slug': self.slug,
        })

    def get_absolute_url(self):
        """Returns the URL of the article."""
        return self._get_permalink_for_page(self.news_feed.page)

    @property
    def get_summary(self):
        summary = self.summary or striptags(truncate_paragraphs(self.content, 1))

        return truncatewords(summary, 15)

    @property
    def last_modified(self):
        version = Version.objects.get_for_object(self).first()

        if version:
            return version.revision.date_created

    def render_card(self):
        return render_to_string('news/includes/card.html', {
            'article': self,
        })

    def get_related_articles(self, count=3):
        related_articles = Article.objects.filter(
            categories=self.categories.all(),
        ).exclude(
            id=self.id
        )

        if related_articles.count() < count:
            related_articles |= Article.objects.exclude(
                id__in=[self.pk] + [x.id for x in related_articles]
            )

        return related_articles.distinct()[:count]
class StandardPage(ContentBase):

    content_primary = HtmlField(
        blank=True,
        null=True,
    )
Exemple #12
0
class Content(ContentBase):

    content_primary = HtmlField("primary content", blank=True)
Exemple #13
0
class SectionBase(models.Model):

    page = models.ForeignKey(
        Page,
    )

    type = models.CharField(
        choices=get_section_type_choices(SECTION_TYPES),
        max_length=100,
    )

    background_colour = models.CharField(
        max_length=255,
        choices=[
            ('white', 'White'),
        ],
        default='white',
    )

    kicker = models.CharField(
        max_length=50,
        blank=True,
        null=True,
    )

    title = models.CharField(
        max_length=140,
        blank=True,
        null=True,
    )

    text = models.TextField(
        blank=True,
        null=True,
    )

    content = HtmlField(
        blank=True,
        null=True,
    )

    image = ImageRefField(
        blank=True,
        null=True,
    )

    mobile_image = ImageRefField(
        blank=True,
        null=True,
    )

    image_side = models.CharField(
        max_length=10,
        choices=[
            ('left', 'Left'),
            ('right', 'Right'),
        ],
        default='left',
    )

    link_text = models.CharField(
        max_length=100,
        blank=True,
        null=True,
    )

    link_page = models.ForeignKey(
        'pages.Page',
        blank=True,
        null=True,
        help_text='Use this to link to an internal page.',
        related_name='+'
    )

    link_url = models.CharField(
        'link URL',
        max_length=200,
        blank=True,
        null=True,
        help_text='Use this to link to any other URL.',
    )

    order = models.PositiveIntegerField(
        default=0,
        help_text='Order which the section will be displayed',
    )

    class Meta:
        abstract = True
        ordering = ['order']

    def __str__(self):
        return next((x for x in get_section_types_flat() if x['slug'] == self.type), None)['name']

    def clean(self):
        sections = get_section_types_flat()

        for section in sections:
            if self.type == section['slug']:
                required = [getattr(self, field) for field in section['required']]
                if not all(required):
                    fields_str = ''
                    fields_len = len(section['required'])
                    fields = {}

                    for index, field in enumerate(section['required']):
                        fields[field] = ValidationError(f'Please provide an {field}', code='required')
                        connector = ', '

                        if index == fields_len - 2:
                            connector = ' and '
                        elif index == fields_len -1:
                            connector = ''

                        anchor = f'id_{self._meta.model_name}_set-{self.order}-{field}'
                        fields_str += f'<a href="#{anchor}">{field.title()}</a>{connector}'

                    fields['__all__'] = ValidationError(mark_safe(f"{fields_str} fields are required"), code='error')

                    raise ValidationError(fields)

        if self.link_text and (not self.link_page or not self.link_url):
            raise ValidationError({
                'link_page': 'Please supply 1 of "link page" or "link URL"',
            })

    @property
    def template(self):
        folder_name = self.type.split('-')[0]
        file_name = '-'.join(self.type.split('-')[1:])

        return {
            'folder': folder_name,
            'file_name': f'{file_name}.html'
        }

    @property
    def has_link(self):
        return self.link_location and self.link_text

    @cached_property
    def link_location(self):
        if self.link_page_id:
            try:
                return self.link_page.get_absolute_url()
            except Page.DoesNotExist:
                pass
        return self.link_url

    def get_searchable_text(self):
        """Returns a blob of text suitable for searching."""

        # Let's look for the options for our section type.
        for section_group in SECTION_TYPES:
            for section_type in section_group[1]['sections']:
                section_label = slugify('{}-{}'.format(section_group[0], section_type[0]))

                if not section_label == self.type:
                    continue

                # If we defeated the above clause then we have the options
                # for the right section type.
                section_options = section_type[1]

                # Don't require that search_fields is set.
                if 'search_fields' not in section_options:
                    continue

                search_fields = section_options['search_fields']

                search_text_items = []
                for field in search_fields:
                    search_item = getattr(self, field)
                    if search_item:
                        search_text_items.append(strip_tags(search_item))

                return u'\n'.join(search_text_items)

        return ''
Exemple #14
0
class Setting(models.Model):

    name = models.CharField(
        max_length=1024,
        help_text='Name of the setting',
    )

    key = models.CharField(
        max_length=1024,
        help_text='The key used to reference the setting',
    )

    type = models.CharField(
        max_length=1024,
        choices=[
            ('string', 'String'),
            ('text', 'Text'),
            ('html', 'HTML'),
            ('number', 'Number'),
            ('image', 'Image'),
        ],
    )

    string = models.CharField(
        max_length=2048,
        blank=True,
        null=True,
    )

    text = models.TextField(
        blank=True,
        null=True,
    )

    html = HtmlField(
        'HTML',
        null=True,
        blank=True,
    )

    number = models.IntegerField(
        blank=True,
        null=True,
    )

    image = ImageRefField(
        blank=True,
        null=True,
    )

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name

    @cached_property
    def value(self):
        return {
            'string': self.string,
            'text': linebreaksbr(mark_safe(self.text)),
            'html': mark_safe(self.html),
            'number': self.number,
            'image': self.image if self.image else '',
        }[self.type]