Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Feature/new articles #29

Merged
merged 34 commits into from
Oct 12, 2016
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
92aae2c
Merge branch 'develop' into glamkit
Aug 31, 2016
71deb4b
WIP adding glamkit extras set to setup.py
Aug 31, 2016
0fe4625
First cut of authors app extracted from SFMOMA, with corresponding Au…
Sep 1, 2016
99c9995
Remove extraneous print statement, and typo.
Sep 1, 2016
22b195d
Installing sponsors and press releases
Sep 1, 2016
dbe6a33
Merge branch 'feature/124_authors' into glamkit
Sep 1, 2016
da9431e
Adding migrations after postgres setup for tests.
Sep 5, 2016
601e8c4
Initial glamkit articles app
Sep 5, 2016
5b912ed
Add articles to the glamkit dashboard, and redo initial migration to …
Sep 5, 2016
2802ddc
Merge branch 'develop' into glamkit
Sep 5, 2016
9617316
Merge branch 'develop' into glamkit
Sep 15, 2016
5876315
Re #122 refactored Article model to allow Polymorphic and nonpolymorp…
Sep 27, 2016
885d284
Merge branch 'develop' into glamkit
Sep 27, 2016
3e241f0
Fixing missed merge conflict
Sep 27, 2016
df7c743
Tweak to tests now Article is refactored.
Sep 27, 2016
5e4c307
Tweak to articles tests
Sep 27, 2016
7c0cfd7
Merge branch 'develop' into glamkit
Sep 27, 2016
527e166
Refactored articles from the glamkit branch
Sep 27, 2016
bf28636
Listing template
Sep 27, 2016
f968cf5
Authors app now uses Article/Listing pattern; authors.page app is no …
Sep 27, 2016
1228c36
Plugin.model_admin isn't normally required. Also updating article docs.
Sep 27, 2016
866e5a1
Merge article/publishing changes from branch 'glamkit' into feature/n…
Sep 27, 2016
0eaad19
Adding and enabling Article-based Authors app.
Sep 27, 2016
7a33213
Merge branch 'develop' into feature/new_articles
Oct 11, 2016
318869a
WIP new articles refactor
Oct 11, 2016
2d23de9
Authors works now.
Oct 11, 2016
35a98a9
Move "Preview" button up by "view on site", and made the latter behav…
Oct 11, 2016
ade899b
Docs updates and removing unnecessary commented code.
Oct 12, 2016
c91ae5d
Fix tests
Oct 12, 2016
f68738c
content_collections tests passing
Oct 12, 2016
09acaf7
Merge branch 'develop' of github.com:ic-labs/django-icekit into develop
Oct 12, 2016
b21028f
Merge branch 'develop' into feature/new_articles
Oct 12, 2016
1367e6d
Repair publishing tests
Oct 12, 2016
8cd681f
Remove stray code
Oct 12, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/howto/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ ICEkit also has a simpler `PublishableArticle` model, which implements a
Publishable Fluent Contents model - ie it's a straightforward Django model
that happens to have rich content and be publishable. This is useful for more
normal Django collections-of-things, like Press Releases, People, Articles.

It's common to have a collecton of items with a paired `AbstractLayoutPage`
Page to list/navigate the collection. See the `icekit-press-releases`
project for a worked example of this.
Page to list/navigate the collection. See the `authors` app for a worked
example of this.

### Adding help text to a Placeholder

Expand Down
6 changes: 6 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,20 @@ Add custom functionality to your site.
* [Placeholders]
* [Publishing]
* [Page trees and mptt]
* [Articles]
* [Writing portable apps]


# Apps for ICEkit
## Included apps, pagetypes and plugins

* Apps:
* `authors` - descriptions of site contributors

