Skip to content

Commit

Permalink
Merge branch 'release/2.1.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastian-code committed Apr 13, 2020
2 parents 59c2cb8 + c2a17f2 commit 95afc2d
Show file tree
Hide file tree
Showing 15 changed files with 265 additions and 50 deletions.
2 changes: 1 addition & 1 deletion bootcamp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "2.0.0"
__version__ = "2.1.1"
__version_info__ = tuple(
[
int(num) if num.isdigit() else num
Expand Down
79 changes: 78 additions & 1 deletion bootcamp/helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import re
from urllib.parse import urljoin

from django.core.exceptions import PermissionDenied
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.http import HttpResponseBadRequest
from django.views.generic import View
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator

import bs4
import requests


def paginate_data(qs, page_size, page, paginated_type, **kwargs):
Expand Down Expand Up @@ -79,3 +85,74 @@ def update_votes(obj, user, value):
user=user, defaults={"value": value},
)
obj.count_votes()


def fetch_metadata(text):
"""Method to consolidate workflow to recover the metadata of a page of the first URL found a in
a given text block.
:requieres:
:param text: Block of text of any lenght
"""
urls = get_urls(text)
try:
return get_metadata(urls[0])

except IndexError:
return None


def get_urls(text):
"""Method to look for all URLs in a given text, extract them and return them as a tuple of urls.
:requires:
:param text: A valid block of text of any lenght.
:returns:
A tuple of valid URLs extracted from the text.
"""
regex = r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"
return re.findall(regex, text)


def get_metadata(url):
"""This function looks for the page of a given URL, extracts the page content and parses the content
with bs4. searching for the page meta tags giving priority to the Open Graph Protocol
https://ogp.me/, then it returns the metadata in case there is any, or tries to build one.
:requires:
:param url: Any valid URL to search for.
:returns:
A dictionary with metadata from a given webpage.
"""
response = requests.get(url)
soup = bs4.BeautifulSoup(response.content)
ogs = soup.html.head.find_all(property=re.compile(r"^og"))
data = {og.get("property")[3:]: og.get("content") for og in ogs}
if not data.get("url"):
data["url"] = url

if not data.get("title"):
data["title"] = soup.html.title.text

if not data.get("image"):
images = soup.find_all("img")
if len(images) > 0:
data["image"] = urljoin(url, images[0].get("src"))

if not data.get("description"):
data["description"] = ""
for text in soup.body.find_all(string=True):
if (
text.parent.name != "script"
and text.parent.name != "style"
and not isinstance(text, bs4.Comment)
):
data["description"] += text

data["description"] = re.sub("\n|\r|\t", " ", data["description"])
data["description"] = re.sub(" +", " ", data["description"])
data["description"] = data["description"].strip()[:255]

return data
4 changes: 2 additions & 2 deletions bootcamp/messager/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,6 @@ def send_message(sender, recipient, message):
"recipient": str(recipient),
}
transaction.on_commit(
lambda: async_to_sync(channel_layer.group_send)(recipient.username,
payload))
lambda: async_to_sync(channel_layer.group_send)(recipient.username, payload)
)
return new_message
38 changes: 38 additions & 0 deletions bootcamp/news/migrations/0002_auto_20200405_1227.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 3.0.3 on 2020-04-05 12:27

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("news", "0001_initial"),
]

operations = [
migrations.AddField(
model_name="news",
name="meta_description",
field=models.TextField(max_length=255, null=True),
),
migrations.AddField(
model_name="news",
name="meta_image",
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name="news",
name="meta_title",
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name="news",
name="meta_type",
field=models.CharField(max_length=255, null=True),
),
migrations.AddField(
model_name="news",
name="meta_url",
field=models.CharField(max_length=2048, null=True),
),
]
14 changes: 14 additions & 0 deletions bootcamp/news/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from channels.layers import get_channel_layer

from bootcamp.notifications.models import Notification, notification_handler
from bootcamp.helpers import fetch_metadata


class News(models.Model):
Expand All @@ -32,6 +33,11 @@ class News(models.Model):
settings.AUTH_USER_MODEL, blank=True, related_name="liked_news"
)
reply = models.BooleanField(verbose_name=_("Is a reply?"), default=False)
meta_url = models.CharField(max_length=2048, null=True)
meta_type = models.CharField(max_length=255, null=True)
meta_title = models.CharField(max_length=255, null=True)
meta_description = models.TextField(max_length=255, null=True)
meta_image = models.CharField(max_length=255, null=True)

class Meta:
verbose_name = _("News")
Expand All @@ -42,6 +48,14 @@ def __str__(self):
return str(self.content)

def save(self, *args, **kwargs):
# extract metada from content url
data = fetch_metadata(self.content)
self.meta_url = data.get("url")
self.meta_type = data.get("type", "website")
self.meta_title = data.get("title")
self.meta_description = data.get("description")
self.meta_image = data.get("image")

super().save(*args, **kwargs)
if not self.reply:
channel_layer = get_channel_layer()
Expand Down
Empty file.
12 changes: 12 additions & 0 deletions bootcamp/news/templatetags/urlize_target_blank.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django import template
from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe
from django.utils.html import urlize as urlize_impl

register = template.Library()


