From: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl> Date: Fri, 15 Jun 2012 15:05:19 +0000 (+0200) Subject: cover image repo X-Git-Url: https://git.mdrn.pl/redakcja.git/commitdiff_plain/05f67adb27390c7a9cf214e83550d3bbca88c8f2?ds=sidebyside cover image repo --- diff --git a/apps/catalogue/constants.py b/apps/catalogue/constants.py index d75d6b4b..0c842324 100644 --- a/apps/catalogue/constants.py +++ b/apps/catalogue/constants.py @@ -1,5 +1,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. +# TRIM_BEGIN = " TRIM_BEGIN " TRIM_END = " TRIM_END " diff --git a/apps/catalogue/templatetags/catalogue.py b/apps/catalogue/templatetags/catalogue.py index 3cc7210c..8d5ff65b 100644 --- a/apps/catalogue/templatetags/catalogue.py +++ b/apps/catalogue/templatetags/catalogue.py @@ -34,6 +34,8 @@ def main_tabs(context): 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} diff --git a/apps/catalogue/templatetags/common_tags.py b/apps/catalogue/templatetags/common_tags.py new file mode 100755 index 00000000..ccaf03bf --- /dev/null +++ b/apps/catalogue/templatetags/common_tags.py @@ -0,0 +1,7 @@ +from django import template +register = template.Library() + + +@register.filter +def build_absolute_uri(uri, request): + return request.build_absolute_uri(uri) diff --git a/apps/cover/forms.py b/apps/cover/forms.py new file mode 100755 index 00000000..754d697c --- /dev/null +++ b/apps/cover/forms.py @@ -0,0 +1,80 @@ +# -*- 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 diff --git a/apps/cover/migrations/0001_initial.py b/apps/cover/migrations/0001_initial.py new file mode 100644 index 00000000..f31c405f --- /dev/null +++ b/apps/cover/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# -*- 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 diff --git a/apps/cover/migrations/__init__.py b/apps/cover/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/cover/models.py b/apps/cover/models.py index e69de29b..0aeddb4a 100644 --- a/apps/cover/models.py +++ b/apps/cover/models.py @@ -0,0 +1,44 @@ +# -*- 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 diff --git a/apps/cover/templates/cover/add_image.html b/apps/cover/templates/cover/add_image.html new file mode 100755 index 00000000..8506e614 --- /dev/null +++ b/apps/cover/templates/cover/add_image.html @@ -0,0 +1,21 @@ +{% 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 %} diff --git a/apps/cover/templates/cover/image_detail.html b/apps/cover/templates/cover/image_detail.html new file mode 100755 index 00000000..8ebf988e --- /dev/null +++ b/apps/cover/templates/cover/image_detail.html @@ -0,0 +1,29 @@ +{% 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 %} diff --git a/apps/cover/templates/cover/image_list.html b/apps/cover/templates/cover/image_list.html new file mode 100755 index 00000000..5b2b0006 --- /dev/null +++ b/apps/cover/templates/cover/image_list.html @@ -0,0 +1,30 @@ +{% 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 %} diff --git a/apps/cover/tests.py b/apps/cover/tests.py new file mode 100644 index 00000000..d6a1ad7d --- /dev/null +++ b/apps/cover/tests.py @@ -0,0 +1,20 @@ +# -*- 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") diff --git a/apps/cover/urls.py b/apps/cover/urls.py index 5e6ffc54..2337d47a 100644 --- a/apps/cover/urls.py +++ b/apps/cover/urls.py @@ -1,4 +1,8 @@ -# -*- 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 @@ -10,5 +14,7 @@ urlpatterns = patterns('cover.views', 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'), ) diff --git a/apps/cover/views.py b/apps/cover/views.py index 0f341ad7..0c29209c 100644 --- a/apps/cover/views.py +++ b/apps/cover/views.py @@ -1,11 +1,19 @@ -# 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) @@ -73,36 +81,59 @@ def preview_from_xml(request): 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, + }) diff --git a/redakcja/settings/test.py b/redakcja/settings/test.py index dcbbb1f9..717de290 100644 --- a/redakcja/settings/test.py +++ b/redakcja/settings/test.py @@ -26,10 +26,8 @@ USE_CELERY = False 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), diff --git a/requirements.txt b/requirements.txt index 5e942547..370f6a99 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ httplib2 # oauth2 dependency ## Django Django>=1.3,<1.4 -sorl-thumbnail>=3.2 +sorl-thumbnail>=11.09,<12 django-maintenancemode>=0.9 django-pagination django-gravatar