* Page type plugins:
* `layout_page` - integrates with our flexible layouts system.
* `search_page` - integrates with [Haystack].
* `authors.page.AuthorListing` - a list of Authors
* Modular content plugins:
* `brightcove`
* `child_pages`
Expand Down Expand Up @@ -88,6 +93,7 @@ team at [the Interaction Consortium]: [labs@interaction.net.au](mailto:labs@inte
[Creating content plugins]: howto/plugins.md
[Deploying ICEkit]: howto/deployment.md
[Layouts]: topics/layouts.md
[Articles]: topics/articles.md
[Placeholders]: topics/placeholders.md
[Publishing]: topics/publishing.md
[Page trees and mptt]: topics/page-trees-and-mptt.md
Expand Down
184 changes: 184 additions & 0 deletions docs/topics/articles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# `Article`s and `ListingPage`s

`Article`s are publishable content types that live in collections. Normally
articles are mounted under a parent's URL.
Copy link
Member

@mrmachine mrmachine Sep 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Articles looks/reads badly in plain text and rendered markup. Could we just say "Article and ListingPage" in the heading and "Article is a publishable content type that lives in a collection"? Same applies everywhere in docs. We should add this to the doc about writing docs.

Also, it seems that ListingPage is an abstract model (but doesn't have an Abstract prefix or Base suffix like the others), and Article is a concrete implementation of ArticleBase (actually there are a few concrete Article models, so it's a little ambiguous).

If we're just talking about the concept of articles and listing pages, let's not write them as literals? E.g. "Articles and listing pages" as the heading. "Articles are publishable content types (ArticleBase subclasses) that live in collections (AbstractListingPage or ListingPageBase subclasses)." as the body?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's changed to AbstractListingPage and AbstractCollectedContent, and neither requires the other.


An example of an article is a Press Release, which would be mounted under a
Press Release Listing Page. The Listing Page has the URL "/press" and thus all
the Press Releases have the URL "/press/<slug>".

## `ArticleBase`

The `icekit.articles.models.ArticleBase` inherits `title`, `slug` and
publishing fields, and each `ArticleBase` subclass implements some key
functionality:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this should be icekit.articles.abstract_models.ArticleBase, and perhaps we should just list the full class name in the heading instead of listing it in short form in the head and long form in the body?

This might make the transition to rST a bit easier, as a headings/refs like .. model:: icekit.articles.abstract_models.ArticleBase will become a link target from other pages via :model:~icekit.articles.abstract_models.ArticleBase``(with justArticleBase as the link text).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs are updated, but didn't have time to get into the linkification.


1. **Links to parents.**
In order for an Article to know its URL (which is based on a parent's URL), it
should define a `parent` link, which is normally a link to a `ListingPage`.
Copy link
Member

@mrmachine mrmachine Sep 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds odd. Normally a listing page would be for a specific type of article, and would just list them all. No FK from individual article instances back to a listing page instance. The listing page might be configurable to filter the list by category.

The way this is described, I'd be worried that deleting a listing page will delete all of its linked articles.

And I wonder why this is necessary. Are we intending to arbitrarily link some articles to one listing page and others to a different listing page (with articles and listing page both being of the same type)?

Or link articles of different types (not polymorphic child types, concrete/polymorphic-parent article models) to a single article listing page?

And with a single FK from the article to the listing page, why not just make them page types and directly add them to the page tree?

And this means it will only appear in one page? Unless we also have additional listing pages that behave more typically.

I thought the idea was for articles/content to be completely independent of pages and the page tree, but be pulled into the site anywhere they are relevant by list pages (or other page types)?

Perhaps a bit more of an explanation would help to clarify the intention and provide rationale on why it is necessary or better simply having more typical listpage/article pairs, where the article is not coupled to a listing page.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for coupling an article to a ListingPage is so that they inherit the Page's URL and that there is only one URL per article, and that can be decided by editors. So in some sites, press releases will be at /press-releases/. In others, they'll at /media/releases/.

In some cases we're intending to link some articles to one listing page and some to another. SFMOMA's watch/read/listen articles are a case in point. In other cases, we're not. e.g. all press releases belong under press. In this case, I've defined @property parent to return the first draft press release listing page.

Other pages may (and do) list articles in different ways, but this arrangement is the one that defines the URL.

Agreed that the on_delete behaviour needs fixing, and documentation needs clarifying, and possibly ArticleBase should become ContentCollection or something.

The reason Articles are not page_types in the page tree is that a) they have different admin_listing requirements (a tree is not a useful way to view/sort Articles) b) the page tree gets very unwieldy with sometimes thousands of items to sort and filter c) we don't want Articles to be children of arbitrary pages, only a defined subset.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these objectives can be achieved without an FK to a ListingPage (or in fact any UrlNode/Page) instance.

