Projet

Général

Profil

0001-Add-a-send-to-view-fixes-7080.patch

Benjamin Dauvergne, 29 avril 2015 15:36

Télécharger (13,2 ko)

Voir les différences:

Subject: [PATCH] Add a send-to view (fixes #7080)

To you it, make a POST using the multipart/form-data content type on

 /send-to/?email=<email-of-target-user>

Files are transmitted in the 'documents' fields of multipart/form-data body.
Each file must have a filename given in its content-disposition header.
Example:

POST /send-to/?email=john.doe@exampke.com HTTP/1.1
Host: localhost:8000
Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266
Content-Length: xxx

-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="documents"; filename="attachment1.txt"
Content-Type: text/plain

Content of a.txt.

-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="documents"; filename="attachment2.html"
Content-Type: text/html

<!DOCTYPE html><title>Content of a.html.</title>

-----------------------------9051914041544843365972754266--
 COPYING               | 11 +++++++-
 fargo/fargo/fields.py | 53 +++++++++++++++++++++++++++++++++++++
 fargo/fargo/forms.py  | 40 +++++++++++++++++++++++++---
 fargo/fargo/views.py  | 24 ++++++++++++-----
 fargo/tests.py        | 73 +++++++++++++++++++++++++++++++++++++++++++++++++--
 fargo/urls.py         | 11 ++++----
 6 files changed, 195 insertions(+), 17 deletions(-)
 create mode 100644 fargo/fargo/fields.py
COPYING
1
Fargo is entirely under the copyright of Entr'ouvert and distributed
1
Fargo is mainly under the copyright of Entr'ouvert and distributed
2 2
under the license AGPLv3 or later.
3

  
4
A file was copied from the project django-multiupload
5
(https://github.com/Chive/django-multiupload) and licensed under the MIT
6
license.
7

  
8
	 The MIT License (MIT)
9
	 
10
	 Copyright (c) 2014 Chive - Kim Thoenen (kim@smuzey.ch)
11

  
fargo/fargo/fields.py
1
# -*- coding: utf-8 -*-
2
# Copied from https://github.com/Chive/django-multiupload/blob/master/multiupload/fields.py
3
from django import forms
4
from django.core.exceptions import ValidationError
5
from django.utils.translation import ugettext_lazy as _
6

  
7

  
8
class MultiFileInput(forms.FileInput):
9
    def render(self, name, value, attrs=None):
10
        attrs['multiple'] = 'multiple'
11
        return super(MultiFileInput, self).render(name, value, attrs)
12

  
13
    def value_from_datadict(self, data, files, name):
14
        if hasattr(files, 'getlist'):
15
            return files.getlist(name)
16
        else:
17
            return [files.get(name)]
18

  
19

  
20
class MultiFileField(forms.FileField):
21
    widget = MultiFileInput
22
    default_error_messages = {
23
        'min_num': _(u'Ensure at least %(min_num)s files are uploaded (received %(num_files)s).'),
24
        'max_num': _(u'Ensure at most %(max_num)s files are uploaded (received %(num_files)s).'),
25
        'file_size': _(u'File %(uploaded_file_name)s exceeded maximum upload size.'),
26
    }
27

  
28
    def __init__(self, *args, **kwargs):
29
        self.min_num = kwargs.pop('min_num', 0)
30
        self.max_num = kwargs.pop('max_num', None)
31
        self.maximum_file_size = kwargs.pop('max_file_size', None)
32
        super(MultiFileField, self).__init__(*args, **kwargs)
33

  
34
    def to_python(self, data):
35
        ret = []
36
        for item in data:
37
            i = super(MultiFileField, self).to_python(item)
38
            if i:
39
                ret.append(i)
40
        return ret
41

  
42
    def validate(self, data):
43
        super(MultiFileField, self).validate(data)
44
        num_files = len(data)
45
        if len(data) and not data[0]:
46
            num_files = 0
47
        if num_files < self.min_num:
48
            raise ValidationError(self.error_messages['min_num'] % {'min_num': self.min_num, 'num_files': num_files})
49
        elif self.max_num and num_files > self.max_num:
50
            raise ValidationError(self.error_messages['max_num'] % {'max_num': self.max_num, 'num_files': num_files})
51
        for uploaded_file in data:
52
            if self.maximum_file_size and uploaded_file.size > self.maximum_file_size:
53
                raise ValidationError(self.error_messages['file_size'] % {'uploaded_file_name': uploaded_file.name})
fargo/fargo/forms.py
1
from django.forms import ModelForm
1
import base64
2 2

  
3
from . import models
3
from django.utils.text import slugify
4
from django.core.exceptions import ValidationError
5
from django import forms
6
from django.contrib.auth import get_user_model
7
from django.core.files.base import ContentFile
4 8

  
5
class UploadForm(ModelForm):
9
from . import models, fields
10

  
11
class UploadForm(forms.ModelForm):
6 12
    def __init__(self, *args, **kwargs):
7 13
        self.user = kwargs.pop('user')
8 14
        super(UploadForm, self).__init__(*args, **kwargs)
......
15 21
    class Meta:
16 22
        model = models.Document
17 23
        fields = ['document_file']
24

  
25
class SendToForm(forms.Form):
26
    email = forms.EmailField()
27
    documents = fields.MultiFileField()
28

  
29
    def clean_email(self):
30
        User = get_user_model()
31
        try:
32
            self.cached_user = User.objects.get(
33
                    email=self.cleaned_data['email'])
34
        except User.DoesNotExist:
35
            raise ValidationError('no such user')
36
	return self.cleaned_data['email']
37

  
38

  
39
    def clean_documents(self):
40
        for i, document in enumerate(self.cleaned_data['documents']):
41
            if not document.name or document.name == 'documents':
42
                raise ValidationError('%d-th document is missing a name' % i)
43
	return self.cleaned_data['documents']
44

  
45
    def save(self, *args, **kwargs):
46
        for document in self.cleaned_data['documents']:
47
            document = models.Document(
48
                user=self.cached_user,
49
                document_filename=document.name,
50
                document_file=document)
51
            document.save()
fargo/fargo/views.py
1 1
import urlparse
2 2
import urllib
3 3
import logging
4
from json import dumps
4
import json
5 5

  
6 6
from django.views.generic import CreateView, DeleteView, View, TemplateView
7 7
from django.core.urlresolvers import reverse
8 8
from django.contrib.auth.decorators import login_required
9 9
from django.shortcuts import get_object_or_404, resolve_url
10
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
10
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, HttpResponseBadRequest
11 11
from django.core import signing
12 12
from django.contrib import messages
13 13
from django.contrib.auth import get_user_model, REDIRECT_FIELD_NAME
......
181 181
    def get(self, request):
182 182
        callback = request.GET.get('callback', 'callback')
183 183
        s = '%s(%s)' % (callback.encode('ascii'),
184
                dumps(self.get_data(request)))
184
                json.dumps(self.get_data(request)))
185 185
        return HttpResponse(s, content_type='application/javascript')
186 186

  
187 187
class JSON(JSONP):
......
192 192
            request.user = get_object_or_404(User, username=username)
193 193
        elif not request.user.is_authenticated():
194 194
            return method_decorator(login_required)(JSON.get)(self, request)
195
        response = HttpResponse(dumps(self.get_data(request)),
195
        response = HttpResponse(json.dumps(self.get_data(request)),
196 196
                content_type='application/json')
197 197
        response['Access-Control-Allow-Origin'] = '*'
198 198
        return response
199 199

  
200
class SendTo(View):
201
    http_method_allowed = ['post']
202
   
203
    def post(self, request, *args, **kwargs):
204
        form = forms.SendToForm(request.GET, request.FILES)
205
        if form.is_valid():
206
           form.save()
207
           return HttpResponse('ok', content_type='text/plain')
208
        else:
209
           result = {'errors': form.errors}
210
           return HttpResponseBadRequest(json.dumps(result))
200 211

  
201 212
def login(request, *args, **kwargs):
202 213
    if any(get_idps()):
......
223 234
download        = login_required(Download.as_view())
224 235
upload          = login_required(Upload.as_view())
225 236
remote_download = RemoteDownload.as_view()
237
send_to         = SendTo.as_view()
226 238
delete          = login_required(Delete.as_view())
227 239
pick            = login_required(Pick.as_view())
228
jsonp           = login_required(JSONP.as_view())
229
json            = login_required(JSON.as_view())
240
jsonp_view      = login_required(JSONP.as_view())
241
json_view       = login_required(JSON.as_view())
fargo/tests.py
1
from django.test import TestCase
1
from django.test import TestCase, Client
2 2

  
3
# Create your tests here.
3
class FargoTestCase(TestCase):
4
    def setUp(self):
5
        import StringIO
6
        from django.contrib.auth import get_user_model
7

  
8
        User = get_user_model()
9
        self.email = 'john.doe@example.com'
10
        self.username = 'john.doe'
11
        self.user = User.objects.create(
12
                username=self.username,
13
                email=self.email)
14
        self.filename = 'attachment.pdf'
15
        self.content = 'coucou'
16
        self.contentfile = StringIO.StringIO(self.content)
17
        self.contentfile.content_type = 'application/pdf'
18
        self.contentfile.name = self.filename
19

  
20
    def test_send_to(self):
21
        from fargo.models import Document
22

  
23
        client = Client()
24
        response = client.post('/send-to/?email=%s' % self.email,
25
                data={'documents': [self.contentfile]})
26
        self.assertEqual(response.status_code, 200)
27
        self.assertEqual(response.content, 'ok')
28
        self.assertEqual(Document.objects.count(), 1, 'no document found')
29
        document = Document.objects.get()
30
        self.assertEqual(document.user, self.user)
31
        self.assertEqual(document.document_filename, self.filename)
32
        self.assertEqual(document.document_file.read(), self.content)
33

  
34
    def test_remote_send_to_unknown_user(self):
35
        client = Client()
36
        response = client.post('/send-to/?email=%s' % 'unknown@example.com',
37
                data={'documents': [self.contentfile]})
38
        self.assertEqual(response.status_code, 400)
39
        self.assertJSONEqual(response.content, {
40
                'errors': {
41
                    'email': ['no such user'],
42
                }
43
           }
44
        )
45

  
46
    def test_remote_upload_missing_fields(self):
47
        client = Client()
48
        response = client.post('/send-to/')
49
        self.assertEqual(response.status_code, 400)
50
        self.assertJSONEqual(response.content, {
51
                'errors': {
52
                    'email': ['This field is required.'],
53
                    'documents': ['This field is required.'],
54
                }
55
           }
56
        )
57

  
58
    def test_remote_upload_missing_name(self):
59
        import StringIO
60

  
61
        client = Client()
62
        contentfile = StringIO.StringIO(self.content)
63
        contentfile.content_type = 'application/pdf'
64
        response = client.post('/send-to/?email=%s' % self.email,
65
                data={'documents': [contentfile]})
66
        self.assertEqual(response.status_code, 400)
67
        self.assertJSONEqual(response.content, {
68
                'errors': {
69
                    'documents': ['0-th document is missing a name'],
70
                }
71
           }
72
        )
fargo/urls.py
2 2
from django.conf.urls import patterns, include, url
3 3
from django.contrib import admin
4 4

  
5
from .fargo.views import (home, jsonp, json, document, download, pick, delete, upload,
6
        remote_download, login, logout)
5
from .fargo.views import (home, jsonp_view, json_view, document, download,
6
        pick, delete, upload, remote_download, send_to, login,
7
        logout)
7 8

  
8 9
urlpatterns = patterns('',
9 10
    url(r'^$', home, name='home'),
10
    url(r'^jsonp/$', jsonp, name='jsonp'),
11
    url(r'^json/$', json, name='json'),
11
    url(r'^jsonp/$', jsonp_view, name='jsonp'),
12
    url(r'^json/$', json_view, name='json'),
12 13
    url(r'^(?P<pk>\d+)/$', document, name='document'),
13 14
    url(r'^(?P<pk>\d+)/delete/$', delete, name='delete'),
14 15
    url(r'^(?P<pk>\d+)/pick/$', pick, name='pick'),
15 16
    url(r'^(?P<pk>\d+)/download/(?P<filename>[^/]*)$', download, name='download'),
16 17
    url(r'^upload/$', upload, name='upload'),
17 18
    url(r'^remote-download/(?P<filename>[^/]*)$', remote_download, name='remote_download'),
19
    url(r'^send-to/$', send_to, name='send_to'),
18 20
    url(r'^admin/', include(admin.site.urls)),
19 21
    url(r'^login/$', login, name='auth_login'),
20 22
    url(r'^logout/$', logout, name='auth_logout'),
21 23
)
22 24

  
23

  
24 25
if 'mellon' in settings.INSTALLED_APPS:
25 26
    urlpatterns += patterns('', url(r'^accounts/mellon/', include('mellon.urls')))
26
-