Made Articles get stored in the DB and added image fetching

This commit is contained in:
Sam Collins 2017-01-08 03:49:33 +00:00
parent 0a2ad28b7c
commit a3e44d640a
No known key found for this signature in database
GPG Key ID: 233C5943C800FE30
18 changed files with 435 additions and 55 deletions

5
.gitignore vendored
View File

@ -1,4 +1,5 @@
media/* mhackspace/media/*
.idea/ .idea/
__pycache__/ __pycache__/
node_modules node_modules
src

View File

@ -255,7 +255,7 @@ INSTALLED_APPS += ("compressor", )
STATICFILES_FINDERS += ("compressor.finders.CompressorFinder", ) STATICFILES_FINDERS += ("compressor.finders.CompressorFinder", )
# Location of root django.contrib.admin URL, use {% url 'admin:index' %} # Location of root django.contrib.admin URL, use {% url 'admin:index' %}
ADMIN_URL = r'^admin/' ADMIN_URL = '^admin/'
# Your common stuff: Below this line define 3rd party library settings # Your common stuff: Below this line define 3rd party library settings
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -22,7 +22,7 @@ urlpatterns = [
url(r'^members/$', MemberListView.as_view(), name='members'), url(r'^members/$', MemberListView.as_view(), name='members'),
# Django Admin, use {% url 'admin:index' %} # Django Admin, use {% url 'admin:index' %}
url(settings.ADMIN_URL, admin.site.urls), url(r'{}'.format(settings.ADMIN_URL), admin.site.urls),
# User management # User management
url(r'^users/', include('mhackspace.users.urls', namespace='users')), url(r'^users/', include('mhackspace.users.urls', namespace='users')),

View File

@ -1,12 +1,38 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import ModelAdmin from django.contrib.admin import ModelAdmin
from mhackspace.feeds.models import Feed from django.conf.urls import url
from django.http import HttpResponseRedirect
from django.urls import reverse
from mhackspace.feeds.models import Feed, Article
from mhackspace.feeds.helper import import_feeds
@admin.register(Feed) @admin.register(Feed)
class FeedAdmin(ModelAdmin): class FeedAdmin(ModelAdmin):
list_display = ('url', 'author', 'tags', 'enabled') list_display = ('title', 'home_url', 'author', 'tags', 'enabled')
list_filter = ('enabled', ) list_filter = ('enabled',)
@admin.register(Article)
class ArticleAdmin(ModelAdmin):
date_hierarchy = 'date'
list_display = ('title', 'url', 'feed', 'date', 'displayed')
list_filter = ('displayed', 'feed', 'feed__author')
readonly_fields = ('original_image',)
ordering = ('-date',)
def get_urls(self):
urls = super(ArticleAdmin, self).get_urls()
my_urls = [
url(r'^import/$', self.admin_site.admin_view(self.import_articles))
]
return my_urls + urls
def import_articles(self, request):
imported = import_feeds()
self.message_user(request, 'Successfully imported %s articles' % len(imported))
return HttpResponseRedirect(reverse('admin:feeds_article_changelist'))
admin.site.site_title = 'Maidstone Hackspace Admin Area' admin.site.site_title = 'Maidstone Hackspace Admin Area'
admin.site.site_header = 'Maidstone Hackspace Admin Area' admin.site.site_header = 'Maidstone Hackspace Admin Area'

View File

@ -3,8 +3,10 @@
"model": "feeds.feed", "model": "feeds.feed",
"pk": 1, "pk": 1,
"fields": { "fields": {
"url": "http://waistcoatforensicator.blogspot.com/feeds/posts/default?alt=rss", "home_url": "http://thearduinoguy.org/",
"author": "Simon Ridley", "feed_url": "http://thearduinoguy.org/?feed=rss2",
"title": "The Arduino Guy",
"author": "Mike McRoberts",
"tags": "", "tags": "",
"image": "", "image": "",
"enabled": true "enabled": true
@ -14,19 +16,23 @@
"model": "feeds.feed", "model": "feeds.feed",
"pk": 2, "pk": 2,
"fields": { "fields": {
"url": "http://www.matthewbeddow.co.uk/?feed=rss2", "home_url": "http://waistcoatforensicator.blogspot.com/",
"author": "Mathew Beddow", "feed_url": "http://waistcoatforensicator.blogspot.com/feeds/posts/default?alt=rss",
"tags": "tech", "title": "Waistcoat Forensicator",
"author": "Simon Ridley",
"tags": "",
"image": "", "image": "",
"enabled": true "enabled": false
} }
}, },
{ {
"model": "feeds.feed", "model": "feeds.feed",
"pk": 3, "pk": 3,
"fields": { "fields": {
"url": "http://blog.digitaloctave.co.uk/rss.xml", "home_url": "http://www.matthewbeddow.co.uk/",
"author": "Oliver Marks", "feed_url": "http://www.matthewbeddow.co.uk/?feed=rss2",
"title": "Mathew Beddow",
"author": "Mathew Beddow",
"tags": "", "tags": "",
"image": "", "image": "",
"enabled": true "enabled": true
@ -36,8 +42,10 @@
"model": "feeds.feed", "model": "feeds.feed",
"pk": 4, "pk": 4,
"fields": { "fields": {
"url": "http://webboggles.com/feed/", "home_url": "http://blog.digitaloctave.co.uk",
"author": "Ilya Titov", "feed_url": "http://blog.digitaloctave.co.uk/rss.xml",
"title": "Oliver Marks",
"author": "Oliver Marks",
"tags": "", "tags": "",
"image": "", "image": "",
"enabled": true "enabled": true
@ -47,8 +55,10 @@
"model": "feeds.feed", "model": "feeds.feed",
"pk": 5, "pk": 5,
"fields": { "fields": {
"url": "http://thearduinoguy.org/?feed=rss2", "home_url": "http://webboggles.com/",
"author": "Mike McRoberts", "feed_url": "http://webboggles.com/feed/",
"title": "Ilya Titov",
"author": "Ilya Titov",
"tags": "", "tags": "",
"image": "", "image": "",
"enabled": true "enabled": true
@ -58,11 +68,97 @@
"model": "feeds.feed", "model": "feeds.feed",
"pk": 6, "pk": 6,
"fields": { "fields": {
"url": "https://feeds.feedburner.com/projects-jl", "home_url": "https://jamesleighton.com/",
"author": "James", "feed_url": "https://feeds.feedburner.com/projects-jl",
"title": "James Leighton",
"author": "James Leighton",
"tags": "", "tags": "",
"image": "", "image": "",
"enabled": false "enabled": false
} }
},
{
"model": "feeds.article",
"pk": 507,
"fields": {
"url": "http://thearduinoguy.org/433mhz-radio-controlled-heating-controller-with-blynk-ui/",
"feed": 1,
"title": "433MHz Radio Controlled Heating Controller with Blynk UI",
"original_image": "http://thearduinoguy.org/wp-content/uploads/2016/12/Thermostat_bb-1-578x600.png",
"image": "",
"description": "<p>433MHz Radio Controlled Heating Controller with Blynk\u00a0UI This is a project to control your boiler using a 433MHz radio module. I have used a simple and cheap OOK radio module [\u2026]</p>",
"displayed": true,
"date": "2016-12-09T14:08:20Z"
}
},
{
"model": "feeds.article",
"pk": 508,
"fields": {
"url": "http://webboggles.com/wacom-intuos-pro-manufacturing-fault-leads-to-loss-of-touch/",
"feed": 1,
"title": "Wacom Intuos Pro manufacturing fault leads to loss of touch.",
"original_image": "http://webboggles.com/wp-content/uploads/2016/12/IMG_20161201_215435.jpg",
"image": "",
"description": "<p>I love using my Wacom for work and carry it around on daily basis. Recently the touch stopped working. As the tablet is 3 years old and out of warranty I decided to disassemble it and peek inside to see if I could mend it myself. And here is the problem \u2014 the USB connector\u2019s \u2026 <a href=\"http://webboggles.com/wacom-intuos-pro-manufacturing-fault-leads-to-loss-of-touch/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Wacom Intuos Pro manufacturing fault leads to loss of touch.</span></a></p>",
"displayed": true,
"date": "2016-12-01T23:53:08Z"
}
},
{
"model": "feeds.article",
"pk": 509,
"fields": {
"url": "http://thearduinoguy.org/neopixel-ring-clock/",
"feed": 1,
"title": "NeoPixel Ring Clock",
"original_image": "http://thearduinoguy.org/wp-content/uploads/2016/11/20161129_085040-1.jpg",
"image": "article/neopixel-ring-clock.jpg",
"description": "<p>I recently bought a 24 LED NeoPixel Ring. These things are great and I would highly recommend you getting one as they are great fun and very easy to use. [\u2026]</p>",
"displayed": true,
"date": "2016-11-29T09:11:17Z"
}
},
{
"model": "feeds.article",
"pk": 510,
"fields": {
"url": "http://webboggles.com/spectacular-vr-for-nexus-5/",
"feed": 1,
"title": "Spectacular VR for Nexus 5",
"original_image": "http://webboggles.com/wp-content/uploads/2016/03/IMG_0985.jpg",
"image": "",
"description": "<p>This is a quick and easy 3D print that turns a Nexus 5 phone into a VR device, inspired by google cardboard. DIY: STL files on Thingiverse 2x M3 bolts and nuts are required, or just use cable ties. 2x 28mm bi-convex lenses (The lenses can be ordered on ebay for a couple of pounds)</p>",
"displayed": true,
"date": "2016-03-01T16:59:12Z"
}
},
{
"model": "feeds.article",
"pk": 511,
"fields": {
"url": "http://thearduinoguy.org/lasertag-project/",
"feed": 1,
"title": "LaserTag Project",
"original_image": "http://thearduinoguy.org/wp-content/uploads/2016/08/LASERTAG-800x532.jpg",
"image": "article/lasertag-project.jpg",
"description": "<p>Along with some other guys from Maidstone Hackspace we have started work on a LaserTag project. I seem to have made the most progress so far and the prototype below [\u2026]</p>",
"displayed": true,
"date": "2016-08-31T09:57:13Z"
}
},
{
"model": "feeds.article",
"pk": 512,
"fields": {
"url": "http://webboggles.com/attiny85-game-kit-assembly-instructions/",
"feed": 1,
"title": "Attiny Arcade keychain game kit",
"original_image": "http://webboggles.com/wp-content/uploads/2016/01/IMG_3424-300x246.jpg",
"image": "",
"description": "<p>There has been a lot of updates to the Attiny Arcade keychain game project most notably the production of kits. I will add all the relevant information to this post and make it sticky. Buy via webboggles shop or on ebay. If you want to get in touch about the kit please tweet @webboggles, otherwise \u2026 <a href=\"http://webboggles.com/attiny85-game-kit-assembly-instructions/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Attiny Arcade keychain game kit</span></a></p>",
"displayed": true,
"date": "2016-01-17T00:34:00Z"
}
} }
] ]

View File

@ -1,18 +1,78 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django import template import os
from mhackspace.feeds.models import Feed import logging
from urllib.request import urlretrieve
from django.core.files import File
from django.utils.timezone import make_aware
from django.core.management import call_command
from scaffold.readers.rss_reader import feed_reader from scaffold.readers.rss_reader import feed_reader
register = template.Library() from mhackspace.feeds.models import Feed, Article
logger = logging.getLogger(__name__)
def import_feeds(feed=False):
remove_old_articles()
rss_articles = feed_reader(get_active_feeds(feed))
articles = []
for article in rss_articles:
articles.append(Article(
url=article['url'], # @olymk2 why cant I do article.url here?
feed=Feed.objects.get(pk=1), # fixme: Nice hack :)
title=article['title'],
original_image=article['image'],
description=article['description'],
date=make_aware(article['date'])
))
articles = Article.objects.bulk_create(articles)
download_remote_images()
return articles
def remove_old_articles():
for article in Article.objects.all():
article.image.delete(save=False)
Article.objects.all().delete()
def download_remote_images():
for article in Article.objects.all():
if not article.original_image:
continue
try:
result = urlretrieve(article.original_image.__str__())
article.image.save(
os.path.basename(article.original_image.__str__()),
File(open(result[0], 'rb'))
)
article.save()
except:
logger.exception('Unable to download remote image for %s' % article.original_image)
render_images()
def render_images():
# todo: extract logic and manually render images
call_command('rendervariations', 'feeds.Article.image', '--replace')
def get_active_feeds(feed=False):
if feed is not False:
feeds = Feed.objects.filter(pk__in=feed)
else:
feeds = Feed.objects.all()
@register.inclusion_tag('feed_tiles.html')
def fetch_feeds():
for feed
rss_feeds = [] rss_feeds = []
for feed in Feed.objects.all(): for feed in feeds:
if feed.enabled is False:
continue
rss_feeds.append({ rss_feeds.append({
'author':'Simon Ridley', 'author': feed.author,
'url': 'http://waistcoatforensicator.blogspot.com/feeds/posts/default?alt=rss' 'url': feed.feed_url
}) })
return rss_feeds
return feed_reader(rss_feeds)

View File

@ -0,0 +1,19 @@
from django.core.management.base import BaseCommand
from mhackspace.feeds.helper import import_feeds
class Command(BaseCommand):
help = 'Imports the RSS feeds from active blogs'
def add_arguments(self, parser):
parser.add_argument(
'blog_id',
nargs='*',
type=int,
default=False,
help='Specify a blog to get feeds form'
)
def handle(self, *args, **options):
imported = import_feeds(options['blog_id'])
self.stdout.write(self.style.SUCCESS('Successfully imported %s articles' % len(imported)))

View File

@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-08 03:42
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import stdimage.models
import stdimage.utils
class Migration(migrations.Migration):
dependencies = [
('feeds', '0004_feed_enabled'),
]
operations = [
migrations.CreateModel(
name='Article',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.URLField()),
('title', models.CharField(max_length=255)),
('original_image', models.URLField(blank=True, max_length=255, null=True)),
('image', stdimage.models.StdImageField(blank=True, null=True, upload_to=stdimage.utils.UploadToAutoSlugClassNameDir('title'))),
('description', models.TextField()),
('displayed', models.BooleanField(default=True)),
('date', models.DateTimeField(default=django.utils.timezone.now)),
],
),
migrations.RemoveField(
model_name='feed',
name='url',
),
migrations.AddField(
model_name='feed',
name='feed_url',
field=models.URLField(default='http://thearduinoguy.org/?feed=rss2', verbose_name='RSS Feed URL'),
preserve_default=False,
),
migrations.AddField(
model_name='feed',
name='home_url',
field=models.URLField(default='http://thearduinoguy.org/', verbose_name='Site Home Page'),
preserve_default=False,
),
migrations.AddField(
model_name='feed',
name='title',
field=models.CharField(default='The Arduino Guy', max_length=255),
preserve_default=False,
),
migrations.AlterField(
model_name='feed',
name='id',
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='feed',
name='image',
field=stdimage.models.StdImageField(blank=True, null=True, upload_to=stdimage.utils.UploadToAutoSlugClassNameDir('title')),
),
migrations.AddField(
model_name='article',
name='feed',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='feeds.Feed'),
),
]

View File

@ -2,19 +2,54 @@
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
from django.db import models from django.db import models
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from stdimage.models import StdImageField
from stdimage.utils import UploadToAutoSlugClassNameDir
from mhackspace.users.models import User
@python_2_unicode_compatible @python_2_unicode_compatible
class Feed(models.Model): class Feed(models.Model):
id = models.AutoField(primary_key=True) home_url = models.URLField(verbose_name="Site Home Page")
url = models.CharField(max_length=255) feed_url = models.URLField(verbose_name="RSS Feed URL")
title = models.CharField(max_length=255)
author = models.CharField(max_length=255) author = models.CharField(max_length=255)
tags = models.CharField(max_length=255, blank=True) tags = models.CharField(max_length=255, blank=True)
image = models.ImageField(blank=True) image = StdImageField(
upload_to=UploadToAutoSlugClassNameDir(populate_from='title'),
blank=True,
null=True,
variations={
'home': {
"width": 530,
"height": 220,
"crop": True}})
enabled = models.BooleanField(default=True) enabled = models.BooleanField(default=True)
def __str__(self): def __str__(self):
return self.url return self.title
class Article(models.Model):
url = models.URLField()
feed = models.ForeignKey(Feed)
title = models.CharField(max_length=255)
original_image = models.URLField(max_length=255, blank=True, null=True)
image = StdImageField(
upload_to=UploadToAutoSlugClassNameDir(populate_from='title'),
blank=True,
null=True,
variations={
'home': {
"width": 530,
"height": 220,
"crop": True}})
description = models.TextField()
displayed = models.BooleanField(default=True)
date = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title

View File

@ -1,20 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django import template from django import template
from mhackspace.feeds.models import Feed from mhackspace.feeds.models import Article
from scaffold.readers.rss_reader import feed_reader
register = template.Library() register = template.Library()
@register.inclusion_tag('feeds/list.html') @register.inclusion_tag('feeds/list.html')
def show_feeds(): def show_feeds():
rss_feeds = [] return {'articles': Article.objects.filter(displayed=True).select_related('feed').order_by('-date')}
for feed in Feed.objects.all():
if feed.enabled is False:
continue
rss_feeds.append({
'author': feed.author,
'url': feed.url
})
result = feed_reader(rss_feeds)
return {'articles': [item for item in result]}

View File

@ -6707,5 +6707,25 @@ body {
line-height: 60px; line-height: 60px;
background-color: #f5f5f5; } background-color: #f5f5f5; }
@media (max-width: 991px) {
.card-columns {
-moz-column-count: 2;
column-count: 2; } }
@media (max-width: 767px) {
.card-columns {
-moz-column-count: 1;
column-count: 1; } }
@media (max-width: 767px) {
.card {
margin-bottom: 0.75rem; } }
.card-title {
color: #292b2c; }
#feeds img {
width: 100%; }
[hidden][style="display: block;"] { [hidden][style="display: block;"] {
display: block !important; } display: block !important; }

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,23 @@
.card-columns {
@include media-breakpoint-down(md) {
column-count: 2;
}
@include media-breakpoint-down(sm) {
column-count: 1;
}
}
.card {
@include media-breakpoint-down(sm) {
margin-bottom: 0.75rem;
}
}
.card-title {
color: $body-color;
}
#feeds img {
width: 100%;
}

View File

@ -6,6 +6,8 @@
@import "components/header"; @import "components/header";
@import "components/footer"; @import "components/footer";
@import "pages/home";
//////////////////////////////// ////////////////////////////////
//Django Toolbar// //Django Toolbar//
//////////////////////////////// ////////////////////////////////

View File

@ -0,0 +1,7 @@
{% extends "admin/change_list.html" %}
{% block object-tools-items %}
<li>
<a href="import/" class="grp-state-focus addlink">Import Articles</a>
</li>
{{ block.super }}
{% endblock %}

View File

@ -1,10 +1,15 @@
{% load static %}
{% get_static_prefix as STATIC_PREFIX %}
<div class="card-columns" id="feeds"> <div class="card-columns" id="feeds">
{% for article in articles %} {% for article in articles %}
<div class="card" style="width: 20rem;"> <div class="card">
<img class="card-img-top img-fluid" src="{{ article.image }}" alt="{{ article.title }}"> <a href="{{ article.url }}">
<img class="card-img-top img-fluid" alt="{{ article.title }}"
src="{% firstof article.image.home.url article.feed.image.home.url "static/images/card.png" %}">
</a>
<div class="card-block"> <div class="card-block">
<h4 class="card-title">{{ article.title }}</h4> <a href="{{ article.url }}" class="card-link"><h4 class="card-title">{{ article.title }}</h4></a>
<p class="card-text">{{ article.author }}</p> <p class="card-text">{{ article.feed.author }}</p>
<p class="card-text">{{ article.description|striptags }}</p> <p class="card-text">{{ article.description|striptags }}</p>
<a href="{{ article.url }}" class="card-link">View Original</a> <a href="{{ article.url }}" class="card-link">View Original</a>
</div> </div>

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-08 00:50
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import stdimage.models
class Migration(migrations.Migration):
dependencies = [
('users', '0004_auto_20170106_2030'),
]
operations = [
migrations.AlterField(
model_name='user',
name='image',
field=stdimage.models.StdImageField(blank=True, null=True, upload_to='avatars/'),
),
migrations.AlterField(
model_name='userblurb',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='users', to=settings.AUTH_USER_MODEL),
),
]