Commit 206bfc61 authored by cynddl's avatar cynddl

Document the Article model

parent c32a9424
Pipeline #1190 passed with stages
in 1 minute and 57 seconds
...@@ -43,37 +43,60 @@ article content. You should aim at around 500 characters. Use bracket ellipsis ...@@ -43,37 +43,60 @@ article content. You should aim at around 500 characters. Use bracket ellipsis
class Article(VoteMixin): class Article(VoteMixin):
#: Logical state (eg. article submitted, published, or rejected)
status = FSMField(default='NEW', choices=STATUS_CHOICES, protected=True) status = FSMField(default='NEW', choices=STATUS_CHOICES, protected=True)
#: Original URL
url = models.URLField("URL", help_text=URL_HELP_TEXT) url = models.URLField("URL", help_text=URL_HELP_TEXT)
#: Language of the webpage
lang = models.CharField( lang = models.CharField(
_("Language"), choices=LANG_CHOICES, default="NA", max_length=50) _("Language"), choices=LANG_CHOICES, default="NA", max_length=50)
#: Plain-text Opengraph metadata
metadata = models.TextField( metadata = models.TextField(
_("Opengraph metadata"), blank=True, null=True) _("Opengraph metadata"), blank=True, null=True)
#: Screenshot or banner image for the original webpage
screenshot = models.ImageField( screenshot = models.ImageField(
_("Article screenshot"), blank=True, null=True) _("Article screenshot"), blank=True, null=True)
#: Article title
title = models.CharField( title = models.CharField(
_("Article title"), max_length=255, default="", _("Article title"), max_length=255, default="",
help_text=TITLE_HELP_TEXT) help_text=TITLE_HELP_TEXT)
#: Short name for the website (eg. "NY Times")
website = models.CharField(_("Website"), max_length=255, default="") website = models.CharField(_("Website"), max_length=255, default="")
#: Short content extracts (eg. two to three paragraphs)
extracts = models.TextField( extracts = models.TextField(
_("Content extracts"), blank=True, null=True, _("Content extracts"), blank=True, null=True,
#: First submission date
created_at = models.DateTimeField(_("Creation date"), auto_now_add=True) created_at = models.DateTimeField(_("Creation date"), auto_now_add=True)
#: Name of the user who first submitted the article
created_by = models.CharField(max_length=255, null=True) created_by = models.CharField(max_length=255, null=True)
#: Last update date
updated_at = models.DateTimeField(_("Last update"), auto_now=True) updated_at = models.DateTimeField(_("Last update"), auto_now=True)
#: Published date
published_at = models.DateTimeField( published_at = models.DateTimeField(
_("Publication date"), blank=True, null=True) _("Publication date"), blank=True, null=True)
#: priority: True if article have priority #: priority: True if article have priority
priority = models.BooleanField(default=False) priority = models.BooleanField(default=False)
#: List of short tags to describe the article (eg. "Privacy", "Copyright")
tags = TaggableManager(blank=True) tags = TaggableManager(blank=True)
class Meta: class Meta:
verbose_name = _("Article") verbose_name = _("Article")
verbose_name_plural = _("Articles") verbose_name_plural = _("Articles")
permissions = ( permissions = (
("can_change_status", "Can change article status"), ("can_change_status", "Can change article status"),
("can_change_priority", "Can change article priority"), ("can_change_priority", "Can change article priority"),
...@@ -81,9 +104,11 @@ class Article(VoteMixin): ...@@ -81,9 +104,11 @@ class Article(VoteMixin):
("can_edit", "Can edit articles") ("can_edit", "Can edit articles")
) )
#: By default, sort articles by published, updated, or created date
ordering = ["-published_at", "-updated_at", "-created_at"] ordering = ["-published_at", "-updated_at", "-created_at"]
def __str__(self): def __str__(self):
""" Returns article title. """
return self.title return self.title
# Finite state logic # Finite state logic
...@@ -91,32 +116,42 @@ class Article(VoteMixin): ...@@ -91,32 +116,42 @@ class Article(VoteMixin):
@transition(field=status, source='DRAFT', target='PUBLISHED', @transition(field=status, source='DRAFT', target='PUBLISHED',
permission="rp.can_change_status") permission="rp.can_change_status")
def publish(self): def publish(self):
""" Publish a complete draft. """
self.published_at = self.published_at =
@transition(field=status, source='NEW', target='DRAFT', @transition(field=status, source='NEW', target='DRAFT',
permission="rp.can_change_status") permission="rp.can_change_status")
def recover(self): def recover(self):
""" Force an article to be considered as a draft. """
pass pass
@transition(field=status, source=['NEW', 'DRAFT'], target='REJECTED', @transition(field=status, source=['NEW', 'DRAFT'], target='REJECTED',
permission="rp.can_change_status") permission="rp.can_change_status")
def reject(self): def reject(self):
""" Manual rejection of the article. """
pass pass
@transition(field=status, source='DRAFT', target='DRAFT', @transition(field=status, source='DRAFT', target='DRAFT',
permission="rp.can_change_priority") permission="rp.can_change_priority")
def set_priority(self): def set_priority(self):
""" Set the boolean priority of an article to True. """
self.priority = True self.priority = True
@transition(field=status, source='DRAFT', target='DRAFT', @transition(field=status, source='DRAFT', target='DRAFT',
permission="rp.can_change_priority") permission="rp.can_change_priority")
def unset_priority(self): def unset_priority(self):
""" Set the boolean priority of an article to False. """
self.priority = False self.priority = False
@transition(field=status, source='DRAFT', target='DRAFT') @transition(field=status, source='DRAFT', target='DRAFT')
@transition(field=status, source='NEW', @transition(field=status, source='NEW',
target=RETURN_VALUE('NEW', 'DRAFT'), permission="rp.can_vote") target=RETURN_VALUE('NEW', 'DRAFT'), permission="rp.can_vote")
def upvote(self, by=None): def upvote(self, by=None):
Upvote the article score for the given user and remove previous votes.
If the score crosses the threshold ```ARTICLE_SCORE_THRESHOLD```,
automatically moves the article from _NEW_ to _DRAFT_.
super(Article, self).upvote(by) super(Article, self).upvote(by)
if self.und_score >= ARTICLE_SCORE_THRESHOLD: if self.und_score >= ARTICLE_SCORE_THRESHOLD:
return 'DRAFT' return 'DRAFT'
...@@ -127,9 +162,18 @@ class Article(VoteMixin): ...@@ -127,9 +162,18 @@ class Article(VoteMixin):
@transition(field=status, source='DRAFT', target='DRAFT', @transition(field=status, source='DRAFT', target='DRAFT',
permission="rp.can_vote") permission="rp.can_vote")
def downvote(self, by=None): def downvote(self, by=None):
Downvote the article score for the given user and remove previous votes.
Draft articles can be downvoted but will not be moved back in the
_NEW_ queue.
super(Article, self).downvote(by) super(Article, self).downvote(by)
def add_new_url(url, by=None): def add_new_url(url, by=None):
""" Manually add a new article from its URL.
Verify if the article has not been submitted before and automatically
upvote for the given user if applicable.
url = cleanup_url(url) url = cleanup_url(url)
article, _ = Article.objects.get_or_create(url=url) article, _ = Article.objects.get_or_create(url=url)
...@@ -179,36 +223,3 @@ class Article(VoteMixin): ...@@ -179,36 +223,3 @@ class Article(VoteMixin):
"screenshot-{0}.{1}".format(, file_name_ext), "screenshot-{0}.{1}".format(, file_name_ext),
files.File(fp), save=True) files.File(fp), save=True)
def fetch_screenshot(self):
from selenium import webdriver
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
from PIL import Image
from pyvirtualdisplay import Display
with NamedTemporaryFile() as f, Display(visible=False, size=(3200, 1800)):
binary = None
if hasattr(env, "FIREFOX_BINARY_PATH"):
binary = FirefoxBinary(env.FIREFOX_BINARY_PATH)
profile = None
if hasattr(env, "FIREFOX_PROFILE_PATH"):
profile = webdriver.FirefoxProfile(env.FIREFOX_PROFILE_PATH)
driver = webdriver.Firefox(profile, firefox_binary=binary)
driver.set_window_size(1200, 1800)
screen = driver.get_screenshot_as_png()
im =
im.thumbnail((240, 360))
im_io = BytesIO(), format="PNG")
"screenshot-%i" %, ContentFile(im_io.getvalue()),
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment