# -*- coding: utf-8 -*-
-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
TRIM_BEGIN = " TRIM_BEGIN "
TRIM_END = " TRIM_END "
tabs.append(Tab('create', _('Add'), reverse("catalogue_create_missing")))
tabs.append(Tab('upload', _('Upload'), reverse("catalogue_upload")))
+ tabs.append(Tab('cover', _('Covers'), reverse("cover_image_list")))
+
return {"tabs": tabs, "active_tab": active}
--- /dev/null
+from django import template
+register = template.Library()
+
+
+@register.filter
+def build_absolute_uri(uri, request):
+ return request.build_absolute_uri(uri)
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+import re
+from urllib2 import urlopen
+from django import forms
+from cover.models import Image
+
+class ImageAddForm(forms.ModelForm):
+ class Meta:
+ model = Image
+
+class ImageEditForm(forms.ModelForm):
+ """Form used for editing a Book."""
+ class Meta:
+ model = Image
+ exclude = ['download_url']
+
+
+class ReadonlyImageEditForm(ImageEditForm):
+ """Form used for not editing a Book."""
+
+ def __init__(self, *args, **kwargs):
+ ret = super(ReadonlyImageEditForm, self).__init__(*args, **kwargs)
+ for field in self.fields.values():
+ field.widget.attrs.update({"readonly": True})
+ return ret
+
+ def save(self, *args, **kwargs):
+ raise AssertionError, "ReadonlyImageEditForm should not be saved."
+
+
+class FlickrForm(forms.Form):
+ source_url = forms.URLField()
+
+ def clean_source_url(self):
+ url = self.cleaned_data['source_url']
+ m = re.match(r'(https?://)?(www.)?flickr.com/photos/(?P<author>[^/]+)/(?P<img>\d+)', url)
+ if not m:
+ raise forms.ValidationError("It doesn't look like Flickr URL.")
+ author_slug, img_id = m.group('author'), m.group('img')
+ base_url = "https://www.flickr.com/photos/%s/%s/" % (author_slug, img_id)
+
+ try:
+ html = urlopen(url).read().decode('utf-8')
+ except:
+ raise forms.ValidationError('Error reading page.')
+ match = re.search(r'<a href="([^"]*)" rel="license cc:license">Some rights reserved</a>', html)
+ try:
+ assert match
+ license_url = match.group(1)
+ self.cleaned_data['license_url'] = license_url
+ re_license = re.compile(r'http://creativecommons.org/licenses/([^/]*)/([^/]*)/.*')
+ m = re_license.match(license_url)
+ assert m
+ self.cleaned_data['license_name'] = 'CC %s %s' % (m.group(1).upper(), m.group(2))
+ except AssertionError:
+ raise forms.ValidationError('Error reading license name.')
+
+ m = re.search(r'<strong class="username">By <a href="[^"]*">([^<]*)</a></strong>', html)
+ if m:
+ self.cleaned_data['author'] = "%s@Flickr" % m.group(1)
+ else:
+ raise forms.ValidationError('Error reading author name.')
+
+ m = re.search(r'<h1[^>]*>(.*?)</h1>', html)
+ if not m:
+ raise forms.ValidationError('Error reading image title.')
+ self.cleaned_data['title'] = m.group(1)
+
+ url_size = base_url + "sizes/o/"
+ html = urlopen(url_size).read().decode('utf-8')
+ m = re.search(r'<div id="allsizes-photo">\s*<img src="([^"]*)"', html)
+ if m:
+ self.cleaned_data['download_url'] = m.group(1)
+ else:
+ raise forms.ValidationError('Error reading image URL.')
+ return base_url
--- /dev/null
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'Image'
+ db.create_table('cover_image', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('title', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('author', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('license_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('license_url', self.gf('django.db.models.fields.URLField')(max_length=255, blank=True)),
+ ('source_url', self.gf('django.db.models.fields.URLField')(max_length=200)),
+ ('download_url', self.gf('django.db.models.fields.URLField')(unique=True, max_length=200)),
+ ('file', self.gf('django.db.models.fields.files.ImageField')(max_length=100)),
+ ))
+ db.send_create_signal('cover', ['Image'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'Image'
+ db.delete_table('cover_image')
+
+
+ models = {
+ 'cover.image': {
+ 'Meta': {'object_name': 'Image'},
+ 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'download_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}),
+ 'file': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'license_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'license_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'}),
+ 'source_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ }
+ }
+
+ complete_apps = ['cover']
\ No newline at end of file
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+import re
+from urllib2 import urlopen
+from urlparse import urljoin
+from django.conf import settings
+from django.core.files.base import ContentFile
+from django.db import models
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+from django.utils.translation import ugettext_lazy as _
+
+
+class Image(models.Model):
+ title = models.CharField(max_length=255)
+ author = models.CharField(max_length=255)
+ license_name = models.CharField(max_length=255)
+ license_url = models.URLField(max_length=255, blank=True)
+ source_url = models.URLField()
+ download_url = models.URLField(unique=True)
+ file = models.ImageField(upload_to='cover/image', editable=False)
+
+ class Meta:
+ verbose_name = _('cover image')
+ verbose_name_plural = _('cover images')
+
+ def __unicode__(self):
+ return u"%s - %s" % (self.author, self.title)
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ('cover_image', [self.id])
+
+
+@receiver(post_save, sender=Image)
+def download_image(sender, instance, **kwargs):
+ if instance.pk and not instance.file:
+ t = urlopen(instance.download_url).read()
+ instance.file.save("%d.jpg" % instance.pk, ContentFile(t))
+
+
\ No newline at end of file
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+
+
+{% block content %}
+<h1>{% trans "Add image" %}</h1>
+
+<form method="post">
+{% csrf_token %}
+<input type="hidden" name='form_id' value="flickr" />
+{{ ff.as_p }}
+<input type="submit" value="{% trans "Load from Flickr" %}" />
+</form>
+
+
+<form method="post">
+{% csrf_token %}
+{{ form.as_p }}
+<input type="submit" value="{% trans "Add image" %}" />
+</form>
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+{% load build_absolute_uri from common_tags %}
+
+{% block content %}
+<h1>{% trans "Cover image" %}</h1>
+
+<div style="float: right">
+<a href="{{ object.file.url }}"><img style="width:400px" src="{{ object.file.url }}" /></a>
+<br/><a href="{{ object.source_url }}">{{ object.title }}</a> by {{ object.author }},
+ {% if object.license_url %}<a href="{{ object.license_url }}">{% endif %}
+ {{ object.license_name }}</a>
+ {% if object.license_url %}</a>{% endif %}
+
+<br/>{% trans "source" %}: {{ object.download_url }}
+</div>
+
+<form method="post">
+{% csrf_token %}
+{{ form.as_p }}
+<input type="submit" value="{% trans "Change" %}" />
+</form>
+
+<textarea style="width:100%" rows="5">
+<dc:relation.coverImage.url xmlns:dc="http://purl.org/dc/elements/1.1/">{{ object.file.url|build_absolute_uri:request }}</dc:relation.coverImage.url>
+<dc:relation.coverImage.attribution xmlns:dc="http://purl.org/dc/elements/1.1/">{{ object.author }}, {{ object.license_name }}</dc:relation.coverImage.attribution>
+<dc:relation.coverImage.source xmlns:dc="http://purl.org/dc/elements/1.1/">{{ object.source_url }}</dc:relation.coverImage.source>
+</textarea>
+{% endblock %}
--- /dev/null
+{% extends "catalogue/base.html" %}
+{% load i18n %}
+{% load url from future %}
+{% load thumbnail pagination_tags %}
+
+
+{% block content %}
+<h1>{% trans "Cover images" %}</h1>
+
+{% if can_add %}
+ <a href="{% url 'cover_add_image' %}">{% trans "Add new" %}</a>
+{% endif %}
+
+<ul>
+{% autopaginate object_list 100 %}
+{% for image in object_list %}
+ <a href="{{ image.get_absolute_url }}">
+
+ <img style="height: 200px"
+ src="{% thumbnail image.file "x200" as thumb %}
+ {{ thumb.url }}
+ {% empty %}
+ {{ image.file.url }}
+ {% endthumbnail %}" />
+ {{ image }}</a>
+{% endfor %}
+{% paginate %}
+</ul>
+
+{% endblock %}
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from nose.tools import *
+from django.test import TestCase
+from cover.forms import FlickrForm
+
+
+class FlickrTests(TestCase):
+ def test_flickr(self):
+ form = FlickrForm({"source_url": "https://www.flickr.com/photos/rczajka/6941928577/in/photostream"})
+ self.assertTrue(form.is_valid())
+ self.assertEqual(form.cleaned_data['source_url'], "https://www.flickr.com/photos/rczajka/6941928577/")
+ self.assertEqual(form.cleaned_data['author'], "rczajka@Flickr")
+ self.assertEqual(form.cleaned_data['title'], u"Pirate Stańczyk")
+ self.assertEqual(form.cleaned_data['license_name'], "CC BY 2.0")
+ self.assertEqual(form.cleaned_data['license_url'], "http://creativecommons.org/licenses/by/2.0/deed.en")
+ self.assertEqual(form.cleaned_data['download_url'], "http://farm8.staticflickr.com/7069/6941928577_2a0306f6a6_b.jpg")
-# -*- coding: utf-8
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
from django.conf.urls.defaults import patterns, url
url(r'^preview/(?P<book>[^/]+)/(?P<chunk>[^/]+)/(?P<rev>\d+)/$',
'preview', name='cover_preview'),
- url(r'^flickr/$', 'flickr'),
+ url(r'^image/$', 'image_list', name='cover_image_list'),
+ url(r'^image/(?P<pk>\d+)/?$', 'image', name='cover_image'),
+ url(r'^add_image/$', 'add_image', name='cover_add_image'),
)
-# Create your views here.
+# -*- coding: utf-8 -*-
+#
+# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
import os.path
from django.conf import settings
+from django.contrib.auth.decorators import permission_required
from django.http import HttpResponse, HttpResponseRedirect, Http404
-from catalogue.models import Chunk
-from django.views.decorators.http import require_POST
+from django.shortcuts import get_object_or_404, render
from django.views.decorators.csrf import csrf_exempt
-from django.shortcuts import render
+from django.views.decorators.http import require_POST
+from catalogue.helpers import active_tab
+from catalogue.models import Chunk
+from cover.models import Image
+from cover import forms
PREVIEW_SIZE = (216, 300)
return HttpResponse(os.path.join(settings.MEDIA_URL, fname))
-def flickr(request):
- url = request.POST.get('flickr_url')
- if url:
- import re
- from urllib2 import urlopen
+@active_tab('cover')
+def image(request, pk):
+ image = get_object_or_404(Image, pk=pk)
- html = urlopen(url).read()
- match = re.search(r'<a href="([^"]*)" rel="license cc:license">Some rights reserved</a>', html)
- try:
- assert match
- license_url = match.group(1)
- re_license = re.compile(r'http://creativecommons.org/licenses/([^/]*)/([^/]*)/.*')
- m = re_license.match(license_url)
- assert m
- license_name = 'CC %s %s' % (m.group(1).upper(), m.group(2))
- except AssertionError:
- license_name = 'NIEZNANA LICENCJA'
-
- m = re.search(r'<strong class="username">By <a href="[^"]*">([^<]*)</a></strong>', html)
- if m:
- author = m.group(1)
+ if request.user.has_perm('cover.change_image'):
+ if request.method == "POST":
+ form = forms.ImageEditForm(request.POST, instance=image)
+ if form.is_valid():
+ form.save()
+ return HttpResponseRedirect(image.get_absolute_url())
else:
- author = "NIEZNANY AUTOR"
-
- url_size = url.rstrip('/') + '/sizes/o/'
- html = urlopen(url_size).read()
- m = re.search(r'<div id="allsizes-photo">\s*<img src="([^"]*)"', html)
- if m:
- image_url = m.group(1)
+ form = forms.ImageEditForm(instance=image)
+ editable = True
else:
- url = ""
-
- return render(request, 'cover/flickr.html', locals())
+ form = forms.ReadonlyImageEditForm(instance=image)
+ editable = False
+
+ return render(request, "cover/image_detail.html", {
+ "object": image,
+ "form": form,
+ "editable": editable,
+ })
+
+
+@active_tab('cover')
+def image_list(request):
+ objects = Image.objects.all()
+ enable_add = request.user.has_perm('cover.add_image')
+ return render(request, "cover/image_list.html", {
+ 'object_list': Image.objects.all(),
+ 'can_add': request.user.has_perm('cover.add_image'),
+ })
+
+
+@permission_required('cover.add_image')
+@active_tab('cover')
+def add_image(request):
+ form = ff = None
+ if request.method == 'POST':
+ if request.POST.get('form_id') == 'flickr':
+ ff = forms.FlickrForm(request.POST)
+ if ff.is_valid():
+ form = forms.ImageAddForm(ff.cleaned_data)
+ else:
+ form = forms.ImageAddForm(request.POST)
+ if form.is_valid():
+ obj = form.save()
+ return HttpResponseRedirect(obj.get_absolute_url())
+ if form is None:
+ form = forms.ImageAddForm()
+ if ff is None:
+ ff = forms.FlickrForm()
+ return render(request, 'cover/add_image.html', {
+ 'form': form,
+ 'ff': ff,
+ })
INSTALLED_APPS += ('django_nose', 'dvcs.tests')
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
-#TEST_MODULES = ('catalogue')
-
-TEST_MODULES = ('catalogue', 'dvcs.tests', 'wiki', 'toolbar')
-COVER_APPS = ('catalogue', 'dvcs', 'wiki', 'toolbar')
+TEST_MODULES = ('catalogue', 'cover', 'dvcs.tests', 'wiki', 'toolbar')
+COVER_APPS = ('catalogue', 'cover', 'dvcs', 'wiki', 'toolbar')
NOSE_ARGS = (
'--tests=' + ','.join(TEST_MODULES),
'--cover-package=' + ','.join(COVER_APPS),
## Django
Django>=1.3,<1.4
-sorl-thumbnail>=3.2
+sorl-thumbnail>=11.09,<12
django-maintenancemode>=0.9
django-pagination
django-gravatar