The "one true URL" for a given press release (when there are multiple PressReleaseListingPage objects added to a page tree -- which is probably uncommon) would be the first (or a specifically chosen) PressReleaseListingPage instance as returned by app_reverse(multiple=True). Usually there will be only one. But if there are many, we just need to pick one to be the main/primary one.

Listing pages that need to show only a subset, e.g. watch/read/listen from SFMOMA, would have a category as a field on the PressReleaseListingPage that filters the PressRelease queryset being listed, or have the category as a URL parameter (e.g. /press/<category>/<slug>) to provide multiple list pages filtered by category.

I agree that making articles pages in the page tree is not a good idea. But it seems this implementation is half way attempting to do that by hard coding a link from an article to a specific page instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Press Release behaviour you describe is already implemented: https://github.com/ic-labs/icekit-press-releases/blob/master/press_releases/models.py#L76

Given the watch/read/listen from SFMOMA, how would you implement a listing page at /watch/ that lists a subset, and to serve that subset at /watch/<slug> ? It seems you're either hardcoding the URL or using a database link. My approach allows the latter, without precluding the former.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with the way it is done in SFMOMA? If we want multiple list pages, one for each category, all at top level URLs, just add a category field to the list page plugin and filter the objects being listed by category. If the category list pages can all be under a common URL, then include the category as a URL parameter. I think this is by far the more common case and least confusing or error prone solution to filtered list pages (vs. FKs from each object being listed to a specific list page instance). Having an FK field that is ignored (at best) or faked when not needed (which is probably most of the time) in order to support a less common / more complex case seems like a bad trade-off. And I'm still worried about cascade deletes. The other on_delete behaviours (protect, set null) might not be much better either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the parent is 'faked'. It's genuinely needed in order to figure out where the user wants the content to be mounted at, to figure out where the 'back' breadcrumb goes, etc. I'd ideally not call it 'parent' but if we do, then we get to reuse the templates for a Page.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant faked in the sense that some objects implement parent as a property that fetches an object to act as parent via some other mechanism (e.g. just picking the first one it finds) instead of having a parent FK.

There are one time setup steps for the end user in both cases. Create and publish a list page. Maybe it has a category field maybe it doesn't, but this difference shouldn't be a significant hurdle for users.

I'm not sure what you mean by the page needing the same slug as the category.

Once there's a published list page, they just create an article and select either an appropriate category or a parent instance. I don't see a huge difference for the user. It's probably easier to select a category from a select list than search for a specific list page in the page tree.

My reluctance to go ahead with this FK to a parent page right now is that I think it will be difficult to change or remove once anything is using it, and I think it's still not clear that this is definitely the right way to go.

Copy link
Contributor Author

@cogat cogat Oct 13, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Back to first principles. The need is to allow objects to appear at some url, such as "/watch/", where "watch" is a grouping term chosen in some way by the user. There are two candidate ways of doing this: assign a category, or assign a page.

If you need a page at /watch/ that lists the items, as we often do, then it seems convenient to create such a page, and link the items to it. That's what this pattern is.

If you don't need a page, you don't have to have one; you make "parent" link to a category model and use a traditional URL pattern. If you don't need a parent, then what you're doing is outside of what this code is helpful for.

If you like the listing page, but don't like the way items have a parent to it, you don't need to use the parent pattern.

If you need a page that filters items in a different way, then either extend/upgrade the listing page model or implement something else. No dramas.

The only time we need to use this pattern is when it is architecturally sensible: when there is content to be listed, and we need one or more pages to list them under. I don't think we're painting ourselves into a corner.