@register.filter(is_safe=True, needs_autoescape=True)
@stringfilter
def urlize_target_blank(value, autoescape=None):
return value.replace("<a", '<a target="_blank"')
2 changes: 1 addition & 1 deletion bootcamp/news/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def get_thread(request):
news = News.objects.get(pk=news_id)
news_html = render_to_string("news/news_single.html", {"news": news})
thread_html = render_to_string(
"news/news_thread.html", {"thread": news.get_thread()}
"news/news_thread.html", {"thread": news.get_thread(), "request": request}
)
return JsonResponse({"uuid": news_id, "news": news_html, "thread": thread_html})

Expand Down
52 changes: 41 additions & 11 deletions bootcamp/static/css/news.css
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
.stream {
margin: 2em 0 0 0;
padding: 0;
}

.stream li {
.infinite-container li {
list-style: none;
border-style: solid none solid none;

}

.stream {
margin: 2em 0 0 0;
padding: 0;
}

.stream li:last-child {
border-bottom: none;
}
Expand All @@ -35,11 +35,6 @@
border-radius: 50%;
}

.interaction {
padding-left: 1.5em;
margin-bottom: 1em
}

.interaction a {
margin-right: 1.3em;
font-size: 1.1em;
Expand Down Expand Up @@ -159,3 +154,38 @@
.remove-news:hover {
color: #333333;
}

.meta.card {
background-color: #f8f8f8;
margin-bottom: 10px;
color: #6c757d;
}

.meta.card:hover {
background-color: #fff;
}

.meta .card-body {
padding: 0.8rem;
}

.meta .card-title, .meta .card-text, .meta .card-btn{
font-size: 0.8em;
line-height: 1.4em;
margin: 0;
}

.meta .card-img-top{
height: 100px;
background: center no-repeat #ccc;
background-size: cover;
}

.meta .card-text{
margin-bottom: 5px;
}

.meta:hover .btn{
background-color: #888;
color: #FFF;
}
9 changes: 7 additions & 2 deletions bootcamp/templates/articles/article_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ <h5 class="card-header">{% trans 'Leave a Comment' %}:</h5>
{% get_comment_form for article as form %}
<form action="{% comment_form_target %}" method="POST">
{% csrf_token %}
{{ form|crispy }}
{{ form.comment|as_crispy_field }}
{{ form.honeyspot }}
{{ form.content_type }}
{{ form.object_pk }}
{{ form.timestamp }}
{{ form.security_hash }}
<input type="hidden" name="next" value="{% url 'articles:article' article.slug %}" />
<input type="submit" value="{% trans 'Add comment' %}" id="id_submit" />
</form>
Expand All @@ -67,7 +72,7 @@ <h5 class="card-header">{% trans 'Leave a Comment' %}:</h5>
{% endthumbnail %}
<div class="media-body">
<h5 class="mt-0">{{ comment.user.get_profile_name|title }}</h5>
{{ comment }}
{{ comment.comment|linebreaks }}
</div>
</div>
{% endfor %}
Expand Down
51 changes: 34 additions & 17 deletions bootcamp/templates/news/news_single.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{% load i18n %}
{% load humanize static %}
{% load thumbnail %}
{% load urlize_target_blank %}

<li class="infinite-item card" news-id="{{ news.uuid_id }}">
<div class="card-body">
Expand All @@ -22,24 +23,40 @@
<a href="{% url 'users:detail' news.user.username %}">{{ news.user.get_profile_name|title }}</a>
</strong>
</p>
<p>{{ news }}</p>
{% if news.image %}
<img class="card-img-top" src="{{ news.image }}" alt="Card image cap">
<p>{{ news|urlize|urlize_target_blank }}</p>

{% if news.meta_url %}
<a href="{{ news.meta_url }}" target="_blank" class="card meta">
{% if news.meta_image %}
<div class="card-img-top" style="background-image: url({{ news.meta_image }})"></div>
{% endif %}
<div class="card-body">
{% if news.meta_title %}
<h5 class="card-title">{{ news.meta_title }}</h5>
{% endif %}
{% if news.meta_description %}
<p class="card-text">{{ news.meta_description }}</p>
{% endif %}
{% if news.meta_url %}
<p class="card-btn">{{ news.meta_url }}</p>
{% endif %}
</div>
</a>
{% endif %}
<div class="interaction" id="interaction">
<a href="#" class="like" title="{% for i in news.get_likers %}{{ i }}&#10;{% endfor %}">
{% if request.user in news.get_likers %}
<i class="heart fa fa-heart" aria-hidden="true"></i>
{% else %}
<i class="heart fa fa-heart-o" aria-hidden="true"></i>
{% endif %}
<span class="like-count">{{ news.count_likers }}</span>
</a>
<a href="#" class="comment"><i class="fa fa-comment-o" aria-hidden="true"></i>
<span class="comment-count">{{ news.count_thread }}</span>
</a>
<span class="timestamp">{{ news.timestamp|naturaltime }}</span>
</div>
</div>
</div>
<div class="interaction" id="interaction">
<a href="#" class="like" title="{% for i in news.get_likers %}{{ i }}&#10;{% endfor %}">
{% if request.user in news.get_likers %}
<i class="heart fa fa-heart" aria-hidden="true"></i>
{% else %}
<i class="heart fa fa-heart-o" aria-hidden="true"></i>
{% endif %}
<span class="like-count">{{ news.count_likers }}</span>
</a>
<a href="#" class="comment"><i class="fa fa-comment-o" aria-hidden="true"></i>
<span class="comment-count">{{ news.count_thread }}</span>
</a>
<span class="timestamp">{{ news.timestamp|naturaltime }}</span>
</div>
</li>
Loading

0 comments on commit 95afc2d

Please sign in to comment.