Skip to content

Commit

Permalink
Allow featured title to be stored in redis or environment variable
Browse files Browse the repository at this point in the history
- This allows us to let users set the featured guide more reliably from the CMS
  UI itself, ONLY if the REDISCLOUD_URL environment variable and caching is
  enabled.
- We'll fallback to storing the featured title in the environment if
  the REDISCLOUD_URL environment variable is not available.
  • Loading branch information
durden committed May 12, 2016
1 parent b13a9d6 commit e6beae1
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 46 deletions.
18 changes: 18 additions & 0 deletions docs/deployment.rst
Expand Up @@ -108,6 +108,24 @@ Adding Redis caching on Heroku
3. The application will automatically start caching if you used the redis cloud addon described above. You can use a different Redis caching add-on, but you'll need to change the setup of the caching layer in `cache.py` appropriately.
4. See docs related to `using Python with redis on Heroku <https://devcenter.heroku.com/articles/rediscloud#using-redis-from-python>`_

Using Redis to store Featured Guide
-----------------------------------

By default, the featured guide is stored in an environment variable called
`FEATURED_TITLE`. This environment variable must be set in a way that will
persist across all running instances of the application. You can do this with
the Heroku CLI or admin panel, if you're running on Heroku.

A better solution for managing the featured guide is to use Redis. The CMS
will automatically use a single key in the 'caching' Redis database mentioned
above if you're using the `REDISCLOUD_URL` setup. So, there's no need to worry
about this if you are using the standard caching setup with `REDISCLOUD_URL`.

**You will not be able to set the featured guide via the CMS UI if you're not
using Redis to store the featured guide.** This is because setting an
environment variable via the application itself is unreliable if you're running
multiple instances of the application on multiple dynos or servers.

Useful Heroku add-ons
---------------------

Expand Down
26 changes: 0 additions & 26 deletions pskb_website/lib.py
Expand Up @@ -115,29 +115,3 @@ def lookup_url_redirect(requested_url):
pass

return new_url


def find_featured_article(articles=None):
"""
Find featured article in list of articles or published articles
:params articles: List of article objects to search for featured article or
use published articles if no list is given
:returns: Article object of featured article or None if not found
"""

featured = os.environ.get('FEATURED_TITLE')
if featured is None:
return None

if articles is None:
# FIXME: This should only fetch the most recent x number.
articles = list(models.get_available_articles(status=PUBLISHED))

featured = featured.strip()

for article in articles:
if article.title.strip() == featured:
return article

return None
4 changes: 4 additions & 0 deletions pskb_website/models/__init__.py
Expand Up @@ -19,6 +19,10 @@
from .file import read_redirects
from .file import update_article_listing

from .featured import allow_set_featured_article
from .featured import set_featured_article
from .featured import get_featured_article

from .user import find_user

from .email_list import add_subscriber
Expand Down
75 changes: 75 additions & 0 deletions pskb_website/models/featured.py
@@ -0,0 +1,75 @@
"""
Handle storing and retrieving featured article
The FEATURED_TITLE can only be set via the UI when there's an available
persistent storage mechanism like Redis configured. Otherwise the
FEATURED_TITLE is stored in an environment variable that cannot be set from the
UI. We do not allow setting of the environment variable from the UI because
the application could be running on multiple instances so setting a single
environment variable will only affect a single instance.
You can use Heroku's admin panel or CLI to set environment variables for all
instances of your application, if you're running on Heroku without Redis.
"""

import os

from .. import PUBLISHED
from .. import cache
from . import get_available_articles

KEY = 'FEATURED_TITLE'


def allow_set_featured_article():
"""
Return True or False if the ability to set the featured article is allowed
from the UI. If False, then the featured guide should be set via an
environment variable called FEATURED_TITLE.
"""

return cache.is_enabled()


def set_featured_article(title):
"""
Set featured article
:param title: Title of featured article
"""

