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.utils.translation import ugettext_lazy as _
from django.core import files
from django.core.files.base import ContentFile
from taggit.managers import TaggableManager
......@@ -80,6 +81,8 @@ class Article(VoteMixin):
("can_edit", "Can edit articles")
)
ordering = ["-published_at", "-updated_at", "-created_at"]
def __str__(self):
return self.title
......@@ -139,12 +142,11 @@ class Article(VoteMixin):
article.save()
return article
# Content extraction
def fetch_content(self):
if self.lang != "NA":
article = ArticleParser(url=self.url, language=lang_lower)
article = ArticleParser(url=self.url, language=self.lang.lower())
else:
article = ArticleParser(url=self.url)
......@@ -154,6 +156,32 @@ class Article(VoteMixin):
self.extracts = article.text
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):
from selenium import webdriver
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
......
......@@ -37,13 +37,13 @@
</div>
<div class="row">
<div class="col-sm-3">
<div class="col-sm-4"=>
{% if object.screenshot %}
<img class="img-responsive mb-4"
<img class="img-fluid mb-4"
src="/media/{{ object.screenshot }}">
{% endif %}
</div>
<div class="col-sm-9 lead">
<div class="col-sm-8 lead">
<h4><a target="_blank" href="{{object.url}}">{{object.title}}</a></h4>
<p>{{article.created_at.date}}
{% for t in article.tags.all %}
......
......@@ -22,6 +22,9 @@
{% else %}
<span class="ml-2"><strong>Article ID #{{object.id}}</strong></span>
{% 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>
<div class="ml-auto">
<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.conf.urls import url
from rp.views.articles import ArticleListFlux, ArticleEdit, ArticleDetailView
from rp.views.articles import ArticleListFlux, ArticleEdit, ArticleDetailView, ArticleList
urlpatterns = [
url(
r"^$",
ArticleList.as_view(),
name="public-article-list"
),
url(
r"^article/list/(?P<filter_view>\w+)",
login_required(ArticleListFlux.as_view()),
......
from django.http import HttpResponseRedirect
from django.views.generic.detail import DetailView
from django.views.generic.list import ListView
from django.views.generic.edit import UpdateView
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse, reverse_lazy
......@@ -18,6 +19,13 @@ from rp.models import Article
from .votes import UDList
class ArticleList(ListView):
model = Article
paginate_by = 10
template_name = "rp/article_list_public.html"
class ArticleListFlux(UDList):
model = Article
paginate_by = 10
......@@ -56,6 +64,17 @@ class ArticleEdit(PermissionRequiredMixin, UpdateView):
fields = ['screenshot', 'url', 'lang', 'title', 'tags', 'extracts']
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):
self.object = form.save()
......
......@@ -23,6 +23,7 @@ CONTRIB_APPS = [
"taggit",
"crispy_forms",
"django_markdown2",
"sorl.thumbnail",
"allauth",
"allauth.account",
......
......@@ -55,7 +55,6 @@ TEMPLATES = [
"django.templatetags.i18n",
]
},
},
]
......
......@@ -10,3 +10,5 @@
@import "components/navigation.css";
@import "components/table.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);
}
}
.input {
width : 100%;
background-color: white;
}
textarea.form-control.bold {
font-size: 20px;
form .row img {
max-width: 100%;
}
......@@ -13,29 +13,36 @@
}
@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');
font-weight: normal;
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'FiraSansMedium';
font-family: 'FiraSans';
src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-Medium.woff2') format('woff2');
font-weight: bold;
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'FiraSansSemiBold';
font-family: 'FiraSans';
src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-SemiBold.woff2') format('woff2');
font-weight: bold;
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'FiraSansBook';
font-family: 'FiraSans';
src: url('https://fonts.lqdn.fr/collections/Fira_Sans/FiraSans-Book.woff2') format('woff2');
font-weight: lighter;
font-weight: 300;
font-style: normal;
}
html, body {
font-family: FiraSansRegular;
font-family: FiraSans;
font-size: 16px;
}
......@@ -8,11 +8,11 @@ h1, h2 {
}
h3 {
font-family: FiraSansMedium;
font-weight: 500;
}
h4 {
font-family: FiraSansSemiBold;
font-weight: 600;
margin-top: 20px;
margin-bottom: 20px;
}
......@@ -31,6 +31,6 @@ strong {
.subtitle {
font-weight: bold;
font-family: FiraSansBook;
font-weight: 300;
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