Exemple #1
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 #2
0
class TestModelBase(models.Model):
    image = ImageRefField(
        blank=True,
        null=True,
        on_delete=models.CASCADE,
    )

    class Meta:
        abstract = True
Exemple #3
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 ContentColumn(models.Model):
    page = models.ForeignKey(Page)

    title = models.CharField(max_length=100, )

    url = models.CharField(
        "URL",
        max_length=100,
    )

    image = ImageRefField()
Exemple #5
0
class Partner(PageBase):

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

    logo = ImageRefField()

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

    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ['order']

    def __str__(self):
        return self.title
Exemple #6
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 #7
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 #8
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]
Exemple #9
0
class SearchMetaBase(OnlineBase):
    """Base model for models used to generate a standalone HTML page."""

    objects = SearchMetaBaseManager()

    # SEO fields.

    browser_title = models.CharField(
        max_length=1000,
        blank=True,
        help_text=(
            "The heading to use in the user's web browser. "
            "Leave blank to use the page title. "
            "Search engines pay particular attention to this attribute."))

    meta_description = models.TextField(
        "description",
        blank=True,
        help_text="A brief description of the contents of this page.",
    )

    sitemap_priority = models.FloatField(
        "priority",
        choices=(
            (1.0, "Very high"),
            (0.8, "High"),
            (0.5, "Medium"),
            (0.3, "Low"),
            (0.0, "Very low"),
        ),
        default=None,
        blank=True,
        null=True,
        help_text=
        ("The relative importance of this content on your site. Search engines use this "
         "as a hint when ranking the pages within your site."),
    )

    sitemap_changefreq = models.IntegerField(
        "change frequency",
        choices=((1, "Always"), (2, "Hourly"), (3, "Daily"), (4, "Weekly"),
                 (5, "Monthly"), (6, "Yearly"), (7, "Never")),
        default=None,
        blank=True,
        null=True,
        help_text=
        ("How frequently you expect this content to be updated. "
         "Search engines use this as a hint when scanning your site for updates."
         ),
    )

    robots_index = models.BooleanField(
        "allow indexing",
        default=True,
        help_text=
        ("Uncheck to prevent search engines from indexing this page. "
         "Do this only if the page contains information which you do not wish "
         "to show up in search results."),
    )

    robots_follow = models.BooleanField(
        "follow links",
        default=True,
        help_text=
        ("Uncheck to prevent search engines from following any links they find in this page. "
         "Do this only if the page contains links to other sites that you do not wish to "
         "publicise."),
    )

    robots_archive = models.BooleanField(
        "allow archiving",
        default=True,
        help_text=
        ("Uncheck this to prevent search engines from archiving this page. "
         "Do this this only if the page is likely to change on a very regular basis. "
         ),
    )

    # Open Graph fields
    og_title = models.CharField(
        verbose_name='title',
        blank=True,
        max_length=100,
        help_text=
        'Title that will appear on Facebook posts. This is limited to 100 characters, '
        'but Facebook will truncate the title to 88 characters.')

    og_description = models.TextField(
        verbose_name='description',
        blank=True,
        max_length=300,
        help_text=
        'Description that will appear on Facebook posts. It is limited to 300 '
        'characters, but it is recommended that you do not use anything over 200.'
    )

    og_image = ImageRefField(
        verbose_name='image',
        blank=True,
        null=True,
        help_text=
        'The recommended image size is 1200x627 (1.91:1 ratio); this gives you a big '
        'stand out thumbnail. Using an image smaller than 400x209 will give you a '
        'small thumbnail and will splits posts into 2 columns. '
        'If you have text on the image make sure it is centered.')

    # Twitter card fields
    twitter_card = models.IntegerField(
        verbose_name='card',
        choices=[
            (0, 'Summary'),
            (1, 'Photo'),
            (2, 'Video'),
            (3, 'Product'),
            (4, 'App'),
            (5, 'Gallery'),
            (6, 'Large Summary'),
        ],
        blank=True,
        null=True,
        default=None,
        help_text=
        'The type of content on the page. Most of the time "Summary" will suffice. '
        'Before you can benefit from any of these fields make sure to go to '
        'https://dev.twitter.com/docs/cards/validation/validator and get approved.'
    )

    twitter_title = models.CharField(
        verbose_name='title',
        blank=True,
        max_length=70,
        help_text=
        'The title that appears on the Twitter card, it is limited to 70 characters.'
    )

    twitter_description = models.TextField(
        verbose_name='description',
        blank=True,
        max_length=200,
        help_text='Description that will appear on Twitter cards. It is limited '
        'to 200 characters. This does\'nt effect SEO, so focus on copy '
        'that complements the tweet and title rather than on keywords.')

    twitter_image = ImageRefField(
        verbose_name='image',
        blank=True,
        null=True,
        help_text=
        'The minimum size it needs to be is 280x150. If you want to use a larger image'
        'make sure the card type is set to "Large Summary".')

    def get_context_data(self):
        """Returns the SEO context data for this page."""
        title = str(self)
        # Return the context.
        return {
            "meta_description": self.meta_description,
            "robots_index": self.robots_index,
            "robots_archive": self.robots_archive,
            "robots_follow": self.robots_follow,
            "title": self.browser_title or title,
            "header": title,
            "og_title": self.og_title,
            "og_description": self.og_description,
            "og_image": self.og_image,
            "twitter_card": self.twitter_card,
            "twitter_title": self.twitter_title,
            "twitter_description": self.twitter_description,
            "twitter_image": self.twitter_image
        }

    def render(self, request, template, context=None, **kwargs):
        """Renders a template as a HttpResponse using the context of this page."""
        page_context = self.get_context_data()
        page_context.update(context or {})
        return render(request, template, page_context, **kwargs)

    class Meta:
        abstract = True
Exemple #10
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 #11
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]