# None for timeout b/c this should never expire
cache.save(KEY, title, timeout=None)


def get_featured_article(articles=None):
"""
Find featured article in list of articles or published articles
:params articles: List of article objects to search for featured article or
use published articles if no list is given
:returns: Article object of featured article or None if not found
"""

featured = None
if allow_set_featured_article():
featured = cache.get(KEY)

if featured is None:
featured = os.environ.get(KEY)

if featured is None:
return None

if articles is None:
# FIXME: This should only fetch the most recent x number.
articles = list(get_available_articles(status=PUBLISHED))

featured = featured.strip()

for article in articles:
# Don't allow surrounding spaces to mismatch
if article.title.strip() == featured:
return article

return None
21 changes: 9 additions & 12 deletions pskb_website/templates/article.html
Expand Up @@ -155,20 +155,17 @@ <h4>Contributors</h4>
</form>
{% endif %}

{% if collaborator %}
{% if article.published %}
<form action="/feature/" method="POST">
<input name="title" type="hidden" value="{{article.title}}"/>
<input name="stack" type="hidden" value="{{article.stacks[0]}}"/>
<button type="submit" class="btn btn-success btn-xs">
Set as featured guide
<span class="glyphicon glyphicon-level-up" aria-hidden="true"></span>
</button>
</form>
{% endif %}
{% if allow_set_featured %}
<form action="/feature/" method="POST">
<input name="title" type="hidden" value="{{article.title}}"/>
<input name="stack" type="hidden" value="{{article.stacks[0]}}"/>
<button type="submit" class="btn btn-success btn-xs">
Set as featured guide
<span class="glyphicon glyphicon-level-up" aria-hidden="true"></span>
</button>
</form>
{% endif %}


{% if branches %}
<h5>Community suggestions</h5>
<ul>
Expand Down
16 changes: 8 additions & 8 deletions pskb_website/views.py
Expand Up @@ -2,8 +2,6 @@
Main views of PSKB app
"""

import os

from flask import redirect, url_for, session, request, render_template, flash, g

from . import PUBLISHED, IN_REVIEW, DRAFT, STATUSES, SLACK_URL
Expand All @@ -15,7 +13,6 @@
from . import filters
from . import utils
from .lib import (
find_featured_article,
login_required,
is_logged_in,
lookup_url_redirect,
Expand Down Expand Up @@ -206,7 +203,7 @@ def my_drafts():
g.drafts_active = True
articles = models.get_articles_for_author(session['login'],
status=DRAFT)
featured_article = find_featured_article()
featured_article = models.get_featured_article()

return render_template('index.html', articles=articles,
featured_article=featured_article)
Expand Down Expand Up @@ -437,13 +434,17 @@ def render_article_view(request_obj, article, only_visible_by_user=None):
# 'discussion' for every article and there's no way to delete them!
allow_comments = not app.debug

allow_set_featured = collaborator and (
models.allow_set_featured_article()) and (
article.published)

return render_template('article.html',
article=article,
allow_delete=allow_delete,
canonical_url=canonical_url,
article_identifier=article_identifier,
branches=branches,
collaborator=collaborator,
allow_set_featured=allow_set_featured,
user=user,
publish_statuses=publish_statuses,
redirect_url=redirect_url,
Expand Down Expand Up @@ -669,8 +670,7 @@ def set_featured_title():

return redirect(url)

os.environ['FEATURED_TITLE'] = article.title

models.set_featured_article(article.title)
flash('Featured guide updated', category='info')

return redirect(url_for('index'))
Expand Down Expand Up @@ -709,7 +709,7 @@ def render_published_articles(status_code=200):
# FIXME: This should only fetch the most recent x number.
articles = list(models.get_available_articles(status=PUBLISHED))

featured_article = find_featured_article(articles)
featured_article = models.get_featured_article(articles)
if featured_article:
articles.remove(featured_article)

Expand Down

0 comments on commit e6beae1

Please sign in to comment.