0001-Add-a-send-to-view-fixes-7080.patch
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 |
- |