Projet

Général

Profil

0001-add-validation-of-documents.patch

Benjamin Dauvergne, 05 octobre 2015 16:39

Télécharger (35,7 ko)

Voir les différences:

Subject: [PATCH] add validation of documents

User can validate some document as of being of a certain type, and
attach metadata to this validation event.
 fargo/fargo/admin.py                              |  47 ++++-
 fargo/fargo/forms.py                              |  67 +++++--
 fargo/fargo/managers.py                           |  24 +++
 fargo/fargo/migrations/0003_auto_20150924_1056.py |  97 ++++++++++
 fargo/fargo/models.py                             |  70 ++++++--
 fargo/fargo/tables.py                             |  13 +-
 fargo/fargo/utils.py                              |  20 +++
 fargo/fargo/views.py                              | 209 +++++++++++++++++-----
 fargo/settings.py                                 |  16 ++
 fargo/templates/fargo/base_iframe.html            |  13 ++
 fargo/templates/fargo/pick.html                   |  27 ++-
 fargo/templates/fargo/table.html                  |   1 +
 fargo/templates/fargo/validation.html             |  22 +++
 fargo/urls.py                                     |  27 +--
 setup.py                                          |   1 +
 15 files changed, 562 insertions(+), 92 deletions(-)
 create mode 100644 fargo/fargo/managers.py
 create mode 100644 fargo/fargo/migrations/0003_auto_20150924_1056.py
 create mode 100644 fargo/fargo/utils.py
 create mode 100644 fargo/templates/fargo/base_iframe.html
 create mode 100644 fargo/templates/fargo/validation.html
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
            required = fields.get('required', True)
35
            self.fields[name] = forms.CharField(required=required, label=label)
36

  
37
    def save(self, commit=True):
38
        assert commit, 'not committing is unsupported'
39
        data = {}
40
        for fields in self.schema['metadata']:
41
            name = fields['name']
42
            value = self.cleaned_data.get(name)
43
            if value:
44
                data[fields['name']] = value
45
        validation, created = models.Validation.objects.get_or_create(
46
            user_document=self.user_document,
47
            document_type=self.document_type,
48
            defaults={
49
                'data': data,
50
                'creator': self.creator,
51
                'start': self.cleaned_data['start'],
52
                'end': self.cleaned_data['end'],
53
            })
54
        if not created:
55
            validation.data = data
56
            validation.creator = self.creator
57
            validation.start = self.cleaned_data['start']
58
            validation.end = self.cleaned_data['end']
59
            validation.save()
60
        return validation
61

  
62
    class Meta:
63
        model = models.Validation
64
        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[:30],
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
        self.user_document = get_object_or_404(
286
            models.UserDocument, user__username=username[:30],
287
            document__content_hash=content_hash)
288
        if not utils.get_document_type_schema(settings, document_type):
289
            raise Http404
290
        self.document_type = document_type
291
        return super(Validation, self).dispatch(request,
292
                                                username=username,
293
                                                content_hash=content_hash,
294
                                                document_type=document_type)
295

  
296
    def post(self, request, *args, **kwargs):
297
        if 'cancel' in request.POST:
298
            return HttpResponseRedirect(self.get_success_url())
299
        return super(Validation, self).post(request, *args, **kwargs)
300

  
301
    def get_form_kwargs(self, **kwargs):
302
        kwargs = super(Validation, self).get_form_kwargs(**kwargs)
303
        try:
304
            validation = models.Validation.objects.get(
305
                user_document=self.user_document,
306
                document_type=self.document_type)
307
            kwargs['instance'] = validation
308
            initials = kwargs['initial'] = kwargs.get('initial', {})
309
            for key, value in validation.data.iteritems():
310
                initials[key] = value
311
        except models.Validation.DoesNotExist:
312
            pass
313
        kwargs['user'] = self.request.user
314
        kwargs['user_document'] = self.user_document
315
        kwargs['document_type'] = self.document_type
316
        return kwargs
317

  
318
    def get_success_url(self):
319
        return self.request.GET.get(REDIRECT_FIELD_NAME) \
320
                or reverse(settings.LOGIN_REDIRECT_URL)
321

  
322

  
323
class DocumentTypes(View):
324
    def get(self, request):
325
        data = {
326
            'err': 0,
327
            'data': settings.FARGO_DOCUMENT_TYPES,
328
        }
329
        return HttpResponse(dumps(data), content_type='application/json')
330

  
331

  
332
home = login_required(Homepage.as_view())
333
document = login_required(Document.as_view())
334
download = login_required(Download.as_view())
335
upload = login_required(Upload.as_view())
214 336
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())
337
delete = login_required(Delete.as_view())
338
pick = login_required(Pick.as_view())
339
jsonp = login_required(JSONP.as_view())
340
json = login_required(JSON.as_view())
219 341
pick_list = login_required(PickList.as_view())
342
metadata = Metadata.as_view()
343
validation = login_required(Validation.as_view())
344
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/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
-