use a link to present pdf validation request attachments (fixes #29508)

Use sorl-thumbnail to produce thumbnails.
This commit is contained in:
Benjamin Dauvergne 2019-01-24 19:40:22 +01:00
parent 2c5563ee91
commit a1c8807b20
9 changed files with 186 additions and 24 deletions

View File

@ -108,6 +108,7 @@ setup(
'authentic2',
'Pillow',
'python-magic',
'sorl-thumbnail',
],
entry_points={
'authentic2.plugin': [

View File

@ -25,7 +25,7 @@ class Plugin(object):
return urls.urlpatterns
def get_apps(self):
return [__name__]
return ['sorl.thumbnail', __name__]
def get_authentication_backends(self):
return []

View File

@ -143,3 +143,5 @@ from authentic2.settings import DJANGO_RBAC_PERMISSIONS_HIERARCHY
DJANGO_RBAC_PERMISSIONS_HIERARCHY['cut_validate'] = ['view', 'search']
DJANGO_RBAC_PERMISSIONS_HIERARCHY['cut_fc'] = ['view', 'search']
THUMBNAIL_ENGINE = 'sorl.thumbnail.engines.convert_engine.Engine'
THUMBNAIL_FORCE_OVERWRITE = False

View File

@ -7,9 +7,13 @@ from django.db import models
from django.conf import settings
from django.db.models.query import Q
from django.utils.timezone import now
from django.core.urlresolvers import reverse
from django.core.files.storage import default_storage
from django.contrib.contenttypes.fields import GenericForeignKey
from sorl.thumbnail import get_thumbnail
class Journal(models.Model):
timestamp = models.DateTimeField(
@ -190,6 +194,52 @@ class ValidationRequestAttachment(models.Model):
image = models.ImageField(
verbose_name='contenu')
@property
def extension(self):
return self.image.name.rsplit('.', 1)[-1]
@property
def url(self):
return reverse(
'cut-manager-user-validation-attachment',
kwargs={
'pk': self.pk,
'filename': self.image.name.rsplit('/', 1)[-1]
})
@property
def thumbnail(self):
try:
thumbnail = get_thumbnail(self.image, '200x200')
except Exception:
raise
return None
try:
# check file exists and is readable
with default_storage.open(thumbnail.name):
pass
return thumbnail
except IOError:
pass
return None
@property
def thumbnail_image(self):
thumbnail = self.thumbnail
if thumbnail:
return {
'src': reverse(
'cut-manager-user-validation-attachment-thumbnail',
kwargs={
'pk': self.pk,
'filename': self.image.name.rsplit('/', 1)[-1]
}
),
'height': thumbnail.height,
'width': thumbnail.width,
}
return {}
class Meta:
ordering = ('pk',)
verbose_name = u'Pièce jointe'

View File

@ -28,9 +28,9 @@
{% endif %}
<script>
$('body').on('click', 'img.popup-image', function (event) {
$('body').on('click', 'a.popup-image', function (event) {
var target = event.target;
var src = target.src;
var src = $(target).parent('a')[0].href;
var vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
var vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
var ratio_width = target.width / (vw - 100);
@ -56,17 +56,26 @@ $('body').on('click', 'img.popup-image', function (event) {
$overlay.on('click', function () {
$overlay.remove();
});
event.preventDefault();
})
</script>
<div>
{% if attachment_urls %}
<div id="attachments">
<h4>Pièces jointes</h4>
<p>
{% for attachment_url in attachment_urls %}
<img class="popup-image" src="{{ attachment_url }}" tile="Pièce jointe {{ forloop.counter }}" style="max-height: 20vmin; border: 1px solid black; padding: 1ex"/>
{% endfor %}
</ul>
{% endif %}
{% for attachment in attachments %}
<a
{% if attachment.extension != 'pdf' %}
class="popup-image"
{% endif %}
target="_blank"
href="{{ attachment.url }}">
<img
src="{{ attachment.thumbnail_image.src }}"
title="Pièce jointe {{ forloop.counter }}"
style="border: 1px solid black; padding: 1ex"/>
</a>
{% endfor %}
</p>
</div>
<h4>Identité</h4>
<div>

View File

@ -40,8 +40,10 @@ urlpatterns = required(
name='cut-manager-user-next-validation'),
url('^manage/validation/(?P<pk>\d+)/$', views.validation,
name='cut-manager-user-validation'),
url('^manage/validation/attachment/(?P<pk>\d*)/$', views.validation_attachment,
url('^manage/validation/attachment/(?P<pk>\d*)/(?P<filename>.*)$', views.validation_attachment,
name='cut-manager-user-validation-attachment'),
url('^manage/validation/attachment-thumbnail/(?P<pk>\d*)/(?P<filename>.*)$', views.validation_attachment_thumbnail,
name='cut-manager-user-validation-attachment-thumbnail'),
]
)

View File

@ -15,13 +15,15 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import magic
from contextlib import closing
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.views.generic.base import TemplateView
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.db.transaction import atomic
@ -207,9 +209,7 @@ class Validation(UserEditCoreView):
def get_context_data(self, **kwargs):
ctx = super(Validation, self).get_context_data(**kwargs)
ctx['validation_request'] = self.validation_request
ctx['attachment_urls'] = [
reverse('cut-manager-user-validation-attachment', kwargs={'pk': attachment.pk})
for attachment in self.validation_request.attachments.all()]
ctx['attachments'] = self.validation_request.attachments.all()
ctx['action'] = u'Valider'
ctx['validation_form'] = forms.ValidationForm()
return ctx
@ -270,11 +270,24 @@ class Validation(UserEditCoreView):
validation = Validation.as_view()
def validation_attachment(request, pk):
def validation_attachment(request, pk, filename):
if not request.user.is_authenticated() or not request.user.has_perm_any('custom_user.cut_validate_user'):
raise PermissionDenied
attachment = models.ValidationRequestAttachment.objects.get(pk=pk)
return HttpResponse(attachment.image, content_type='image/jpeg')
attachment.image.open()
mime_type = magic.from_buffer(attachment.image.read(10000), mime=True)
attachment.image.open()
return HttpResponse(attachment.image, content_type=mime_type)
def validation_attachment_thumbnail(request, pk, filename):
if not request.user.is_authenticated() or not request.user.has_perm_any('custom_user.cut_validate_user'):
raise PermissionDenied
attachment = models.ValidationRequestAttachment.objects.get(pk=pk)
thumbnail = attachment.thumbnail
if not thumbnail:
raise Http404
return HttpResponse(thumbnail.read(), content_type='image/jpeg')
class ValidationHomepage(BaseTableView):

View File

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
import base64
import uuid
from mock import MagicMock
import pytest
import pathlib2
@ -9,6 +8,8 @@ import pathlib2
from django.contrib.auth import get_user_model
from authentic2_cut import models
from utils import login
User = get_user_model()
JOHN = u'Jôhn'
@ -18,6 +19,18 @@ EMAIL = 'john.doe@example.com'
TEST_DIR = pathlib2.Path(__file__).parent
@pytest.fixture
def admin(db):
user = User(
username='admin',
email='admin@example.net',
is_superuser=True,
is_staff=True)
user.set_password('admin')
user.save()
return user
@pytest.fixture
def john(glc_app):
response = glc_app.post_json('/api/users/', params={
@ -86,15 +99,62 @@ def helper_test_validation_image(glc_app, john, image_file, extension):
validation_request = models.ValidationRequest.objects.get(id=response.json['id'])
attachment = models.ValidationRequestAttachment.objects.get(validation_request=validation_request)
assert attachment.image.name.endswith(extension)
return response.json['id']
def test_validation_jpg(glc_app, john, jpeg_file):
helper_test_validation_image(glc_app, john, jpeg_file, 'jpeg')
def test_validation_jpg(app, admin, glc_app, john, jpeg_file):
validation_id = helper_test_validation_image(glc_app, john, jpeg_file, 'jpeg')
response = login(app, admin, 'cut-manager-user-validation')
response = response.click(str(validation_id))
assert response.pyquery('.popup-image')
def test_validation_png(glc_app, john, png_file):
helper_test_validation_image(glc_app, john, png_file, 'png')
def test_validation_png(app, admin, glc_app, john, png_file):
validation_id = helper_test_validation_image(glc_app, john, png_file, 'png')
response = login(app, admin, 'cut-manager-user-validation')
response = response.click(str(validation_id))
assert response.pyquery('.popup-image')
def test_validation_pdf(glc_app, john, pdf_file):
helper_test_validation_image(glc_app, john, pdf_file, 'pdf')
def test_validation_pdf(app, admin, glc_app, john, pdf_file):
validation_id = helper_test_validation_image(glc_app, john, pdf_file, 'pdf')
response = login(app, admin, 'cut-manager-user-validation')
response = response.click(str(validation_id))
assert not response.pyquery('.popup-image')
def test_many_attachments(app, admin, glc_app, john, png_file, jpeg_file, pdf_file):
external_id = uuid.uuid4().hex
response = glc_app.post_json('/api/users/%s/validate/' % john._oidc_sub, params={
'external_id': external_id,
'justificatifs': [
{
'b64_content': base64.b64encode(png_file),
},
{
'b64_content': base64.b64encode(jpeg_file),
},
{
'b64_content': base64.b64encode(pdf_file),
},
],
}, status=201)
assert response.json == {
'status': 'received',
'id': response.json['id'],
'result': 1,
'external_id': external_id,
'sub': john._oidc_sub,
}
validation_request = models.ValidationRequest.objects.get(id=response.json['id'])
assert validation_request.attachments.count() == 3
validation_id = str(response.json['id'])
response = login(app, admin, 'cut-manager-user-validation')
response = response.click(validation_id)
assert len(response.pyquery('#attachments p a')) == 3
attachments_elts = response.pyquery('#attachments p a')
assert [elt.attrib.get('class', '') for elt in attachments_elts] == ['popup-image', 'popup-image', '']
assert app.get(attachments_elts[0].attrib['href']).content == png_file
assert app.get(attachments_elts[1].attrib['href']).content == jpeg_file
assert app.get(attachments_elts[2].attrib['href']).content == pdf_file

25
tests/utils.py Normal file
View File

@ -0,0 +1,25 @@
from django.core.urlresolvers import reverse
from authentic2.utils import make_url
def login(app, user, path=None, password=None, remember_me=None):
if path:
real_path = make_url(path)
login_page = app.get(real_path, status=302).maybe_follow()
else:
login_page = app.get(reverse('auth_login'))
assert login_page.request.path == reverse('auth_login')
form = login_page.form
form.set('username', user.username if hasattr(user, 'username') else user)
# password is supposed to be the same as username
form.set('password', password or user.username)
if remember_me is not None:
form.set('remember_me', bool(remember_me))
response = form.submit(name='login-password-submit').follow()
if path:
assert response.request.path == real_path
else:
assert response.request.path == reverse('auth_homepage')
assert '_auth_user_id' in app.session
return response