(I was sat with the team at SFMOMA, and I can tell you the page+category approach was opaque enough that I needed to do the setup for them every time. I myself had to sift through the code the first time to figure out what was going on. They expected to be able to see the article once they had attached the category, which didn't work, because the URL pattern for the article is defined by the Page.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This explanation is a little clearer to me. Thanks.

Still, parent is a very generic name to begin with and is also overloaded and has different implementations. If we're suggesting that parent might be an FK to UrlNode or to something else like Category (for a more typical implementation), we should say that in the docs. It sounded to me like it would always be a page (UrlNode).

If the only reason we're using the name parent is to re-use templates, maybe it's worth changing the name and using different templates?

Could we make the implementation consistent by insisting that parent must be an FK? I can imagine that this could easily cause us problems and inefficient queries.

Finally, are we doing anything to make the page selection easier? E.g. showing a model choice field that is filtered to show only FooListingPage instances? I can imagine that a popup version of the entire page tree change list page would not be very friendly to use, and that allowing any type of UrlNode instance to be linked as the parent for an article could be problematic.

Copy link
Contributor Author

@cogat cogat Oct 13, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Insisting on an FK might be OK, but I'd rather not make people choose 'press page' for every press release. At the moment I think we cross that bridge when we come to it, as the migration is likely to be straightforward.

FWIW, I'm implementing parent like this:
parent = models.ForeignKey(
"ArticleCategoryPage",
limit_choices_to={'publishing_is_draft': True},
on_delete=models.PROTECT,
)


1. **A view.**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be 2. (even if Markdown allows us to be lazy and renders it as an ordered list)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

If an Article is mounted under a `ListingPage` parent, the ListingPagePlugin
will call `get_response(request, parent)` on the article, which should
return the necessary `HttpResponse`. If the Article is a `fluent_contents`
model, the view functions are implemented by `fluent_contents`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe mention that this happens via ListingPagePlugin.get_view_response() which alters the argument list for all sub-URLs (adds page)? When I looked at the code, this wasn't immediately obvious where the page argument was coming from and I had to look at fluent source (because their docs were also light on the subject).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


There is also a class `icekit.articles.models.PolymorphicArticleBase` which
extends the Article class with django-polymorphic functionality, allowing
you to define articles of different shapes and mount them under the same
`parent` model.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we just make this the default/only option? A polymorphic parent model can be used itself (without a child). But it gives the option of adding additional slightly different types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No major reason. Defining the admin is a bit more unwieldy.


## `ListingPage`

The `icekit.articles.models.ListingPage` model is a page type that requires
`get_items()` and `get_visible_items()` to be defined in subclasses.
When viewed, ListingPage lists the items returned by `get_items()`.
`get_visible_items()` is necessary because an editor may wish to preview
unpublished items (that aren't returned by `get_items()`)
Copy link
Member

@mrmachine mrmachine Sep 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, icekit.articles.abstract_models.ListingPage. And should get_items() be named get_published_items()?


## Admins

For normal Articles, inherit from `ArticleAdminBase`. For polymorphic articles,
inherit from `PolymorphicArticleParentAdmin` and `PolymorphicArticleChildAdmin`
as shown in the example below.

## Bare-bones example

The following defines a minimal rich content Article, mounted under a
minimal `ArticleCategoryPage`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a different name/type of article in the example might avoid confusion (e.g. where every base and concrete model has some form of "article" in it). E.g. press release or essay?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


In `models.py`:

from django.db import models
from icekit.abstract_models import FluentFieldsMixin
from icekit.articles.abstract_models import ListingPage, ArticleBase


class ArticleCategoryPage(ListingPage):
def get_items(self):
unpublished_pk = self.get_draft().pk
return Article.objects.published().filter(parent_id=unpublished_pk)

def get_visible_items(self):
unpublished_pk = self.get_draft().pk
return Article.objects.visible().filter(parent_id=unpublished_pk)


class Article(ArticleBase, FluentFieldsMixin):
parent = models.ForeignKey(
ArticleCategoryPage,
limit_choices_to={'publishing_is_draft': True}
)

In `admin.py`:

from django.contrib import admin
from icekit.admin import FluentLayoutsMixin
from icekit.articles.admin import ArticleAdminBase
from .models import Article


@admin.register(Article)
class ArticleAdmin(ArticleAdminBase, FluentLayoutsMixin):
pass

In `page_type_plugins.py`:

from fluent_pages.extensions import page_type_pool
from icekit.page_types.layout_page.admin import LayoutPageAdmin
from icekit.articles.page_type_plugins import ListingPagePlugin
from .models import ArticleCategoryPage


@page_type_pool.register
class ArticleCategoryPagePlugin(ListingPagePlugin):
model = ArticleCategoryPage


## Bare-bones polymorphic example

The following defines a polymorphic structure of minimal rich content Article,
and a zero-content (except title) `RedirectArticle`.

In `models.py`:

from django.db import models
from django.http import HttpResponseRedirect
from fluent_pages.pagetypes.redirectnode.models import RedirectNode
from icekit.abstract_models import FluentFieldsMixin
from icekit.fields import ICEkitURLField
from icekit.articles.abstract_models import ListingPage, PolymorphicArticleBase
from django.utils.translation import ugettext_lazy as _


class ArticleCategoryPage(ListingPage):
def get_items(self):
unpublished_pk = self.get_draft().pk
return Article.objects.published().filter(parent_id=unpublished_pk)

def get_visible_items(self):
unpublished_pk = self.get_draft().pk
return Article.objects.visible().filter(parent_id=unpublished_pk)


class Article(PolymorphicArticleBase):
parent = models.ForeignKey(
ArticleCategoryPage,
limit_choices_to={'publishing_is_draft': True}
)

class LayoutArticle(Article, FluentFieldsMixin):
class Meta:
verbose_name = "Article"


class RedirectArticle(Article):
new_url = ICEkitURLField(
help_text=_('The URL to redirect to.')
)
redirect_type = models.IntegerField(
_("Redirect type"),
choices=RedirectNode.REDIRECT_TYPE_CHOICES,
default=302,
help_text=_(
"Use 'normal redirect' unless you want to transfer SEO ranking to the new page."
)
)

def get_response(self, request, *args, **kwargs):
response = HttpResponseRedirect(self.new_url)
response.status_code = self.redirect_type
return response

class Meta:
verbose_name = "Redirect"

In `admin.py`:

from django.contrib import admin
from icekit.admin import FluentLayoutsMixin
from icekit.articles.admin import PolymorphicArticleParentAdmin, \
PolymorphicArticleChildAdmin
from .models import Article, LayoutArticle, RedirectArticle


class ArticleChildAdmin(PolymorphicArticleChildAdmin):
base_model = Article


class LayoutArticleAdmin(ArticleChildAdmin, FluentLayoutsMixin):
base_model=LayoutArticle


class RedirectArticleAdmin(ArticleChildAdmin):
base_model=RedirectArticle


@admin.register(Article)
class ArticleParentAdmin(PolymorphicArticleParentAdmin):
base_model = Article
child_models = ((LayoutArticle, LayoutArticleAdmin),
(RedirectArticle, RedirectArticleAdmin),)


Finally, `page_type_plugins.py` is identical to the non-polymorphic example above.
2 changes: 1 addition & 1 deletion docs/topics/portable-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ In the app whose label is being updated, make the following changes:
with a deprecation warning.

* Add a `db_table: {old app label}` option to all `CreateModel` operations in
existing migrations, if not already defined, giving the current app label
existing migrations, if not already defined, giving the previous app label
as the value.

* Run `manage.py makemigrations {app_label}`. Django will detect that the
Expand Down
1 change: 1 addition & 0 deletions icekit/articles/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = '%s.apps.AppConfig' % __name__
114 changes: 102 additions & 12 deletions icekit/articles/abstract_models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from icekit.abstract_models import FluentFieldsMixin
from urlparse import urljoin

from django.template.response import TemplateResponse

from icekit.publishing.models import PublishingModel
from django.db import models
from polymorphic.models import PolymorphicModel
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this import came with django-polymorphic==0.8 -- if we're not using a try/except for the old import, we should add >=0.8 to setup.py.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confused about why this particular case warrants specifying a version in setup.py. Surely by the same argument every requirement in setup.py should have a minimum supported version? And new installations of ICEkit won't include polymorphic 0.8, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, if we know about any minimum version requirements they should be specified in setup.py. Same with any maximum version requirements or known incompatible versions (e.g. containing bugs that were subsequently fixed) to be excluded. However, until we actually know about a feature or implementation that is broken or missing, we don't need to add a random minimum version spec to every requirement. In this case, we know that imports were changed in 0.8 and if we don't want to support pre/post 0.8 with a try/except, we should exclude the known incompatible versions.

If it's easy enough (and a moved import seems pretty easy), I vote for maintaining compatibility. I believe elsewhere in ICEkit code we do try/except this import. But given the number of ICEkit installations out there and the low probability of needing to upgrade any of them to latest ICEkit without also being able to upgrade polymorphic, I'm also fine with dropping support for <=0.8. In which case we can remove the other try/except imports (if there are any).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version is pegged.


# TODO: this should ideally be in icekit.abstract_models, but doing so
# creates circular import errors.
class PublishableFluentModel(FluentFieldsMixin, PublishingModel):
class Meta:
abstract = True
from icekit.page_types.layout_page.abstract_models import AbstractLayoutPage
from django.db import models

class TitleSlugMixin(models.Model):
# TODO: this should perhaps become part of a wider ICEkit mixin that covers
Expand All @@ -22,12 +22,102 @@ def __unicode__(self):
return self.title


class PublishableArticle(PublishableFluentModel, TitleSlugMixin):
'''
Basic Article type (ie that forms the basis of independent collections of
publishable things).
'''
class ListingPage(AbstractLayoutPage):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AbstractListingPage or ListingPageBase? (I prefer the former, and it is used elsewhere already e.g. AbstractLayoutPage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used former.

"""
A Page type that serves lists of things. Good for
e.g. PressReleaseListingPage or ArticleCategoryPage.
"""
class Meta:
abstract = True

def get_items(self):
"""
Get the items that are listed on this page.
Remember that incoming relations will be on the draft version of
the page. Do something like this:

unpublished_pk = self.get_draft().pk
return Article.objects.published().filter(parent_id=unpublished_pk)

Editors normally expect to only see published() items in a listing, not
visible() items, unless clearly marked as such.

:return: the items that are associated with this page
"""
raise NotImplementedError(
"Please implement `get_items()` on your ListingPage model"
)


def get_visible_items(self):
"""
Get all items that are associated with this page and can be previewed
by the user.
Again, incoming relations will be on the draft version of
the page. Do something like this:

unpublished_pk = self.get_draft().pk
return Article.objects.visible().filter(parent_id=unpublished_pk)

Editors normally expect to only see published() items in a listing, not
visible() items, unless clearly marked as such.

:return: the items that are associated with this page
"""
raise NotImplementedError(
"Please implement `get_visible_items()` on your ListingPage model"
)

class ArticleBase(PublishingModel, TitleSlugMixin):
"""
Articles can be mounted into a publishable listing page,
which has the URL returned by `get_parent_url()`.

Subclasses should define a `parent` attribute, or override either
`get_parent_url()` or `get_absolute_url()`.

Subclasses should also define get_response for rendering itself, or
get_layout_template_name() in order to render Rich Content.
"""

class Meta:
abstract = True
unique_together = (('slug', 'publishing_is_draft', 'parent'),)

def get_parent_url(self):
if not hasattr(self, 'parent'):
raise NotImplementedError("PublishableArticle subclasses need to implement `parent` or `get_parent_url`.")

parent = self.parent.get_published() or self.parent.get_draft()
return parent.get_absolute_url()

def get_absolute_url(self):
parent_url = self.get_parent_url()
return urljoin(parent_url, self.slug) + "/"

def is_suppressed_message(self):
if not self.parent.has_been_published:
return "This article's parent needs to be published before it " \
"can be viewed by the public"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message should use the verbose name from the actual subclass instead of "article"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return None

def get_response(self, request, parent, *args, **kwargs):
context = {
'page': self,
'title': self.title
}
try:
return TemplateResponse(
request,
self.get_layout_template_name(),
context
)
except AttributeError:
raise AttributeError("You need to define "
"`get_layout_template_name()` on your `ArticleBase` model, "
"or override `get_response()`")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use the actual subclass name instead of ArticleBase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.



class PolymorphicArticleBase(PolymorphicModel, ArticleBase):
class Meta:
abstract = True
Loading