Commit 94291e72 authored by cynddl's avatar cynddl

Replace page screenshots with header image extracted from metadata // update frontend layout

parent 532562b7
Pipeline #1152 passed with stages
in 2 minutes and 46 seconds
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-06-17 09:29
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('rp', '0015_article_created_by'),
]
operations = [
migrations.AlterModelOptions(
name='article',
options={'ordering': ['-published_at', '-updated_at', '-created_at'], 'permissions': (('can_change_status', 'Can change article status'), ('can_change_priority', 'Can change article priority'), ('can_vote', 'Can vote articles'), ('can_edit', 'Can edit articles')), 'verbose_name': 'Article', 'verbose_name_plural': 'Articles'},
),
]
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core import files
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
...@@ -80,6 +81,8 @@ class Article(VoteMixin): ...@@ -80,6 +81,8 @@ class Article(VoteMixin):
("can_edit", "Can edit articles") ("can_edit", "Can edit articles")
) )
ordering = ["-published_at", "-updated_at", "-created_at"]
def __str__(self): def __str__(self):
return self.title return self.title
...@@ -139,12 +142,11 @@ class Article(VoteMixin): ...@@ -139,12 +142,11 @@ class Article(VoteMixin):
article.save() article.save()
return article return article
# Content extraction # Content extraction
def fetch_content(self): def fetch_content(self):
if self.lang != "NA": if self.lang != "NA":
article = ArticleParser(url=self.url, language=lang_lower) article = ArticleParser(url=self.url, language=self.lang.lower())
else: else:
article = ArticleParser(url=self.url) article = ArticleParser(url=self.url)
...@@ -154,6 +156,32 @@ class Article(VoteMixin): ...@@ -154,6 +156,32 @@ class Article(VoteMixin):
self.extracts = article.text self.extracts = article.text
self.save() self.save()
def fetch_image(self):
import requests
import imghdr
if self.lang != "NA":
article = ArticleParser(url=self.url, language=self.lang.lower())
else:
article = ArticleParser(url=self.url)
article.download()
article.parse()
img_path = article.meta_img
if img_path:
resp = requests.get(img_path, stream=True)
if resp.status_code == requests.codes.ok:
fp = BytesIO()
fp.write(resp.content)
file_name_ext = imghdr.what(None, resp.content)
self.screenshot.save(
"screenshot-{0}.{1}".format(self.id, file_name_ext),
files.File(fp), save=True)
def fetch_screenshot(self): def fetch_screenshot(self):
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
......
...@@ -37,13 +37,13 @@ ...@@ -37,13 +37,13 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-3"> <div class="col-sm-4"=>
{% if object.screenshot %} {% if object.screenshot %}
<img class="img-responsive mb-4" <img class="img-fluid mb-4"
src="/media/{{ object.screenshot }}"> src="/media/{{ object.screenshot }}">
{% endif %} {% endif %}
</div> </div>
<div class="col-sm-9 lead"> <div class="col-sm-8 lead">
<h4><a target="_blank" href="{{object.url}}">{{object.title}}</a></h4> <h4><a target="_blank" href="{{object.url}}">{{object.title}}</a></h4>
<p>{{article.created_at.date}} <p>{{article.created_at.date}}
{% for t in article.tags.all %} {% for t in article.tags.all %}
......
...@@ -22,6 +22,9 @@ ...@@ -22,6 +22,9 @@
{% else %} {% else %}
<span class="ml-2"><strong>Article ID #{{object.id}}</strong></span> <span class="ml-2"><strong>Article ID #{{object.id}}</strong></span>
{% endif %} {% endif %}
<a class="btn btn-outline-primary" href="?fetch_content">Fetch content</a>
<a class="btn btn-outline-primary" href="?fetch_image">Fetch image</a>
</p> </p>
<div class="ml-auto"> <div class="ml-auto">
<span>Save and&nbsp;</span> <span>Save and&nbsp;</span>
......
{% extends "base.html" %}
{% load md2 %}
{% load thumbnail %}
{% block content-header %}
<div class="row justify-content-left">
<div class="col-md-6 offset-md-1">
<p>La revue de presse recense les articles de presse relatifs aux sujets de la Quadrature, compilés par ses bénévoles.
Voir aussi notre revue de presse internationale.</p>
</div>
<div class="col-md-4">
</div>
</div>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12 white-bg">
<div class="well">
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}"><i class="fa fa-chevron-left" aria-hidden="true"></i></a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}"><i class="fa fa-chevron-right" aria-hidden="true"></i></a>
{% endif %}
</span>
</div>
{% endif %}
<div class="row">
<div class="col-md-10 offset-1">
{% for article in object_list %}
<section class="article-card d-flex">
<div class="article-card-img">
{% thumbnail article.screenshot "800x400" crop="smart" as im %}
<img src="{{ im.url }}" width="400" height="200">
{% endthumbnail %}
<a href="{{article.url}}"><span class="fa fa-external-link fa-2x"></span></a>
</div>
<div class="article-card-content">
<p class="tags-list">
{% for t in article.tags.all %}
<a href="#" class="">{{t}}</a>
{% endfor %}
</p>
<h4><a href="{{article.url}}">{{article.title}}</a></h4>
{% comment %} <p class="subtitle">via Le Monde le {{article.created_at |date:'d/m'}}</p> {% endcomment %}
<div class="read-more-box">
{{ article.extracts |markdown }}
<p class="read-more"><a href="#" class="btn btn-secondary">Read More</a></p>
</div>
</div>
</section>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<script>
$(".read-more-box .btn").click(function() {
var totalHeight = 0;
var el = $(this);
var p = el.parent();
var up = p.parent();
var ps = up.find("p:not('.read-more')");
// measure how tall inside should be by adding together heights of all inside paragraphs (except read-more paragraph)
ps.each(function() {
totalHeight += $(this).outerHeight(true);
});
up.css({
// Set height to prevent instant jumpdown when max height is removed
"height": up.height(),
"max-height": 9999
}).animate({
"height": totalHeight
});
// fade out read-more
p.fadeOut();
// prevent jump-down
return false;
});
</script>
{% endblock %}
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.conf.urls import url from django.conf.urls import url
from rp.views.articles import ArticleListFlux, ArticleEdit, ArticleDetailView from rp.views.articles import ArticleListFlux, ArticleEdit, ArticleDetailView, ArticleList
urlpatterns = [ urlpatterns = [
url(
r"^$",
ArticleList.as_view(),
name="public-article-list"
),
url( url(
r"^article/list/(?P<filter_view>\w+)", r"^article/list/(?P<filter_view>\w+)",
login_required(ArticleListFlux.as_view()), login_required(ArticleListFlux.as_view()),
......
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.views.generic.list import ListView
from django.views.generic.edit import UpdateView from django.views.generic.edit import UpdateView
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
...@@ -18,6 +19,13 @@ from rp.models import Article ...@@ -18,6 +19,13 @@ from rp.models import Article
from .votes import UDList from .votes import UDList
class ArticleList(ListView):
model = Article
paginate_by = 10
template_name = "rp/article_list_public.html"
class ArticleListFlux(UDList): class ArticleListFlux(UDList):
model = Article model = Article
paginate_by = 10 paginate_by = 10
...@@ -56,6 +64,17 @@ class ArticleEdit(PermissionRequiredMixin, UpdateView): ...@@ -56,6 +64,17 @@ class ArticleEdit(PermissionRequiredMixin, UpdateView):
fields = ['screenshot', 'url', 'lang', 'title', 'tags', 'extracts'] fields = ['screenshot', 'url', 'lang', 'title', 'tags', 'extracts']
success_url = reverse_lazy("rp:article-list") success_url = reverse_lazy("rp:article-list")
def get(self, request, **kwargs):
self.object = self.get_object()
if 'fetch_content' in self.request.GET:
self.object.fetch_content()
elif 'fetch_image' in self.request.GET:
self.object.fetch_image()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
def form_valid(self, form): def form_valid(self, form):
self.object = form.save() self.object = form.save()
......
...@@ -23,6 +23,7 @@ CONTRIB_APPS = [ ...@@ -23,6 +23,7 @@ CONTRIB_APPS = [
"taggit", "taggit",
"crispy_forms", "crispy_forms",
"django_markdown2", "django_markdown2",
"sorl.thumbnail",
"allauth", "allauth",
"allauth.account", "allauth.account",
......
...@@ -55,7 +55,6 @@ TEMPLATES = [ ...@@ -55,7 +55,6 @@ TEMPLATES = [
"django.templatetags.i18n", "django.templatetags.i18n",
] ]
}, },
}, },
] ]
......
...@@ -12,3 +12,4 @@ django-allauth ...@@ -12,3 +12,4 @@ django-allauth
django-fsm django-fsm
django-markdown2 django-markdown2
url url
sorl-thumbnail
...@@ -10,3 +10,5 @@ ...@@ -10,3 +10,5 @@
@import "components/navigation.css"; @import "components/navigation.css";
@import "components/table.css"; @import "components/table.css";
@import "components/badge.css"; @import "components/badge.css";
@import "components/card.css";
@import "components/form.css";
.article-card {
margin-bottom: 2rem;
}
.article-card-img {
position: relative;
margin-right: 1rem;
background-color: var(--color-light-blue);
&:hover img { opacity: .2; }
width: 400px;
height: 200px;
& a {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
padding: 20px;
border: 1px solid #fff;
line-height: 1;
opacity: 0;
color: #fff;
}
&:hover a {
opacity: 1;
}
}
.article-card-content {
& h4 {
margin: 0.5rem 0 1rem 0;
}
& h4, & h4 a {
color: var(--color-black);
}
& .subtitle {
color: var(--color-medium-blue);
font-weight: normal;
}
& .tags-list {
margin: 0;
font-weight: 200;
font-size: 1.1rem;
}
& .tags-list a {
color: var(--color-black);
margin-right: 0.5rem;
}
}
.read-more-box {
max-height: 120px;
position: relative;
overflow: hidden;
color: var(--color-black);
& .read-more {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
margin: 0;
padding: 30px 0;
/* "transparent" only works here because == rgba(0,0,0,0) */
background-image: linear-gradient(to bottom, transparent, white);
}
}
form .row img {
max-width: 100%;
.input {
width : 100%;
background-color: white;
}
textarea.form-control.bold {
font-size: 20px;
} }
...@@ -13,29 +13,36 @@ ...@@ -13,29 +13,36 @@
} }
@font-face { @font-face {
font-family: 'FiraSansRegular'; font-family: 'FiraSans';
src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-Light.woff2') format('woff2');
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: 'FiraSans';
src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-Regular.woff2') format('woff2'); src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-Regular.woff2') format('woff2');
font-weight: normal; font-weight: 400;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'FiraSansMedium'; font-family: 'FiraSans';
src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-Medium.woff2') format('woff2'); src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-Medium.woff2') format('woff2');
font-weight: bold; font-weight: 500;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'FiraSansSemiBold'; font-family: 'FiraSans';
src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-SemiBold.woff2') format('woff2'); src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-SemiBold.woff2') format('woff2');
font-weight: bold; font-weight: 600;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'FiraSansBook'; font-family: 'FiraSans';
src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-Book.woff2') format('woff2'); src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-Book.woff2') format('woff2');
font-weight: lighter; font-weight: 300;
font-style: normal; font-style: normal;
} }
html, body { html, body {
font-family: FiraSansRegular; font-family: FiraSans;
font-size: 16px; font-size: 16px;
} }
...@@ -8,11 +8,11 @@ h1, h2 { ...@@ -8,11 +8,11 @@ h1, h2 {
} }
h3 { h3 {
font-family: FiraSansMedium; font-weight: 500;
} }
h4 { h4 {
font-family: FiraSansSemiBold; font-weight: 600;
margin-top: 20px; margin-top: 20px;
margin-bottom: 20px; margin-bottom: 20px;
} }
...@@ -31,6 +31,6 @@ strong { ...@@ -31,6 +31,6 @@ strong {
.subtitle { .subtitle {
font-weight: bold; font-weight: bold;
font-family: FiraSansBook; font-weight: 300;
color: var(--color-dark-blue); color: var(--color-dark-blue);
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment