0001-add-validation-of-documents.patch
fargo/fargo/admin.py | ||
---|---|---|
1 | 1 |
from django.contrib import admin |
2 |
from django.contrib.auth import get_user_model |
|
2 | 3 | |
3 | 4 |
from . import models |
4 | 5 | |
6 | ||
7 |
class ValidationMixin(object): |
|
8 |
fields = [ 'user', |
|
9 |
'filename', |
|
10 |
'size', |
|
11 |
'document_type', |
|
12 |
'start', |
|
13 |
'end', |
|
14 |
'data' ] |
|
15 |
readonly_fields = ['user', 'filename', 'size'] |
|
16 | ||
17 |
def user(self, validation): |
|
18 |
user = validation.user_document.user |
|
19 |
return user.get_full_name().strip() or user.email or user.username |
|
20 | ||
21 |
def filename(self, validation): |
|
22 |
return validation.user_document.filename |
|
23 | ||
24 |
def size(self, validation): |
|
25 |
return validation.user_document.document.content.size |
|
26 | ||
27 | ||
28 |
class ValidationInline(ValidationMixin, admin.TabularInline): |
|
29 |
model = models.Validation |
|
30 | ||
31 | ||
32 |
class UserDocumentAdmin(admin.ModelAdmin): |
|
33 |
list_display = ['user', 'filename', 'created'] |
|
34 |
readonly_fields = ['created'] |
|
35 |
inlines = [ValidationInline] |
|
36 | ||
37 | ||
5 | 38 |
class DocumentAdmin(admin.ModelAdmin): |
6 |
list_display = ['user', 'document_filename', 'document_file', 'creation'] |
|
7 |
readonly_fields = ['creation'] |
|
39 |
list_display = ['content_hash', 'users'] |
|
40 | ||
41 |
def users(self, instance): |
|
42 |
User = get_user_model() |
|
43 |
qs = User.objects.filter(user_document_set__document=instance) |
|
44 |
return u', '.join(unicode(u) for u in qs) |
|
45 | ||
46 | ||
47 |
class ValidationAdmin(ValidationMixin, admin.ModelAdmin): |
|
48 |
list_display = ['user', 'filename', 'size', 'document_type', 'start', 'end'] |
|
8 | 49 | |
50 |
admin.site.register(models.UserDocument, UserDocumentAdmin) |
|
9 | 51 |
admin.site.register(models.Document, DocumentAdmin) |
52 |
admin.site.register(models.Validation, ValidationAdmin) |
fargo/fargo/forms.py | ||
---|---|---|
1 |
from django.forms import ModelForm |
|
1 |
from django import forms |
|
2 |
from django.utils.translation import ugettext_lazy as _ |
|
3 |
from django.conf import settings |
|
2 | 4 | |
3 |
from . import models |
|
4 | 5 | |
5 |
class UploadForm(ModelForm): |
|
6 |
def __init__(self, *args, **kwargs): |
|
7 |
self.user = kwargs.pop('user') |
|
8 |
super(UploadForm, self).__init__(*args, **kwargs) |
|
6 |
from . import models, utils |
|
7 | ||
8 | ||
9 |
class UploadForm(forms.ModelForm): |
|
10 |
content = forms.FileField( |
|
11 |
label=_('file'), max_length=512) |
|
9 | 12 | |
10 | 13 |
def save(self, *args, **kwargs): |
11 |
self.instance.user = self.user |
|
12 |
self.instance.document_filename = self.files['document_file'].name |
|
14 |
self.instance.filename = self.files['content'].name[:512] |
|
15 |
self.instance.document = models.Document.objects.get_by_file( |
|
16 |
self.files['content']) |
|
13 | 17 |
return super(UploadForm, self).save(*args, **kwargs) |
14 | 18 | |
15 | 19 |
class Meta: |
16 |
model = models.Document |
|
17 |
fields = ['document_file'] |
|
20 |
model = models.UserDocument |
|
21 |
fields = [] |
|
22 | ||
23 | ||
24 |
class ValidationForm(forms.ModelForm): |
|
25 |
def __init__(self, *args, **kwargs): |
|
26 |
self.creator = kwargs.pop('user').get_full_name() |
|
27 |
self.user_document = kwargs.pop('user_document') |
|
28 |
self.document_type = kwargs.pop('document_type') |
|
29 |
self.schema = utils.get_document_type_schema(settings, self.document_type) |
|
30 |
super(ValidationForm, self).__init__(*args, **kwargs) |
|
31 |
for fields in self.schema['metadata']: |
|
32 |
name = fields['name'] |
|
33 |
label = fields['label'] |
|
34 |
self.fields[name] = forms.CharField(required=True, label=label) |
|
35 | ||
36 |
def save(self, commit=True): |
|
37 |
assert commit, 'not committing is unsupported' |
|
38 |
data = {} |
|
39 |
for fields in self.schema['metadata']: |
|
40 |
name = fields['name'] |
|
41 |
value = self.cleaned_data.get(name) |
|
42 |
if value: |
|
43 |
data[fields['name']] = value |
|
44 |
validation, created = models.Validation.objects.get_or_create( |
|
45 |
user_document=self.user_document, |
|
46 |
document_type=self.document_type, |
|
47 |
defaults={ |
|
48 |
'data': data, |
|
49 |
'creator': self.creator, |
|
50 |
'start': self.cleaned_data['start'], |
|
51 |
'end': self.cleaned_data['end'], |
|
52 |
}) |
|
53 |
if not created: |
|
54 |
validation.data = data |
|
55 |
validation.creator = self.creator |
|
56 |
validation.start = self.cleaned_data['start'] |
|
57 |
validation.end = self.cleaned_data['end'] |
|
58 |
validation.save() |
|
59 |
return validation |
|
60 | ||
61 |
class Meta: |
|
62 |
model = models.Validation |
|
63 |
fields = ['start', 'end'] |
fargo/fargo/managers.py | ||
---|---|---|
1 |
from django.db import models |
|
2 | ||
3 |
from . import utils |
|
4 | ||
5 | ||
6 |
class DocumentManager(models.Manager): |
|
7 |
def clean(self): |
|
8 |
'''Remove all documents not linked to an user''' |
|
9 |
qs = self.filter(user_documents__isnull=True) |
|
10 |
for document in qs: |
|
11 |
qs.content.delete(False) |
|
12 |
qs.delete() |
|
13 | ||
14 |
def get_by_file(self, f): |
|
15 |
'''Get document with the same SHA-256 hash as the passde Django file |
|
16 |
like object. |
|
17 |
''' |
|
18 |
file_hash = utils.sha256_of_file(f) |
|
19 |
try: |
|
20 |
o = self.get(content_hash=file_hash) |
|
21 |
except self.model.DoesNotExist: |
|
22 |
o = self.model() |
|
23 |
o.content.save(file_hash, f) |
|
24 |
return o |
fargo/fargo/migrations/0003_auto_20150924_1056.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.db import models, migrations |
|
5 |
import jsonfield.fields |
|
6 |
from django.utils.timezone import utc |
|
7 |
import datetime |
|
8 |
from django.conf import settings |
|
9 | ||
10 | ||
11 |
def clear_document(apps, schema_editor): |
|
12 |
Document = apps.get_model('fargo', 'Document') |
|
13 |
Document.objects.all().delete() |
|
14 | ||
15 | ||
16 |
def noop(apps, schema_editor): |
|
17 |
pass |
|
18 | ||
19 | ||
20 |
class Migration(migrations.Migration): |
|
21 | ||
22 |
dependencies = [ |
|
23 |
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
|
24 |
('fargo', '0002_auto_20150818_2117'), |
|
25 |
] |
|
26 | ||
27 |
operations = [ |
|
28 |
migrations.RunPython(clear_document, noop), |
|
29 |
migrations.CreateModel( |
|
30 |
name='UserDocument', |
|
31 |
fields=[ |
|
32 |
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
|
33 |
('filename', models.CharField(max_length=512, verbose_name='filename')), |
|
34 |
('created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')), |
|
35 |
], |
|
36 |
options={ |
|
37 |
'ordering': ('-created', 'user'), |
|
38 |
'verbose_name': 'user document', |
|
39 |
'verbose_name_plural': 'user documents', |
|
40 |
}, |
|
41 |
), |
|
42 |
migrations.CreateModel( |
|
43 |
name='Validation', |
|
44 |
fields=[ |
|
45 |
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
|
46 |
('document_type', models.CharField(max_length=256, verbose_name='document type')), |
|
47 |
('data', jsonfield.fields.JSONField(null=True, verbose_name='data')), |
|
48 |
('start', models.DateField(verbose_name='start date')), |
|
49 |
('end', models.DateField(verbose_name='end date')), |
|
50 |
('creator', models.CharField(max_length=256, verbose_name='creator')), |
|
51 |
('created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')), |
|
52 |
('user_document', models.ForeignKey(verbose_name='user document', to='fargo.UserDocument')), |
|
53 |
], |
|
54 |
), |
|
55 |
migrations.AlterModelOptions( |
|
56 |
name='document', |
|
57 |
options={'ordering': ('content_hash',), 'verbose_name': 'document', 'verbose_name_plural': 'documents'}, |
|
58 |
), |
|
59 |
migrations.RenameField( |
|
60 |
model_name='document', |
|
61 |
old_name='document_file', |
|
62 |
new_name='content', |
|
63 |
), |
|
64 |
migrations.RemoveField( |
|
65 |
model_name='document', |
|
66 |
name='creation', |
|
67 |
), |
|
68 |
migrations.RemoveField( |
|
69 |
model_name='document', |
|
70 |
name='document_filename', |
|
71 |
), |
|
72 |
migrations.RemoveField( |
|
73 |
model_name='document', |
|
74 |
name='id', |
|
75 |
), |
|
76 |
migrations.RemoveField( |
|
77 |
model_name='document', |
|
78 |
name='user', |
|
79 |
), |
|
80 |
migrations.AddField( |
|
81 |
model_name='document', |
|
82 |
name='content_hash', |
|
83 |
field=models.CharField(default=datetime.datetime(2015, 9, 24, 10, 56, 54, 873399, tzinfo=utc), max_length=128, serialize=False, verbose_name='content hash', primary_key=True), |
|
84 |
preserve_default=False, |
|
85 |
), |
|
86 |
migrations.AddField( |
|
87 |
model_name='userdocument', |
|
88 |
name='document', |
|
89 |
field=models.ForeignKey(related_name='user_documents', verbose_name='document', to='fargo.Document'), |
|
90 |
), |
|
91 |
migrations.AddField( |
|
92 |
model_name='userdocument', |
|
93 |
name='user', |
|
94 |
field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL), |
|
95 |
), |
|
96 |
migrations.RunPython(noop, clear_document), |
|
97 |
] |
fargo/fargo/models.py | ||
---|---|---|
2 | 2 |
from django.db import models |
3 | 3 |
from django.utils.translation import ugettext_lazy as _ |
4 | 4 | |
5 |
class Document(models.Model): |
|
5 |
from jsonfield import JSONField |
|
6 | ||
7 |
from . import utils, managers |
|
8 | ||
9 | ||
10 |
class UserDocument(models.Model): |
|
11 |
'''Document uploaded by an user or an agent''' |
|
6 | 12 |
user = models.ForeignKey( |
7 |
settings.AUTH_USER_MODEL, |
|
8 |
verbose_name=_('user')) |
|
9 |
document_filename = models.CharField( |
|
10 |
verbose_name=_('document filename'), |
|
11 |
max_length=512) |
|
12 |
document_file = models.FileField( |
|
13 |
verbose_name=_('file')) |
|
14 |
creation = models.DateTimeField( |
|
15 |
verbose_name=_('creation date'), |
|
16 |
auto_now_add=True) |
|
13 |
settings.AUTH_USER_MODEL, |
|
14 |
verbose_name=_('user')) |
|
15 |
document = models.ForeignKey( |
|
16 |
'Document', |
|
17 |
related_name='user_documents', |
|
18 |
verbose_name=_('document')) |
|
19 |
filename = models.CharField( |
|
20 |
verbose_name=_('filename'), |
|
21 |
max_length=512) |
|
22 |
created = models.DateTimeField( |
|
23 |
verbose_name=_('creation date'), |
|
24 |
auto_now_add=True) |
|
25 | ||
26 |
class Meta: |
|
27 |
verbose_name = _('user document') |
|
28 |
verbose_name_plural = _('user documents') |
|
29 |
ordering = ('-created', 'user') |
|
30 | ||
31 | ||
32 |
class Validation(models.Model): |
|
33 |
'''Validation of a document as special kind for an user, |
|
34 |
the data field contains metadata extracted from the document. |
|
35 |
''' |
|
36 |
user_document = models.ForeignKey('UserDocument', |
|
37 |
verbose_name=_('user document')) |
|
38 |
document_type = models.CharField(max_length=256, verbose_name=_('document type')) |
|
39 |
data = JSONField(null=True, verbose_name=_('data')) |
|
40 |
start = models.DateField(verbose_name=_('start date')) |
|
41 |
end = models.DateField(verbose_name=_('end date')) |
|
42 |
creator = models.CharField(max_length=256, verbose_name=_('creator')) |
|
43 |
created = models.DateTimeField(verbose_name=_('creation date'), |
|
44 |
auto_now_add=True) |
|
45 | ||
46 | ||
47 |
class Document(models.Model): |
|
48 |
'''Content indexed documents''' |
|
49 |
content_hash = models.CharField( |
|
50 |
primary_key=True, |
|
51 |
max_length=128, |
|
52 |
verbose_name=_('content hash')) |
|
53 |
content = models.FileField( |
|
54 |
verbose_name=_('file')) |
|
55 | ||
56 |
objects = managers.DocumentManager() |
|
57 | ||
58 |
def save(self, *args, **kwargs): |
|
59 |
'''Create content_hash if new''' |
|
60 |
if not self.content_hash: |
|
61 |
self.content_hash = utils.sha256_of_file(self.content) |
|
62 |
super(Document, self).save(*args, **kwargs) |
|
17 | 63 | |
18 | 64 |
def delete(self): |
19 | 65 |
'''Delete file on model delete''' |
... | ... | |
23 | 69 |
class Meta: |
24 | 70 |
verbose_name = _('document') |
25 | 71 |
verbose_name_plural = _('documents') |
26 |
ordering = ('-creation',) |
|
72 |
ordering = ('content_hash',) |
fargo/fargo/tables.py | ||
---|---|---|
1 |
from django.utils.translation import ugettext_lazy as _ |
|
2 | ||
1 | 3 |
import django_tables2 as tables |
4 | ||
2 | 5 |
from . import models |
3 | 6 | |
7 | ||
4 | 8 |
class DocumentTable(tables.Table): |
9 |
'''Display the list of documents of the user''' |
|
10 |
filename = tables.Column(verbose_name=_('filename').title()) |
|
11 |
size = tables.Column(accessor='document.content.size', |
|
12 |
verbose_name=_('size').title()) |
|
13 |
created = tables.DateTimeColumn(verbose_name=_('creation date').title()) |
|
14 | ||
5 | 15 |
class Meta: |
6 | 16 |
model = models.Document |
7 |
fields = ('document_filename', 'creation') |
|
17 |
fields = ('filename', 'size', 'created') |
|
18 |
empty_text = _('You currently have no documents') |
fargo/fargo/utils.py | ||
---|---|---|
1 |
import hashlib |
|
2 |
from django.utils.timezone import utc |
|
3 | ||
4 | ||
5 |
def to_isodate(dt): |
|
6 |
return dt.astimezone(utc).isoformat('T').split('.')[0] + 'Z' |
|
7 | ||
8 | ||
9 |
def get_document_type_schema(settings, document_type): |
|
10 |
for schema in settings.FARGO_DOCUMENT_TYPES: |
|
11 |
if schema.get('name') == document_type: |
|
12 |
return schema |
|
13 | ||
14 | ||
15 |
def sha256_of_file(f): |
|
16 |
'''Compute SHA-256 hash of Django File object''' |
|
17 |
hasher = hashlib.sha256() |
|
18 |
for chunk in f.chunks(): |
|
19 |
hasher.update(chunk) |
|
20 |
return hasher.hexdigest() |
fargo/fargo/views.py | ||
---|---|---|
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, |
|
11 |
HttpResponseForbidden, Http404) |
|
11 | 12 |
from django.core import signing |
12 | 13 |
from django.contrib import messages |
13 | 14 |
from django.contrib.auth import get_user_model, REDIRECT_FIELD_NAME |
... | ... | |
15 | 16 |
from django.contrib.auth import views as auth_views |
16 | 17 |
from django.utils.translation import ugettext as _ |
17 | 18 |
from django.utils.decorators import method_decorator |
19 |
from django.conf import settings |
|
18 | 20 | |
19 | 21 |
from django_tables2 import SingleTableMixin |
20 | 22 | |
21 |
from . import models, forms, tables |
|
23 |
from . import models, forms, tables, utils
|
|
22 | 24 | |
23 | 25 |
try: |
24 | 26 |
from mellon.utils import get_idps |
... | ... | |
30 | 32 |
def __init__(self, *args, **kwargs): |
31 | 33 |
self.logger = logging.getLogger(__name__) |
32 | 34 | |
35 | ||
33 | 36 |
class CommonUpload(Logger, CreateView): |
34 | 37 |
form_class = forms.UploadForm |
35 |
model = models.Document |
|
38 |
model = models.UserDocument
|
|
36 | 39 |
template_name = 'fargo/upload.html' |
37 | 40 | |
38 | 41 |
def get_form_kwargs(self, **kwargs): |
39 | 42 |
kwargs = super(CommonUpload, self).get_form_kwargs(**kwargs) |
40 |
kwargs['user'] = self.request.user |
|
43 |
kwargs['instance'] = models.UserDocument( |
|
44 |
user=self.request.user) |
|
41 | 45 |
return kwargs |
42 | 46 | |
43 | 47 |
def form_valid(self, form): |
44 | 48 |
result = super(CommonUpload, self).form_valid(form) |
45 |
self.logger.info('user uploaded file %r(%s)', |
|
46 |
self.object.document_filename, self.object.pk) |
|
49 |
self.logger.info(u'user uploaded file %s (sha256=%s)', |
|
50 |
self.object.filename, |
|
51 |
self.object.document.content_hash) |
|
47 | 52 |
return result |
48 | 53 | |
54 | ||
49 | 55 |
class Upload(CommonUpload): |
50 | 56 |
def get_success_url(self): |
51 | 57 |
homepage = reverse('home') |
... | ... | |
56 | 62 |
return HttpResponseRedirect(self.get_success_url()) |
57 | 63 |
return super(Upload, self).post(request, *args, **kwargs) |
58 | 64 | |
65 | ||
59 | 66 |
class Documents(object): |
60 | 67 |
def get_queryset(self): |
61 |
return models.Document.objects.filter(user=self.request.user) |
|
68 |
return models.UserDocument.objects \ |
|
69 |
.filter(user=self.request.user) \ |
|
70 |
.select_related('document', 'user') |
|
71 | ||
62 | 72 | |
63 | 73 |
class Homepage(Documents, SingleTableMixin, CommonUpload): |
64 | 74 |
'''Show documents of users, eventually paginate and sort them.''' |
... | ... | |
66 | 76 |
form_class = forms.UploadForm |
67 | 77 |
table_class = tables.DocumentTable |
68 | 78 |
table_pagination = { |
69 |
'per_page': 5,
|
|
79 |
'per_page': 5, |
|
70 | 80 |
} |
71 | 81 | |
72 | 82 |
def get_success_url(self): |
... | ... | |
91 | 101 |
class Document(TemplateView): |
92 | 102 |
template_name = 'fargo/document.html' |
93 | 103 | |
104 | ||
94 | 105 |
class Delete(Logger, DeleteView): |
95 |
model = models.Document |
|
106 |
model = models.UserDocument
|
|
96 | 107 | |
97 | 108 |
def delete(self, request, *args, **kwargs): |
98 | 109 |
result = super(Delete, self).delete(request, *args, **kwargs) |
99 |
messages.info(request, _('File %s deleted') % self.object.document_filename)
|
|
100 |
self.logger.info('user deleted file %r(%s)', |
|
101 |
self.object.document_filename, self.object.pk)
|
|
110 |
messages.info(request, _('File %s deleted') % self.object.filename) |
|
111 |
self.logger.info('user deleted file %r(%s)', self.object.filename,
|
|
112 |
self.object.pk)
|
|
102 | 113 |
return result |
103 | 114 | |
104 | 115 |
def get_success_url(self): |
105 | 116 |
return '../..?%s' % self.request.META['QUERY_STRING'] |
106 | 117 | |
107 |
class Pick(Logger, View): |
|
118 | ||
119 |
class Pick(Documents, Logger, View): |
|
108 | 120 |
http_method_allowed = ['post'] |
109 | 121 | |
110 | 122 |
def post(self, request, pk): |
111 |
document = get_object_or_404(models.Document, pk=pk, user=self.request.user) |
|
123 |
user_document = get_object_or_404(self.get_queryset(), pk=pk, |
|
124 |
user=request.user) |
|
112 | 125 |
pick = self.request.GET['pick'] |
113 |
token = signing.dumps(document.pk) |
|
114 |
url = reverse('remote_download', kwargs={'filename': document.document_filename}) |
|
126 |
token = signing.dumps(user_document.pk) |
|
127 |
url = reverse('remote_download', |
|
128 |
kwargs={'filename': user_document.filename}) |
|
115 | 129 |
url += '?%s' % urllib.urlencode({'token': token}) |
116 | 130 |
url = request.build_absolute_uri(url) |
117 | 131 |
scheme, netloc, path, qs, fragment = urlparse.urlsplit(pick) |
118 | 132 |
qs = urlparse.parse_qs(qs) |
119 |
print url |
|
120 | 133 |
qs['url'] = url |
121 | 134 |
qs = urllib.urlencode(qs, True) |
122 | 135 |
redirect = urlparse.urlunsplit((scheme, netloc, path, qs, fragment)) |
123 |
print 'redirect', redirect
|
|
124 |
self.logger.info('user picked file %r(%s) returned to %s',
|
|
125 |
document.document_filename, document.pk, pick)
|
|
136 |
self.logger.info(u'user picked file %s sha256 %s returned to %s',
|
|
137 |
user_document.filename,
|
|
138 |
user_document.document.content_hash, pick)
|
|
126 | 139 |
return HttpResponseRedirect(redirect) |
127 | 140 | |
128 |
class Download(View): |
|
141 | ||
142 |
class Download(Documents, View): |
|
129 | 143 |
def get(self, request, pk, filename): |
130 |
document = get_object_or_404(models.Document, pk=pk, user=self.request.user) |
|
131 |
return self.return_document(document) |
|
144 |
user_document = get_object_or_404(self.get_queryset(), pk=pk, |
|
145 |
user=self.request.user) |
|
146 |
return self.return_user_document(user_document) |
|
132 | 147 | |
133 |
def return_document(self, document):
|
|
134 |
response = HttpResponse(document.document_file.chunks(),
|
|
135 |
content_type='application/octet-stream') |
|
148 |
def return_user_document(self, user_document):
|
|
149 |
response = HttpResponse(user_document.document.content.chunks(),
|
|
150 |
content_type='application/octet-stream')
|
|
136 | 151 |
response['Content-disposition'] = 'attachment' |
137 | 152 |
return response |
138 | 153 | |
154 | ||
139 | 155 |
class RemoteDownload(Download): |
140 | 156 |
'''Allow downloading any file given the URL contains a signed token''' |
141 | 157 |
def get(self, request, filename): |
... | ... | |
151 | 167 |
return HttpResponseForbidden('token has expired') |
152 | 168 |
except signing.BadSignature: |
153 | 169 |
return HttpResponseForbidden('token signature is invalid') |
154 |
document = get_object_or_404(models.Document, pk=pk) |
|
155 |
return self.return_document(document) |
|
170 |
user_document = get_object_or_404(models.UserDocument, pk=pk) |
|
171 |
return self.return_user_document(user_document) |
|
172 | ||
156 | 173 | |
157 | 174 |
class JSONP(Documents, View): |
158 | 175 |
def get_data(self, request): |
159 | 176 |
d = [] |
160 |
for document in self.get_queryset(): |
|
161 |
url = reverse('download', kwargs={'pk': document.pk, |
|
162 |
'filename': document.document_filename}) |
|
177 |
for user_document in self.get_queryset(): |
|
178 |
url = reverse('download', |
|
179 |
kwargs={'pk': user_document.pk, |
|
180 |
'filename': user_document.filename}) |
|
163 | 181 |
url = request.build_absolute_uri(url) |
164 | 182 |
d.append({ |
165 |
'filename': document.document_filename,
|
|
183 |
'filename': user_document.filename,
|
|
166 | 184 |
'url': url, |
167 | 185 |
}) |
168 | 186 |
return d |
... | ... | |
170 | 188 |
def get(self, request): |
171 | 189 |
callback = request.GET.get('callback', 'callback') |
172 | 190 |
s = '%s(%s)' % (callback.encode('ascii'), |
173 |
dumps(self.get_data(request))) |
|
191 |
dumps(self.get_data(request)))
|
|
174 | 192 |
return HttpResponse(s, content_type='application/javascript') |
175 | 193 | |
194 | ||
176 | 195 |
class JSON(JSONP): |
177 | 196 |
def get(self, request): |
178 | 197 |
username = request.GET.get('username') |
... | ... | |
182 | 201 |
elif not request.user.is_authenticated(): |
183 | 202 |
return method_decorator(login_required)(JSON.get)(self, request) |
184 | 203 |
response = HttpResponse(dumps(self.get_data(request)), |
185 |
content_type='application/json') |
|
204 |
content_type='application/json')
|
|
186 | 205 |
response['Access-Control-Allow-Origin'] = '*' |
187 | 206 |
return response |
188 | 207 | |
189 | 208 | |
209 |
class ChooseDocumentKind(TemplateView): |
|
210 |
template_name = 'fargo/choose_document_kind.html' |
|
211 | ||
212 |
def get_context_data(self, **kwargs): |
|
213 |
ctx = super(ChooseDocumentKind, self).get_context_data(**kwargs) |
|
214 |
ctx['document_kinds'] = settings.FARGO_DOCUMENT_KINDS |
|
215 |
return ctx |
|
216 | ||
217 | ||
190 | 218 |
def login(request, *args, **kwargs): |
191 | 219 |
if any(get_idps()): |
192 | 220 |
if not 'next' in request.GET: |
... | ... | |
207 | 235 |
return HttpResponseRedirect(next_page) |
208 | 236 | |
209 | 237 | |
210 |
home = login_required(Homepage.as_view()) |
|
211 |
document = login_required(Document.as_view()) |
|
212 |
download = login_required(Download.as_view()) |
|
213 |
upload = login_required(Upload.as_view()) |
|
238 |
class Metadata(View): |
|
239 |
def get(self, request, username, content_hash, document_type): |
|
240 |
schema = utils.get_document_type_schema(settings, document_type) |
|
241 |
if not schema: |
|
242 |
raise Http404 |
|
243 |
try: |
|
244 |
user_document = models.UserDocument.objects.get( |
|
245 |
user__username=username, |
|
246 |
document__content_hash=content_hash) |
|
247 |
except models.UserDocument.DoesNotExist: |
|
248 |
raise Http404 |
|
249 |
try: |
|
250 |
validation = models.Validation.objects.get( |
|
251 |
user_document=user_document, |
|
252 |
document_type=document_type) |
|
253 |
except models.Validation.DoesNotExist: |
|
254 |
data = {'err': 1, 'data': 'validation-not-found'} |
|
255 |
else: |
|
256 |
metadata = [] |
|
257 |
data = { |
|
258 |
'err': 0, |
|
259 |
'data': { |
|
260 |
'type': schema['name'], |
|
261 |
'label': schema['label'], |
|
262 |
'creator': validation.creator, |
|
263 |
'created': utils.to_isodate(validation.created), |
|
264 |
'start': validation.start.isoformat(), |
|
265 |
'end': validation.end.isoformat(), |
|
266 |
'metadata': metadata, |
|
267 |
}, |
|
268 |
} |
|
269 |
for field in schema.get('metadata', []): |
|
270 |
value = validation.data.get(field['name']) |
|
271 |
if value: |
|
272 |
metadata.append({ |
|
273 |
'value': value, |
|
274 |
'name': field['name'], |
|
275 |
'label': field['label'], |
|
276 |
}) |
|
277 |
return HttpResponse(dumps(data), content_type='application/json') |
|
278 | ||
279 | ||
280 |
class Validation(CreateView): |
|
281 |
form_class = forms.ValidationForm |
|
282 |
template_name = 'fargo/validation.html' |
|
283 | ||
284 |
def dispatch(self, request, username, content_hash, document_type): |
|
285 |
if 'mellon' in settings.INSTALLED_APPS: |
|
286 |
self.user_document = get_object_or_404( |
|
287 |
models.UserDocument, user__usersamlidentifier__name_id=username, |
|
288 |
document__content_hash=content_hash) |
|
289 |
else: |
|
290 |
self.user_document = get_object_or_404( |
|
291 |
models.UserDocument, user__username=username, |
|
292 |
document__content_hash=content_hash) |
|
293 |
if not utils.get_document_type_schema(settings, document_type): |
|
294 |
raise Http404 |
|
295 |
self.document_type = document_type |
|
296 |
return super(Validation, self).dispatch(request, |
|
297 |
username=username, |
|
298 |
content_hash=content_hash, |
|
299 |
document_type=document_type) |
|
300 | ||
301 |
def post(self, request, *args, **kwargs): |
|
302 |
if 'cancel' in request.POST: |
|
303 |
return HttpResponseRedirect(self.get_success_url()) |
|
304 |
return super(Validation, self).post(request, *args, **kwargs) |
|
305 | ||
306 |
def get_form_kwargs(self, **kwargs): |
|
307 |
kwargs = super(Validation, self).get_form_kwargs(**kwargs) |
|
308 |
kwargs['user'] = self.request.user |
|
309 |
kwargs['user_document'] = self.user_document |
|
310 |
kwargs['document_type'] = self.document_type |
|
311 |
return kwargs |
|
312 | ||
313 |
def get_success_url(self): |
|
314 |
return self.request.GET.get(REDIRECT_FIELD_NAME) or settings.LOGIN_REDIRECT_URL |
|
315 | ||
316 | ||
317 |
class DocumentTypes(View): |
|
318 |
def get(self, request): |
|
319 |
data = { |
|
320 |
'err': 0, |
|
321 |
'data': settings.FARGO_DOCUMENT_TYPES, |
|
322 |
} |
|
323 |
return HttpResponse(dumps(data), content_type='application/json') |
|
324 | ||
325 | ||
326 |
home = login_required(Homepage.as_view()) |
|
327 |
document = login_required(Document.as_view()) |
|
328 |
download = login_required(Download.as_view()) |
|
329 |
upload = login_required(Upload.as_view()) |
|
214 | 330 |
remote_download = RemoteDownload.as_view() |
215 |
delete = login_required(Delete.as_view())
|
|
216 |
pick = login_required(Pick.as_view())
|
|
217 |
jsonp = login_required(JSONP.as_view())
|
|
218 |
json = login_required(JSON.as_view())
|
|
331 |
delete = login_required(Delete.as_view()) |
|
332 |
pick = login_required(Pick.as_view()) |
|
333 |
jsonp = login_required(JSONP.as_view()) |
|
334 |
json = login_required(JSON.as_view()) |
|
219 | 335 |
pick_list = login_required(PickList.as_view()) |
336 |
metadata = Metadata.as_view() |
|
337 |
validation = login_required(Validation.as_view()) |
|
338 |
document_types = DocumentTypes.as_view() |
fargo/settings.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
1 | 2 |
""" |
2 | 3 |
Django settings for fargo project. |
3 | 4 | |
... | ... | |
132 | 133 | |
133 | 134 |
MELLON_IDENTITY_PROVIDERS = [] |
134 | 135 | |
136 |
FARGO_DOCUMENT_TYPES = [ |
|
137 |
{ |
|
138 |
'name': 'justificatif-de-domicile', |
|
139 |
'label': 'Justificatif de domicile', |
|
140 |
'metadata': [ |
|
141 |
{'label': u'Numéro', 'name': 'numero'}, |
|
142 |
{'label': u'Rue', 'name': 'rue'}, |
|
143 |
{'label': u'Code postal', 'name': 'code-postal'}, |
|
144 |
{'label': u'Ville', 'name': 'ville'}, |
|
145 |
] |
|
146 |
} |
|
147 |
] |
|
148 | ||
149 |
LOGIN_REDIRECT_URL = 'home' |
|
150 | ||
135 | 151 |
local_settings_file = os.environ.get('FARGO_SETTINGS_FILE', |
136 | 152 |
os.path.join(os.path.dirname(__file__), 'local_settings.py')) |
137 | 153 |
fargo/static/fargo/css/style.css | ||
---|---|---|
26 | 26 |
background: #ccc; |
27 | 27 |
} |
28 | 28 | |
29 |
body.pick-popup div.table-container { |
|
30 |
width: 100%; |
|
31 |
} |
|
32 | 29 | |
33 |
div.table-container { |
|
34 |
display: inline-block; |
|
35 |
min-width: 40em; |
|
36 |
} |
|
37 | ||
38 |
div.table-container table { |
|
30 |
#user-files table { |
|
39 | 31 |
width: 100%; |
40 |
border-collapse: collapse; |
|
41 |
border: 1px solid #bcbcbc; |
|
42 |
} |
|
43 | ||
44 |
div.table-container table th { |
|
45 |
font-weight: normal; |
|
46 |
padding: 1em 1ex; |
|
47 |
border-bottom: 1px solid #bcbcbc; |
|
48 |
background: #FCFCFC; |
|
49 |
text-align: left; |
|
50 |
} |
|
51 | ||
52 |
div.table-container table td { |
|
53 |
text-align: left; |
|
54 |
padding: 1em 2ex 1em 1ex; |
|
55 |
border-bottom: 1px solid #bcbcbc; |
|
56 |
} |
|
57 | ||
58 |
div.table-container table form { |
|
59 |
margin: 0; |
|
60 |
} |
|
61 | ||
62 |
div.table-container ul.pagination { |
|
63 |
border: 1px solid #bcbcbc; |
|
64 |
background: #FCFCFC; |
|
65 |
border-top: none; |
|
66 |
list-style: none; |
|
67 |
overflow: auto; |
|
68 |
margin: 0; |
|
69 |
padding: 2ex 1ex 1.5ex 1ex; |
|
70 |
} |
|
71 | ||
72 |
div.table-container ul.pagination li { |
|
73 |
float: left; |
|
74 |
line-height: 150%; |
|
75 |
margin-left: 10px; |
|
76 |
} |
|
77 | ||
78 |
div.table-container ul.pagination > li.cardinality { |
|
79 |
float: right; |
|
80 | 32 |
} |
81 | 33 | |
82 | 34 |
thead th.orderable > a { |
fargo/templates/fargo/base_iframe.html | ||
---|---|---|
1 |
{% load gadjo staticfiles %}<!DOCTYPE html> |
|
2 |
<html> |
|
3 |
<head> |
|
4 |
<script src="{% xstatic 'jquery' 'jquery.min.js' %}"></script> |
|
5 |
<link rel="stylesheet" href="{% static "fargo/css/style.css" %}" /> |
|
6 |
{% block extra_head %} |
|
7 |
{% endblock %} |
|
8 |
</head> |
|
9 |
<body {% block bodyargs %}{% endblock %}> |
|
10 |
{% block content %} |
|
11 |
{% endblock %} |
|
12 |
</body> |
|
13 |
</html> |
fargo/templates/fargo/pick.html | ||
---|---|---|
1 |
{% load render_table from django_tables2 %}{% load gadjo i18n staticfiles %}<!DOCTYPE html>
|
|
2 |
<html> |
|
3 |
<head> |
|
4 |
<script src="{% xstatic 'jquery' 'jquery.min.js' %}"></script> |
|
5 |
<link rel="stylesheet" href="{% static "fargo/css/style.css" %}" /> |
|
6 |
</head> |
|
7 |
<body class="pick-popup"> |
|
1 |
{% extends "fargo/base_iframe.html" %}
|
|
2 |
{% load render_table from django_tables2 %} |
|
3 |
{% load i18n %} |
|
4 | ||
5 |
{% block bodyargs %}class="fargo-popup fargo-pick-popup"{% endblock %} |
|
6 | ||
7 |
{% block content %} |
|
8 | 8 |
{% render_table table "fargo/pick_table.html" %} |
9 | 9 |
<form method="post" enctype="multipart/form-data"> |
10 | 10 |
{% csrf_token %} |
... | ... | |
13 | 13 |
<input type="submit" name="cancel" value="{% trans "Cancel" %}"> |
14 | 14 |
</form> |
15 | 15 |
<script type="text/javascript"> |
16 |
$(function() { |
|
17 |
$('table').on('click', 'tbody tr', function() { |
|
18 |
$(this).find('form').submit(); |
|
19 |
}); |
|
20 |
}); |
|
16 |
$(function() {
|
|
17 |
$('table').on('click', 'tbody tr', function() {
|
|
18 |
$(this).find('form').submit();
|
|
19 |
});
|
|
20 |
});
|
|
21 | 21 |
</script> |
22 |
</body> |
|
23 |
</html> |
|
22 |
{% endblock %} |
fargo/templates/fargo/table.html | ||
---|---|---|
33 | 33 |
{% endblock %} |
34 | 34 |
</td> |
35 | 35 |
</tr> |
36 |
<tr><td span="3"><a href="{% url "validation" username=user.username content_hash=row.record.document.content_hash document_type="justificatif-de-domicile" %}">Valider</a></td></tr> |
|
36 | 37 |
{% endblock table.tbody.row %} |
fargo/templates/fargo/validation.html | ||
---|---|---|
1 |
{% extends "fargo/base_iframe.html" %} |
|
2 |
{% load i18n gadjo %} |
|
3 | ||
4 |
{% block extra_head %} |
|
5 |
<link rel="stylesheet" type="text/css" media="all" |
|
6 |
href="{% xstatic 'jquery-ui' 'themes/smoothness/jquery-ui.min.css' %}"/> |
|
7 |
<script src="{% xstatic 'jquery-ui' 'jquery-ui.min.js' %}"></script> |
|
8 |
{% endblock %} |
|
9 | ||
10 |
{% block bodyargs %}class="fargo-popup fargo-pick-popup"{% endblock %} |
|
11 | ||
12 |
{% block content %} |
|
13 |
<form method="post" enctype="multipart/form-data"> |
|
14 |
{% csrf_token %} |
|
15 |
{{ form.as_p }} |
|
16 |
<input type="submit" name="submit" value="{% trans "Validate" %}"> |
|
17 |
<input type="submit" name="cancel" value="{% trans "Cancel" %}"> |
|
18 |
</form> |
|
19 |
<script> |
|
20 |
$('#id_start, #id_end').datepicker(); |
|
21 |
</script> |
|
22 |
{% endblock %} |
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, pick_list) |
|
5 |
from .fargo.views import (home, jsonp, json, document, download, pick, delete, |
|
6 |
upload, remote_download, login, logout, pick_list, |
|
7 |
metadata, validation, document_types) |
|
7 | 8 | |
8 |
urlpatterns = patterns('', |
|
9 |
urlpatterns = patterns( |
|
10 |
'', |
|
9 | 11 |
url(r'^$', home, name='home'), |
10 | 12 |
url(r'^pick$', pick_list, name='list_to_pick'), |
11 | 13 |
url(r'^jsonp/$', jsonp, name='jsonp'), |
... | ... | |
13 | 15 |
url(r'^(?P<pk>\d+)/$', document, name='document'), |
14 | 16 |
url(r'^(?P<pk>\d+)/delete/$', delete, name='delete'), |
15 | 17 |
url(r'^(?P<pk>\d+)/pick/$', pick, name='pick'), |
16 |
url(r'^(?P<pk>\d+)/download/(?P<filename>[^/]*)$', download, name='download'), |
|
18 |
url(r'^(?P<pk>\d+)/download/(?P<filename>[^/]*)$', download, |
|
19 |
name='download'), |
|
17 | 20 |
url(r'^upload/$', upload, name='upload'), |
18 |
url(r'^remote-download/(?P<filename>[^/]*)$', remote_download, name='remote_download'), |
|
21 |
url(r'^remote-download/(?P<filename>[^/]*)$', remote_download, |
|
22 |
name='remote_download'), |
|
23 |
url(r'^metadata/(?P<username>[^/]*)/(?P<content_hash>[^/]*)/(?P<document_type>[^/]*)/$', |
|
24 |
metadata, name='metadata'), |
|
25 |
url(r'^validation/(?P<username>[^/]*)/(?P<content_hash>[^/]*)/(?P<document_type>[^/]*)/$', |
|
26 |
validation, name='validation'), |
|
19 | 27 |
url(r'^admin/', include(admin.site.urls)), |
20 | 28 |
url(r'^login/$', login, name='auth_login'), |
21 | 29 |
url(r'^logout/$', logout, name='auth_logout'), |
30 |
url(r'^document-types/$', document_types, name='document_types'), |
|
22 | 31 |
) |
23 | 32 | |
24 | 33 | |
25 | 34 |
if 'mellon' in settings.INSTALLED_APPS: |
26 |
urlpatterns += patterns('', url(r'^accounts/mellon/', include('mellon.urls'))) |
|
27 | ||
28 |
if settings.DEBUG: |
|
29 |
urlpatterns += patterns('django.contrib.staticfiles.views', |
|
30 |
url(r'^static/(?P<path>.*)$', 'serve'), |
|
31 |
) |
|
35 |
urlpatterns += patterns('', |
|
36 |
url(r'^accounts/mellon/', include('mellon.urls'))) |
setup.py | ||
---|---|---|
97 | 97 |
install_requires=['django>=1.7', |
98 | 98 |
'gadjo', |
99 | 99 |
'django-tables2', |
100 |
'django-jsonfield >= 0.9.3', |
|
100 | 101 |
], |
101 | 102 |
zip_safe=False, |
102 | 103 |
cmdclass={ |
103 |
- |