trivial: apply black

This commit is contained in:
Frédéric Péters 2021-01-11 20:02:29 +01:00
parent ed779e948e
commit faa1f97b00
37 changed files with 585 additions and 380 deletions

6
debian/settings.py vendored
View File

@ -14,15 +14,15 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
#ADMINS = (
# ADMINS = (
# # ('User 1', 'watchdog@example.net'),
# # ('User 2', 'janitor@example.net'),
#)
# )
# ALLOWED_HOSTS must be correct in production!
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
ALLOWED_HOSTS = [
'*',
'*',
]
# Databases

View File

@ -56,11 +56,18 @@ class UserDocumentAdmin(admin.ModelAdmin):
list_display = ['user', 'filename', 'thumbnail', 'created', 'origin']
fields = ['id', 'user', 'filename', 'thumbnail', 'created', 'origin']
readonly_fields = ['created', 'thumbnail']
search_fields = ['user__first_name', 'user__last_name', 'user__email', 'filename',
'origin__label', 'document__content_hash']
search_fields = [
'user__first_name',
'user__last_name',
'user__email',
'filename',
'origin__label',
'document__content_hash',
]
def thumbnail(self, instance):
return instance.document.thumbnail_img_tag
thumbnail.short_description = _('thumbnail')
@ -77,22 +84,14 @@ class DocumentAdmin(admin.ModelAdmin):
def thumbnail(self, instance):
return instance.thumbnail_img_tag
thumbnail.short_description = _('thumbnail')
class ValidationAdmin(admin.ModelAdmin):
fields = ['user',
'content_hash',
'document_type',
'start',
'end',
'data',
'creator',
'created',
'origin']
fields = ['user', 'content_hash', 'document_type', 'start', 'end', 'data', 'creator', 'created', 'origin']
readonly_fields = ['created']
list_display = ['user', 'display', 'document_type', 'start', 'end', 'creator', 'created',
'origin']
list_display = ['user', 'display', 'document_type', 'start', 'end', 'creator', 'created', 'origin']
class OriginAdmin(admin.ModelAdmin):

View File

@ -97,8 +97,10 @@ class PushDocumentSerializer(UserSerializerMixin):
def validate(self, data):
data = super(PushDocumentSerializer, self).validate(data)
user = data['user']
if (Document.objects.used_space(user) + data['file_b64_content'].size
> settings.FARGO_MAX_DOCUMENT_BOX_SIZE):
if (
Document.objects.used_space(user) + data['file_b64_content'].size
> settings.FARGO_MAX_DOCUMENT_BOX_SIZE
):
raise api_errors.APIError('BOX_IS_FULL', limit=str(settings.FARGO_MAX_DOCUMENT_BOX_SIZE))
return data
@ -115,23 +117,24 @@ class PushDocument(CommonAPIMixin, GenericAPIView):
data = serializer.validated_data
origin, created = Origin.objects.get_or_create(
slug=slugify(data.get('origin')),
defaults={'label': data.get('origin')})
slug=slugify(data.get('origin')), defaults={'label': data.get('origin')}
)
document_file = data['file_b64_content']
if data.get('file_name'):
document_file.name = data.get('file_name')
content_hash = utils.sha256_of_file(document_file)
document, created = Document.objects.get_or_create(
content_hash=content_hash,
defaults={'content': document_file})
content_hash=content_hash, defaults={'content': document_file}
)
user_document, created = UserDocument.objects.get_or_create(
user=data.get('user'),
filename=data.get('file_name'),
document=document,
origin=origin,
deletable_by_user=data.get('deletable_by_user'))
deletable_by_user=data.get('deletable_by_user'),
)
if not created:
raise api_errors.APIError('DOCUMENT_EXISTS')
user_document.save()
@ -139,6 +142,7 @@ class PushDocument(CommonAPIMixin, GenericAPIView):
response_status = status.HTTP_200_OK
return Response(None, response_status)
push_document = PushDocument.as_view()
@ -155,10 +159,10 @@ class RecentDocuments(ListAPIView):
def get_queryset(self):
return UserDocument.objects.filter(
user=self.request.user,
created__gt=datetime.datetime.now() - datetime.timedelta(days=14)
user=self.request.user, created__gt=datetime.datetime.now() - datetime.timedelta(days=14)
).order_by('-created')[:10]
recent_documents = RecentDocuments.as_view()
@ -176,11 +180,13 @@ class ValidationSerializer(UserSerializerMixin, serializers.ModelSerializer):
name = field['varname']
required = field.get('required', True)
self.fields[name] = serializers.CharField(
source='data.%s' % name, required=required, allow_blank=True)
source='data.%s' % name, required=required, allow_blank=True
)
def get_url(self, instance):
url = reverse('fargo-api-validation-detail',
kwargs={'document_type': instance.document_type, 'pk': instance.pk})
url = reverse(
'fargo-api-validation-detail', kwargs={'document_type': instance.document_type, 'pk': instance.pk}
)
if 'request' in self.context:
url = self.context['request'].build_absolute_uri(url)
return url
@ -209,11 +215,13 @@ class FilterByUser(filters.BaseFilterBackend):
return queryset
class ValidationAPI(CommonAPIMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
class ValidationAPI(
CommonAPIMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet,
):
serializer_class = ValidationSerializer
permission_classes = (IsAdminUser,)
filter_backends = [FilterByUser]
@ -235,6 +243,6 @@ class ValidationAPI(CommonAPIMixin,
# pass schema to serializer class
return super(ValidationAPI, self).get_serializer(schema=self.document_type_schema, *args, **kwargs)
router = routers.SimpleRouter()
router.register(r'validation/(?P<document_type>[^/]*)', ValidationAPI,
base_name='fargo-api-validation')
router.register(r'validation/(?P<document_type>[^/]*)', ValidationAPI, base_name='fargo-api-validation')

View File

@ -24,30 +24,33 @@ from . import models
class UploadForm(forms.ModelForm):
content = forms.FileField(
label=_('file'), max_length=512)
content = forms.FileField(label=_('file'), max_length=512)
def clean_content(self):
content = self.cleaned_data.get('content')
if content:
if content.size > settings.FARGO_MAX_DOCUMENT_SIZE:
raise forms.ValidationError(_('Uploaded file is too big (limit is %s)') %
filesizeformat(settings.FARGO_MAX_DOCUMENT_SIZE))
raise forms.ValidationError(
_('Uploaded file is too big (limit is %s)')
% filesizeformat(settings.FARGO_MAX_DOCUMENT_SIZE)
)
return content
def clean(self):
content = self.cleaned_data.get('content')
if content:
if (models.Document.objects.used_space(self.instance.user) + content.size
> settings.FARGO_MAX_DOCUMENT_BOX_SIZE):
raise forms.ValidationError(_('Your document box is full (limit is %s)') %
settings.FARGO_MAX_DOCUMENT_BOX_SIZE)
if (
models.Document.objects.used_space(self.instance.user) + content.size
> settings.FARGO_MAX_DOCUMENT_BOX_SIZE
):
raise forms.ValidationError(
_('Your document box is full (limit is %s)') % settings.FARGO_MAX_DOCUMENT_BOX_SIZE
)
return self.cleaned_data
def save(self, *args, **kwargs):
self.instance.filename = self.files['content'].name[:512]
self.instance.document = models.Document.objects.get_by_file(
self.files['content'])
self.instance.document = models.Document.objects.get_by_file(self.files['content'])
return super(UploadForm, self).save(*args, **kwargs)
class Meta:
@ -59,6 +62,4 @@ class EditForm(forms.ModelForm):
class Meta:
model = models.UserDocument
fields = ['title', 'description', 'expiration_date']
widgets = {
'expiration_date': forms.TextInput(attrs={'type': 'date'})
}
widgets = {'expiration_date': forms.TextInput(attrs={'type': 'date'})}

View File

@ -28,16 +28,15 @@ class DocumentManager(models.Manager):
n = n or now()
# use a window of 60 seconds to be sure this document will never be used
qs = self.filter(creation_date__lt=n - datetime.timedelta(seconds=60))
qs = qs.filter(user_documents__isnull=True,
oauth2_tempfiles__isnull=True)
qs = qs.filter(user_documents__isnull=True, oauth2_tempfiles__isnull=True)
for document in qs:
document.content.delete(False)
qs.delete()
def get_by_file(self, f):
'''Get document with the same SHA-256 hash as the passde Django file
like object.
'''
"""Get document with the same SHA-256 hash as the passde Django file
like object.
"""
file_hash = utils.sha256_of_file(f)
try:
o = self.get(content_hash=file_hash)

View File

@ -15,14 +15,21 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Document',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('document_filename', models.CharField(max_length=512, verbose_name='document filename')),
('document_file', models.FileField(upload_to=b'', verbose_name='file')),
('creation', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
(
'user',
models.ForeignKey(
verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
),
),
],
options={
},
options={},
bases=(models.Model,),
),
]

View File

@ -12,7 +12,25 @@ import jsonfield.fields
class Migration(migrations.Migration):
replaces = [('fargo', '0001_initial'), ('fargo', '0002_auto_20150818_2117'), ('fargo', '0003_auto_20150924_1056'), ('fargo', '0004_auto_20160212_0936'), ('fargo', '0005_auto_20160312_1809'), ('fargo', '0006_fill_new_columns'), ('fargo', '0007_auto_20160312_1816'), ('fargo', '0008_validation_origin'), ('fargo', '0009_auto_20160326_2104'), ('fargo', '0010_auto_20160413_0809'), ('fargo', '0011_userdocument_deletable_by_user'), ('fargo', '0012_auto_20161124_0626'), ('fargo', '0013_document_mime_type'), ('fargo', '0014_auto_20171016_0854'), ('fargo', '0015_document_creation_date'), ('fargo', '0016_auto_20180330_2248'), ('fargo', '0017_auto_20180331_1532')]
replaces = [
('fargo', '0001_initial'),
('fargo', '0002_auto_20150818_2117'),
('fargo', '0003_auto_20150924_1056'),
('fargo', '0004_auto_20160212_0936'),
('fargo', '0005_auto_20160312_1809'),
('fargo', '0006_fill_new_columns'),
('fargo', '0007_auto_20160312_1816'),
('fargo', '0008_validation_origin'),
('fargo', '0009_auto_20160326_2104'),
('fargo', '0010_auto_20160413_0809'),
('fargo', '0011_userdocument_deletable_by_user'),
('fargo', '0012_auto_20161124_0626'),
('fargo', '0013_document_mime_type'),
('fargo', '0014_auto_20171016_0854'),
('fargo', '0015_document_creation_date'),
('fargo', '0016_auto_20180330_2248'),
('fargo', '0017_auto_20180331_1532'),
]
initial = True
@ -24,7 +42,12 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Document',
fields=[
('content_hash', models.CharField(max_length=128, serialize=False, verbose_name='content hash', primary_key=True)),
(
'content_hash',
models.CharField(
max_length=128, serialize=False, verbose_name='content hash', primary_key=True
),
),
('content', models.FileField(upload_to=b'uploads/', max_length=300, verbose_name='file')),
('mime_type', models.CharField(max_length=256, blank=True)),
('creation_date', models.DateTimeField(auto_now_add=True)),
@ -38,7 +61,10 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Origin',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('label', models.CharField(max_length=80, verbose_name='Label')),
('slug', models.SlugField(verbose_name='Slug')),
],
@ -46,16 +72,40 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='UserDocument',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('filename', models.CharField(max_length=512, verbose_name='filename')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
('deletable_by_user', models.BooleanField(default=True, verbose_name='deletable by user')),
('title', models.CharField(max_length=200, verbose_name='title', blank=True)),
('description', models.TextField(verbose_name='description', blank=True)),
('expiration_date', models.DateField(null=True, verbose_name='expiration date', blank=True)),
('document', models.ForeignKey(related_name='user_documents', verbose_name='document', to='fargo.Document', on_delete=models.CASCADE)),
('origin', models.ForeignKey(verbose_name='origin', to='fargo.Origin', null=True, on_delete=models.CASCADE)),
('user', models.ForeignKey(related_name='user_documents', verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
(
'document',
models.ForeignKey(
related_name='user_documents',
verbose_name='document',
to='fargo.Document',
on_delete=models.CASCADE,
),
),
(
'origin',
models.ForeignKey(
verbose_name='origin', to='fargo.Origin', null=True, on_delete=models.CASCADE
),
),
(
'user',
models.ForeignKey(
related_name='user_documents',
verbose_name='user',
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
),
),
],
options={
'ordering': ('-created', 'user'),
@ -66,16 +116,32 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Validation',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('content_hash', models.CharField(max_length=128, null=True, verbose_name='content hash', blank=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
(
'content_hash',
models.CharField(max_length=128, null=True, verbose_name='content hash', blank=True),
),
('document_type', models.CharField(max_length=256, verbose_name='document type')),
('data', jsonfield.fields.JSONField(null=True, verbose_name='data')),
('start', models.DateField(verbose_name='start date')),
('end', models.DateField(verbose_name='end date')),
('creator', models.CharField(max_length=256, verbose_name='creator')),
('created', models.DateTimeField(verbose_name='creation date')),
('origin', models.ForeignKey(verbose_name='origin', to='fargo.Origin', null=True, on_delete=models.CASCADE)),
('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
(
'origin',
models.ForeignKey(
verbose_name='origin', to='fargo.Origin', null=True, on_delete=models.CASCADE
),
),
(
'user',
models.ForeignKey(
verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
),
),
],
),
]

View File

@ -13,6 +13,10 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='document',
options={'ordering': ('-creation',), 'verbose_name': 'document', 'verbose_name_plural': 'documents'},
options={
'ordering': ('-creation',),
'verbose_name': 'document',
'verbose_name_plural': 'documents',
},
),
]

View File

@ -29,7 +29,10 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='UserDocument',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('filename', models.CharField(max_length=512, verbose_name='filename')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
],
@ -42,19 +45,31 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Validation',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('document_type', models.CharField(max_length=256, verbose_name='document type')),
('data', jsonfield.fields.JSONField(null=True, verbose_name='data')),
('start', models.DateField(verbose_name='start date')),
('end', models.DateField(verbose_name='end date')),
('creator', models.CharField(max_length=256, verbose_name='creator')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
('user_document', models.ForeignKey(verbose_name='user document', to='fargo.UserDocument', on_delete=models.CASCADE)),
(
'user_document',
models.ForeignKey(
verbose_name='user document', to='fargo.UserDocument', on_delete=models.CASCADE
),
),
],
),
migrations.AlterModelOptions(
name='document',
options={'ordering': ('content_hash',), 'verbose_name': 'document', 'verbose_name_plural': 'documents'},
options={
'ordering': ('content_hash',),
'verbose_name': 'document',
'verbose_name_plural': 'documents',
},
),
migrations.RenameField(
model_name='document',
@ -80,18 +95,31 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='document',
name='content_hash',
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),
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,
),
preserve_default=False,
),
migrations.AddField(
model_name='userdocument',
name='document',
field=models.ForeignKey(related_name='user_documents', verbose_name='document', to='fargo.Document', on_delete=models.CASCADE),
field=models.ForeignKey(
related_name='user_documents',
verbose_name='document',
to='fargo.Document',
on_delete=models.CASCADE,
),
),
migrations.AddField(
model_name='userdocument',
name='user',
field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
field=models.ForeignKey(
verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
),
),
migrations.RunPython(noop, clear_document),
]

View File

@ -14,18 +14,22 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Origin',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('label', models.CharField(max_length=80, verbose_name='Label')),
('slug', models.SlugField(verbose_name='Slug')),
],
options={
},
options={},
bases=(models.Model,),
),
migrations.AddField(
model_name='userdocument',
name='origin',
field=models.ForeignKey(verbose_name='origin', to='fargo.Origin', null=True, on_delete=models.CASCADE),
field=models.ForeignKey(
verbose_name='origin', to='fargo.Origin', null=True, on_delete=models.CASCADE
),
preserve_default=True,
),
]

View File

@ -21,6 +21,8 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='validation',
name='user',
field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE),
field=models.ForeignKey(
verbose_name='user', to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE
),
),
]

View File

@ -19,6 +19,8 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='validation',
name='user',
field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
field=models.ForeignKey(
verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
),
),
]

View File

@ -14,6 +14,8 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='validation',
name='origin',
field=models.ForeignKey(verbose_name='origin', to='fargo.Origin', null=True, on_delete=models.CASCADE),
field=models.ForeignKey(
verbose_name='origin', to='fargo.Origin', null=True, on_delete=models.CASCADE
),
),
]

View File

@ -15,7 +15,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='userdocument',
name='user',
field=models.ForeignKey(related_name='user_documents', verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
field=models.ForeignKey(
related_name='user_documents',
verbose_name='user',
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
),
),
migrations.AlterField(
model_name='validation',

View File

@ -14,6 +14,10 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='document',
options={'ordering': ('creation_date',), 'verbose_name': 'document', 'verbose_name_plural': 'documents'},
options={
'ordering': ('creation_date',),
'verbose_name': 'document',
'verbose_name_plural': 'documents',
},
),
]

View File

@ -59,41 +59,23 @@ class Origin(models.Model):
@python_2_unicode_compatible
class UserDocument(models.Model):
'''Document uploaded by an user or an agent'''
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('user'),
related_name='user_documents',
on_delete=models.CASCADE)
on_delete=models.CASCADE,
)
document = models.ForeignKey(
'Document',
related_name='user_documents',
verbose_name=_('document'),
on_delete=models.CASCADE)
filename = models.CharField(
verbose_name=_('filename'),
max_length=512)
created = models.DateTimeField(
verbose_name=_('creation date'),
auto_now_add=True)
origin = models.ForeignKey(
Origin,
verbose_name=_('origin'),
null=True,
on_delete=models.CASCADE)
deletable_by_user = models.BooleanField(
verbose_name=_('deletable by user'),
default=True)
title = models.CharField(
verbose_name=_('title'),
max_length=200,
blank=True)
description = models.TextField(
verbose_name=_('description'),
blank=True)
expiration_date = models.DateField(
verbose_name=_('expiration date'),
blank=True,
null=True)
'Document', related_name='user_documents', verbose_name=_('document'), on_delete=models.CASCADE
)
filename = models.CharField(verbose_name=_('filename'), max_length=512)
created = models.DateTimeField(verbose_name=_('creation date'), auto_now_add=True)
origin = models.ForeignKey(Origin, verbose_name=_('origin'), null=True, on_delete=models.CASCADE)
deletable_by_user = models.BooleanField(verbose_name=_('deletable by user'), default=True)
title = models.CharField(verbose_name=_('title'), max_length=200, blank=True)
description = models.TextField(verbose_name=_('description'), blank=True)
expiration_date = models.DateField(verbose_name=_('expiration date'), blank=True, null=True)
class Meta:
verbose_name = _('user document')
@ -125,28 +107,19 @@ class UserDocument(models.Model):
return ''
return 'mime-%s mime-%s' % (
self.document.mime_type.split('/')[0],
re.sub(r'[/\.+-]', '-', self.document.mime_type))
re.sub(r'[/\.+-]', '-', self.document.mime_type),
)
@python_2_unicode_compatible
class Validation(models.Model):
'''Validation of a document as special kind for an user,
the data field contains metadata extracted from the document.
'''
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('user'),
on_delete=models.CASCADE)
content_hash = models.CharField(
max_length=128,
verbose_name=_('content hash'),
blank=True,
null=True)
origin = models.ForeignKey(
Origin,
verbose_name=_('origin'),
null=True,
on_delete=models.CASCADE)
"""Validation of a document as special kind for an user,
the data field contains metadata extracted from the document.
"""
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), on_delete=models.CASCADE)
content_hash = models.CharField(max_length=128, verbose_name=_('content hash'), blank=True, null=True)
origin = models.ForeignKey(Origin, verbose_name=_('origin'), null=True, on_delete=models.CASCADE)
document_type = models.CharField(max_length=256, verbose_name=_('document type'))
data = JSONField(null=True, verbose_name=_('data'))
start = models.DateField(verbose_name=_('start date'))
@ -172,11 +145,14 @@ class Validation(models.Model):
parts = []
for meta_field in self.metadata:
parts.append(
_(u'%(label)s: %(value)s') % {
_(u'%(label)s: %(value)s')
% {
'label': meta_field['label'],
'value': self.data.get(meta_field['varname'], ''),
})
}
)
return force_text(u'; '.join(parts))
display.short_description = _('description')
@property
@ -193,17 +169,10 @@ class Validation(models.Model):
class Document(models.Model):
'''Content indexed documents'''
content_hash = models.CharField(
primary_key=True,
max_length=128,
verbose_name=_('content hash'))
content = models.FileField(
upload_to='uploads/',
max_length=300,
verbose_name=_('file'))
mime_type = models.CharField(
max_length=256,
blank=True)
content_hash = models.CharField(primary_key=True, max_length=128, verbose_name=_('content hash'))
content = models.FileField(upload_to='uploads/', max_length=300, verbose_name=_('file'))
mime_type = models.CharField(max_length=256, blank=True)
creation_date = models.DateTimeField(auto_now_add=True)
objects = managers.DocumentManager()
@ -247,10 +216,12 @@ class Document(models.Model):
if not thumbnail:
return ''
return format_html('<img width="{}" height="{}" src="{}"/>',
thumbnail.width,
thumbnail.height,
self.thumbnail_data_url)
return format_html(
'<img width="{}" height="{}" src="{}"/>',
thumbnail.width,
thumbnail.height,
self.thumbnail_data_url,
)
@property
def thumbnail_image(self):

View File

@ -23,9 +23,12 @@ from . import models
class DocumentTable(tables.Table):
'''Display the list of documents of the user'''
size = tables.TemplateColumn(template_code='{{ record.document.content.size|filesizeformat }}',
orderable=False,
verbose_name=_('Size'))
size = tables.TemplateColumn(
template_code='{{ record.document.content.size|filesizeformat }}',
orderable=False,
verbose_name=_('Size'),
)
created = tables.DateTimeColumn(verbose_name=_('Creation Date'))
class Meta:

View File

@ -24,8 +24,13 @@ from django.views.generic import CreateView, DeleteView, UpdateView, View, Templ
from django.urls import reverse, reverse_lazy
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, resolve_url
from django.http import (HttpResponse, HttpResponseRedirect,
HttpResponseBadRequest, HttpResponseForbidden, Http404)
from django.http import (
HttpResponse,
HttpResponseRedirect,
HttpResponseBadRequest,
HttpResponseForbidden,
Http404,
)
from django.core import signing
from django.contrib import messages
from django.contrib.auth import get_user_model, REDIRECT_FIELD_NAME
@ -47,6 +52,7 @@ from . import models, forms, tables
try:
from mellon.utils import get_idps
except ImportError:
def get_idps():
return []
@ -58,9 +64,7 @@ class Logger(object):
class Documents(object):
def get_queryset(self):
return models.UserDocument.objects \
.filter(user=self.request.user) \
.select_related('document', 'user')
return models.UserDocument.objects.filter(user=self.request.user).select_related('document', 'user')
@cached_property
def count(self):
@ -78,15 +82,14 @@ class CommonUpload(Logger, Documents, CreateView):
def get_form_kwargs(self, **kwargs):
kwargs = super(CommonUpload, self).get_form_kwargs(**kwargs)
kwargs['instance'] = models.UserDocument(
user=self.request.user)
kwargs['instance'] = models.UserDocument(user=self.request.user)
return kwargs
def form_valid(self, form):
result = super(CommonUpload, self).form_valid(form)
self.logger.info(u'user uploaded file %s (sha256=%s)',
self.object.filename,
self.object.document.content_hash)
self.logger.info(
u'user uploaded file %s (sha256=%s)', self.object.filename, self.object.document.content_hash
)
return result
@ -108,6 +111,7 @@ class Upload(CommonUpload):
class Homepage(SingleTableMixin, CommonUpload):
'''Show documents of users, eventually paginate and sort them.'''
template_name = 'fargo/home.html'
form_class = forms.UploadForm
table_class = tables.DocumentTable
@ -134,7 +138,6 @@ class Homepage(SingleTableMixin, CommonUpload):
return super(CommonUpload, self).post(request, *args, **kwargs)
class PickView(object):
def dispatch(self, request, *args, **kwargs):
self.pick_url = request.GET.get('pick')
@ -160,8 +163,7 @@ class Delete(Logger, Documents, DeleteView):
raise PermissionDenied()
result = super(Delete, self).delete(request, *args, **kwargs)
messages.info(request, _('File %s deleted') % self.object.filename)
self.logger.info('user deleted file %r(%s)', self.object.filename,
self.object.pk)
self.logger.info('user deleted file %r(%s)', self.object.filename, self.object.pk)
return result
def get_success_url(self):
@ -185,39 +187,41 @@ class Pick(PickView, Documents, Logger, View):
http_method_allowed = ['post']
def post(self, request, pk):
user_document = get_object_or_404(self.get_queryset(), pk=pk,
user=request.user)
user_document = get_object_or_404(self.get_queryset(), pk=pk, user=request.user)
token = signing.dumps(user_document.pk)
download_url = make_url(
reverse('remote_download', kwargs={'filename': user_document.filename}),
token=token,
request=request)
self.logger.info(u'user picked file %s sha256 %s returned to %s',
user_document.filename,
user_document.document.content_hash, pick)
request=request,
)
self.logger.info(
u'user picked file %s sha256 %s returned to %s',
user_document.filename,
user_document.document.content_hash,
pick,
)
return HttpResponseRedirect(make_url(self.pick_url, url=download_url))
class Download(Documents, Logger, View):
def get(self, request, pk, filename):
user_document = get_object_or_404(self.get_queryset(), pk=pk,
user=self.request.user)
self.logger.info('user download file %s with hash %s',
user_document.filename,
user_document.document.content_hash)
user_document = get_object_or_404(self.get_queryset(), pk=pk, user=self.request.user)
self.logger.info(
'user download file %s with hash %s', user_document.filename, user_document.document.content_hash
)
return self.return_user_document(user_document)
def return_user_document(self, user_document):
response = HttpResponse(user_document.document.content.chunks(),
content_type='application/octet-stream')
response = HttpResponse(
user_document.document.content.chunks(), content_type='application/octet-stream'
)
response['Content-disposition'] = 'attachment'
return response
class Thumbnail(Documents, View):
def get(self, request, pk, filename):
user_document = get_object_or_404(self.get_queryset(), pk=pk,
user=self.request.user)
user_document = get_object_or_404(self.get_queryset(), pk=pk, user=self.request.user)
thumbnail = user_document.document.thumbnail
if not thumbnail:
raise Http404
@ -226,6 +230,7 @@ class Thumbnail(Documents, View):
class RemoteDownload(Download):
'''Allow downloading any file given the URL contains a signed token'''
def get(self, request, filename):
if 'token' not in request.GET:
return HttpResponseForbidden('missing token')
@ -240,11 +245,13 @@ class RemoteDownload(Download):
except signing.BadSignature:
return HttpResponseForbidden('token signature is invalid')
user_document = get_object_or_404(models.UserDocument, pk=pk)
self.logger.info('anonymous download of file %s from user %s(%s) with hash %s',
user_document.filename,
user_document.user,
user_document.user.pk,
user_document.document.content_hash)
self.logger.info(
'anonymous download of file %s from user %s(%s) with hash %s',
user_document.filename,
user_document.user,
user_document.user.pk,
user_document.document.content_hash,
)
return self.return_user_document(user_document)
@ -252,20 +259,19 @@ class JSONP(Documents, View):
def get_data(self, request):
d = []
for user_document in self.get_queryset():
url = reverse('download',
kwargs={'pk': user_document.pk,
'filename': user_document.filename})
url = reverse('download', kwargs={'pk': user_document.pk, 'filename': user_document.filename})
url = request.build_absolute_uri(url)
d.append({
'filename': user_document.filename,
'url': url,
})
d.append(
{
'filename': user_document.filename,
'url': url,
}
)
return d
def get(self, request):
callback = request.GET.get('callback', 'callback')
s = '%s(%s)' % (callback.encode('ascii'),
dumps(self.get_data(request)))
s = '%s(%s)' % (callback.encode('ascii'), dumps(self.get_data(request)))
return HttpResponse(s, content_type='application/javascript')
@ -277,8 +283,7 @@ class JSON(JSONP):
request.user = get_object_or_404(User, username=username)
elif not request.user.is_authenticated():
return method_decorator(login_required)(JSON.get)(self, request)
response = HttpResponse(dumps(self.get_data(request)),
content_type='application/json')
response = HttpResponse(dumps(self.get_data(request)), content_type='application/json')
response['Access-Control-Allow-Origin'] = '*'
return response

View File

@ -26,17 +26,28 @@ class OAuth2ClientAdmin(admin.ModelAdmin):
class OAuth2AuthorizeAdmin(admin.ModelAdmin):
list_display = ['id', 'client_name', 'user_document', 'thumbnail',
'access_token', 'code', 'creation_date']
list_display = [
'id',
'client_name',
'user_document',
'thumbnail',
'access_token',
'code',
'creation_date',
]
raw_id_fields = ['user_document']
search_fields = ['client__client_name', 'user_document__user__email',
'user_document__user__first_name',
'user_document__user__last_name',
'user_document__filename',
'user_document__user__contenat_has']
search_fields = [
'client__client_name',
'user_document__user__email',
'user_document__user__first_name',
'user_document__user__last_name',
'user_document__filename',
'user_document__user__contenat_has',
]
def thumbnail(self, instance):
return instance.user_document.document.thumbnail_img_tag
thumbnail.short_description = _('thumbnail')
def client_name(self, instance):
@ -48,9 +59,9 @@ class OAuth2TempFileAdmin(admin.ModelAdmin):
raw_id_fields = ['document']
search_fields = ['filename', 'uuid', 'client__client_name']
def thumbnail(self, instance):
return instance.document.thumbnail_img_tag
thumbnail.short_description = _('thumbnail')
def client_name(self, instance):

View File

@ -30,8 +30,7 @@ from .models import OAuth2Client
class OAuth2User(object):
""" Fake user class to return in case OAuth2 Client authentication
"""
"""Fake user class to return in case OAuth2 Client authentication"""
def __init__(self, oauth2_client):
self.oauth2_client = oauth2_client
@ -57,11 +56,10 @@ class OAuth2User(object):
class FargoOAUTH2Authentication(BasicAuthentication):
def authenticate_through_idp(self, client_id, client_secret):
'''Check client_id and client_secret with configured IdP, and verify it is an OIDC
client.
'''
"""Check client_id and client_secret with configured IdP, and verify it is an OIDC
client.
"""
logger = logging.getLogger(__name__)
authentic_idp = getattr(settings, 'FARGO_IDP_URL', None)
@ -71,9 +69,12 @@ class FargoOAUTH2Authentication(BasicAuthentication):
url = urlparse.urljoin(authentic_idp, 'api/check-password/')
try:
response = requests.post(url, json={
'username': client_id,
'password': client_secret}, auth=(client_id, client_secret), verify=False)
response = requests.post(
url,
json={'username': client_id, 'password': client_secret},
auth=(client_id, client_secret),
verify=False,
)
response.raise_for_status()
except requests.RequestException as e:
logger.warning(u'idp check-password API failed: %s', e)
@ -92,8 +93,7 @@ class FargoOAUTH2Authentication(BasicAuthentication):
def authenticate_credentials(self, client_id, client_secret, request=None):
try:
client = OAuth2Client.objects.get(
client_id=client_id, client_secret=client_secret)
client = OAuth2Client.objects.get(client_id=client_id, client_secret=client_secret)
except OAuth2Client.DoesNotExist:
success, error = self.authenticate_through_idp(client_id, client_secret)
if not success:

View File

@ -41,8 +41,7 @@ class Command(BaseCommand):
f = ContentFile(file_object.read(), name=filename)
document = Document.objects.get_by_file(f)
oauth2_document = OAuth2TempFile.objects.create(
client=client,
document=document,
filename=filename)
client=client, document=document, filename=filename
)
uri = reverse('oauth2-put-document-authorize', args=[oauth2_document.pk])
self.stdout.write('https://localhost:8000' + make_url(uri, redirect_uri=redirect_uri))

View File

@ -15,7 +15,10 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='OAuth2Authorize',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('access_token', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('code', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('creation_date', models.DateTimeField(auto_now=True)),
@ -25,11 +28,22 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='OAuth2Client',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('client_secret', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
(
'client_secret',
models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255),
),
('client_id', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('client_name', models.CharField(max_length=255)),
('redirect_uris', models.TextField(verbose_name='redirect URIs', validators=[fargo.oauth2.models.validate_https_url])),
(
'redirect_uris',
models.TextField(
verbose_name='redirect URIs', validators=[fargo.oauth2.models.validate_https_url]
),
),
],
),
migrations.CreateModel(
@ -37,7 +51,12 @@ class Migration(migrations.Migration):
fields=[
('hash_key', models.CharField(max_length=128, serialize=False, primary_key=True)),
('filename', models.CharField(max_length=512)),
('document', models.ForeignKey(to='fargo.Document', related_name='oauth2_tempfiles', on_delete=models.CASCADE)),
(
'document',
models.ForeignKey(
to='fargo.Document', related_name='oauth2_tempfiles', on_delete=models.CASCADE
),
),
],
),
]

View File

@ -9,7 +9,13 @@ import fargo.oauth2.models
class Migration(migrations.Migration):
replaces = [('oauth2', '0001_initial'), ('oauth2', '0002_auto_20180321_2343'), ('oauth2', '0003_auto_20180322_1016'), ('oauth2', '0004_auto_20180326_1330'), ('oauth2', '0005_auto_20180331_1532')]
replaces = [
('oauth2', '0001_initial'),
('oauth2', '0002_auto_20180321_2343'),
('oauth2', '0003_auto_20180322_1016'),
('oauth2', '0004_auto_20180326_1330'),
('oauth2', '0005_auto_20180331_1532'),
]
initial = True
@ -21,7 +27,10 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='OAuth2Authorize',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('access_token', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('code', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('creation_date', models.DateTimeField(auto_now_add=True)),
@ -35,11 +44,22 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='OAuth2Client',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
(
'id',
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
),
('client_name', models.CharField(max_length=255)),
('redirect_uris', models.TextField(verbose_name='redirect URIs', validators=[fargo.oauth2.models.validate_https_url])),
(
'redirect_uris',
models.TextField(
verbose_name='redirect URIs', validators=[fargo.oauth2.models.validate_https_url]
),
),
('client_id', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('client_secret', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
(
'client_secret',
models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255),
),
],
options={
'ordering': ('client_name',),
@ -50,11 +70,24 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='OAuth2TempFile',
fields=[
('uuid', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=32, serialize=False, primary_key=True)),
(
'uuid',
models.CharField(
default=fargo.oauth2.models.generate_uuid,
max_length=32,
serialize=False,
primary_key=True,
),
),
('filename', models.CharField(max_length=512)),
('creation_date', models.DateTimeField(auto_now_add=True)),
('client', models.ForeignKey(to='oauth2.OAuth2Client', on_delete=models.CASCADE)),
('document', models.ForeignKey(related_name='oauth2_tempfiles', to='fargo.Document', on_delete=models.CASCADE)),
(
'document',
models.ForeignKey(
related_name='oauth2_tempfiles', to='fargo.Document', on_delete=models.CASCADE
),
),
],
options={
'ordering': ('creation_date',),

View File

@ -28,13 +28,17 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='oauth2authorize',
name='client',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='oauth2.OAuth2Client'),
field=models.ForeignKey(
default=1, on_delete=django.db.models.deletion.CASCADE, to='oauth2.OAuth2Client'
),
preserve_default=False,
),
migrations.AddField(
model_name='oauth2tempfile',
name='client',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='oauth2.OAuth2Client'),
field=models.ForeignKey(
default=1, on_delete=django.db.models.deletion.CASCADE, to='oauth2.OAuth2Client'
),
preserve_default=False,
),
]

View File

@ -25,6 +25,8 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='oauth2tempfile',
name='uuid',
field=models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=32, primary_key=True, serialize=False),
field=models.CharField(
default=fargo.oauth2.models.generate_uuid, max_length=32, primary_key=True, serialize=False
),
),
]

View File

@ -14,14 +14,26 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='oauth2authorize',
options={'ordering': ('creation_date',), 'verbose_name': 'OAUTH2 authorization', 'verbose_name_plural': 'OAUTH2 authorizations'},
options={
'ordering': ('creation_date',),
'verbose_name': 'OAUTH2 authorization',
'verbose_name_plural': 'OAUTH2 authorizations',
},
),
migrations.AlterModelOptions(
name='oauth2client',
options={'ordering': ('client_name',), 'verbose_name': 'OAUTH2 client', 'verbose_name_plural': 'OAUTH2 clients'},
options={
'ordering': ('client_name',),
'verbose_name': 'OAUTH2 client',
'verbose_name_plural': 'OAUTH2 clients',
},
),
migrations.AlterModelOptions(
name='oauth2tempfile',
options={'ordering': ('creation_date',), 'verbose_name': 'OAUTH2 temporary file', 'verbose_name_plural': 'OAUTH2 temporary files'},
options={
'ordering': ('creation_date',),
'verbose_name': 'OAUTH2 temporary file',
'verbose_name_plural': 'OAUTH2 temporary files',
},
),
]

View File

@ -50,9 +50,7 @@ def validate_https_url(data):
@python_2_unicode_compatible
class OAuth2Client(models.Model):
client_name = models.CharField(max_length=255)
redirect_uris = models.TextField(
verbose_name=_('redirect URIs'),
validators=[validate_https_url])
redirect_uris = models.TextField(verbose_name=_('redirect URIs'), validators=[validate_https_url])
client_id = models.CharField(max_length=255, default=generate_uuid)
client_secret = models.CharField(max_length=255, default=generate_uuid)
@ -97,9 +95,7 @@ class OAuth2Authorize(models.Model):
@classmethod
def get_lifetime(cls):
return max(
settings.FARGO_CODE_LIFETIME,
settings.FARGO_ACCESS_TOKEN_LIFETIME)
return max(settings.FARGO_CODE_LIFETIME, settings.FARGO_ACCESS_TOKEN_LIFETIME)
def __repr__(self):
return 'OAuth2Authorize for document %r' % self.user_document

View File

@ -16,8 +16,14 @@
from django.conf.urls import url
from .views import (authorize_get_document, get_document_token, get_document,
authorize_put_document, put_document, download_put_document)
from .views import (
authorize_get_document,
get_document_token,
get_document,
authorize_put_document,
put_document,
download_put_document,
)
urlpatterns = [
url(r'get-document/authorize', authorize_get_document, name='oauth2-authorize'),

View File

@ -22,8 +22,7 @@ from django.utils.translation import ugettext as _
from django.utils.timezone import now
from django.core.files.base import ContentFile
from django.urls import reverse
from django.http import (HttpResponse, HttpResponseBadRequest,
HttpResponseRedirect)
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import FormView, TemplateView, View
from django.contrib.auth.decorators import login_required
@ -99,12 +98,14 @@ class OAuth2AuthorizeView(FormView):
def form_valid(self, form):
document = form.cleaned_data['document']
authorization = OAuth2Authorize.objects.create(client=self.client, user_document=document)
logger.info(u'user %s authorized client "%s" to get document "%s" (%s) with code "%s"',
self.request.user,
self.client,
document,
document.pk,
authorization.code)
logger.info(
u'user %s authorized client "%s" to get document "%s" (%s) with code "%s"',
self.request.user,
self.client,
document,
document.pk,
authorization.code,
)
return self.redirect(code=authorization.code, state=self.state)
def get_context_data(self, **kwargs):
@ -135,28 +136,32 @@ class GetDocumentTokenView(OAUTH2APIViewMixin):
if (now() - authorize.creation_date).total_seconds() > settings.FARGO_CODE_LIFETIME:
return self.error('invalid_grant', 'code is expired')
logger.info(u'client "%s" resolved code "%s" to access token "%s"',
request.user.oauth2_client,
authorize.code,
authorize.access_token)
return Response({
'access_token': authorize.access_token,
'expires': settings.FARGO_ACCESS_TOKEN_LIFETIME
})
logger.info(
u'client "%s" resolved code "%s" to access token "%s"',
request.user.oauth2_client,
authorize.code,
authorize.access_token,
)
return Response(
{'access_token': authorize.access_token, 'expires': settings.FARGO_ACCESS_TOKEN_LIFETIME}
)
get_document_token = GetDocumentTokenView.as_view()
def document_response(user_document):
response = HttpResponse(content=user_document.document.content.chunks(), status=200,
content_type='application/octet-stream')
response = HttpResponse(
content=user_document.document.content.chunks(), status=200, content_type='application/octet-stream'
)
filename = user_document.filename
ascii_filename = filename.encode('ascii', 'replace').decode()
percent_encoded_filename = quote(filename.encode('utf8'), safe='')
response['Content-Disposition'] = 'attachment; filename="%s"; filename*=UTF-8\'\'%s' % (ascii_filename,
percent_encoded_filename)
response['Content-Disposition'] = 'attachment; filename="%s"; filename*=UTF-8\'\'%s' % (
ascii_filename,
percent_encoded_filename,
)
return response
@ -166,16 +171,17 @@ def get_document(request):
return HttpResponseBadRequest('http bearer authentication failed: invalid authorization header')
user_document = oauth_authorize.user_document
logger.info(u'client "%s" retrieved document "%s" (%s) with access token "%s"',
oauth_authorize.client,
user_document,
user_document.pk,
oauth_authorize.access_token)
logger.info(
u'client "%s" retrieved document "%s" (%s) with access token "%s"',
oauth_authorize.client,
user_document,
user_document.pk,
oauth_authorize.access_token,
)
return document_response(user_document)
class PutDocumentAPIView(OAUTH2APIViewMixin):
def post(self, request, *args, **kwargs):
filename, error = get_content_disposition_value(request)
if error:
@ -184,17 +190,18 @@ class PutDocumentAPIView(OAUTH2APIViewMixin):
f = ContentFile(request.body, name=filename)
document = Document.objects.get_by_file(f)
oauth2_document = OAuth2TempFile.objects.create(
client=request.user.oauth2_client,
document=document,
filename=filename)
client=request.user.oauth2_client, document=document, filename=filename
)
uri = reverse('oauth2-put-document-authorize', args=[oauth2_document.pk])
response = Response()
response['Location'] = uri
logger.info(u'client "%s" uploaded document "%s" (%s)',
request.user.oauth2_client,
filename,
oauth2_document.pk)
logger.info(
u'client "%s" uploaded document "%s" (%s)',
request.user.oauth2_client,
filename,
oauth2_document.pk,
)
return response
@ -221,11 +228,13 @@ class OAuth2AuthorizePutView(TemplateView):
kwargs['filename'] = self.oauth2_document.filename
kwargs['thumbnail_image'] = self.oauth2_document.document.thumbnail_image
kwargs['oauth2_client'] = self.oauth2_document.client
kwargs['download_url'] = reverse('oauth2-put-document-download', kwargs={'pk': self.oauth2_document.pk})
kwargs['download_url'] = reverse(
'oauth2-put-document-download', kwargs={'pk': self.oauth2_document.pk}
)
# verify if document already exists
if not UserDocument.objects.filter(
user=self.request.user,
document=self.oauth2_document.document).exists():
user=self.request.user, document=self.oauth2_document.document
).exists():
kwargs['error_message'] = ''
else:
kwargs['error_message'] = _('This document is already in your portfolio')
@ -246,16 +255,20 @@ class OAuth2AuthorizePutView(TemplateView):
UserDocument.objects.create(
user=request.user,
document=self.oauth2_document.document,
filename=self.oauth2_document.filename)
logger.info(u'user %s accepted document "%s" (%s) from client "%s"',
request.user,
self.oauth2_document.filename,
self.oauth2_document.pk,
self.oauth2_document.client)
filename=self.oauth2_document.filename,
)
logger.info(
u'user %s accepted document "%s" (%s) from client "%s"',
request.user,
self.oauth2_document.filename,
self.oauth2_document.pk,
self.oauth2_document.client,
)
return self.redirect()
finally:
self.oauth2_document.delete()
authorize_put_document = login_required(OAuth2AuthorizePutView.as_view())
@ -264,4 +277,5 @@ class DownloadPutDocument(View):
oauth2_document = get_object_or_404(OAuth2TempFile, pk=kwargs['pk'])
return document_response(oauth2_document)
download_put_document = login_required(DownloadPutDocument.as_view())

View File

@ -28,6 +28,7 @@ from django.conf.global_settings import STATICFILES_FINDERS
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
@ -143,6 +144,7 @@ LOGOUT_URL = '/logout/'
# Authentication settings
try:
import mellon
INSTALLED_APPS = INSTALLED_APPS + ('mellon',)
except ImportError:
mellon = None
@ -182,11 +184,7 @@ FARGO_DOCUMENT_TYPES = [
'name': 'avis-d-imposition',
'label': u'Avis d\'imposition',
'metadata': [
{
'label': u'Personne-s concernée-s',
'varname': 'personnes_concernees',
'type': 'string'
},
{'label': u'Personne-s concernée-s', 'varname': 'personnes_concernees', 'type': 'string'},
{
'label': u'Année',
'varname': 'annee',
@ -197,7 +195,7 @@ FARGO_DOCUMENT_TYPES = [
'label': u'Revenu fiscal de référence',
'varname': 'revenu_fiscal_de_reference',
'type': 'string',
'validation': ' *[0-9]+ *'
'validation': ' *[0-9]+ *',
},
],
},
@ -227,9 +225,7 @@ LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'formatters': {
'simple': {
'format': '%(levelname)s %(asctime)s %(name)s: %(message)s'
},
'simple': {'format': '%(levelname)s %(asctime)s %(name)s: %(message)s'},
},
'handlers': {
'console': {
@ -262,10 +258,9 @@ FARGO_CODE_LIFETIME = 300
FARGO_ACCESS_TOKEN_LIFETIME = 3600
FARGO_OAUTH2_TEMPFILE_LIFETIME = 86400
local_settings_file = os.environ.get('FARGO_SETTINGS_FILE',
os.path.join(
os.path.dirname(__file__),
'local_settings.py'))
local_settings_file = os.environ.get(
'FARGO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')
)
if os.path.exists(local_settings_file):
exec(open(local_settings_file).read())

View File

@ -18,9 +18,23 @@ from django.conf import settings
from django.conf.urls import include, url
from django.contrib import admin
from .fargo.views import (home, jsonp, json, download, pick, delete, upload, edit,
remote_download, login, logout, pick_list, document_types, thumbnail)
from .fargo.api_views import (push_document, recent_documents, router)
from .fargo.views import (
home,
jsonp,
json,
download,
pick,
delete,
upload,
edit,
remote_download,
login,
logout,
pick_list,
document_types,
thumbnail,
)
from .fargo.api_views import push_document, recent_documents, router
urlpatterns = [
url(r'^$', home, name='home'),
@ -30,18 +44,14 @@ urlpatterns = [
url(r'^(?P<pk>\d+)/edit/$', edit, name='edit'),
url(r'^(?P<pk>\d+)/delete/$', delete, name='delete'),
url(r'^(?P<pk>\d+)/pick/$', pick, name='pick'),
url(r'^(?P<pk>\d+)/download/(?P<filename>[^/]*)$', download,
name='download'),
url(r'^(?P<pk>\d+)/thumbnail/(?P<filename>[^/]*)$', thumbnail,
name='thumbnail'),
url(r'^(?P<pk>\d+)/download/(?P<filename>[^/]*)$', download, name='download'),
url(r'^(?P<pk>\d+)/thumbnail/(?P<filename>[^/]*)$', thumbnail, name='thumbnail'),
url(r'^upload/$', upload, name='upload'),
url(r'^remote-download/(?P<filename>[^/]*)$', remote_download,
name='remote_download'),
url(r'^remote-download/(?P<filename>[^/]*)$', remote_download, name='remote_download'),
url(r'^admin/', admin.site.urls),
url(r'^login/$', login, name='auth_login'),
url(r'^logout/$', logout, name='auth_logout'),
url(r'^document-types/$', document_types, name='document_types'),
url(r'^api/documents/push/$', push_document, name='fargo-api-push-document'),
url(r'^api/documents/recently-added/$', recent_documents),
url(r'^api/', include(router.urls)),
@ -50,6 +60,7 @@ urlpatterns = [
if settings.DEBUG and 'debug_toolbar' in settings.INSTALLED_APPS:
import debug_toolbar
urlpatterns = [
url(r'^__debug__/', include(debug_toolbar.urls)),
] + urlpatterns

View File

@ -26,28 +26,29 @@ class eo_sdist(sdist):
def get_version():
'''Use the VERSION, if absent generates a version with git describe, if not
tag exists, take 0.0- and add the length of the commit log.
'''
"""Use the VERSION, if absent generates a version with git describe, if not
tag exists, take 0.0- and add the length of the commit log.
"""
if os.path.exists('VERSION'):
with open('VERSION', 'r') as v:
return v.read()
if os.path.exists('.git'):
p = subprocess.Popen(['git','describe','--dirty=.dirty','--match=v*'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(
['git', 'describe', '--dirty=.dirty', '--match=v*'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
result = p.communicate()[0]
if p.returncode == 0:
result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v
if '-' in result: # not a tagged version
result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v
if '-' in result: # not a tagged version
real_number, commit_count, commit_hash = result.split('-', 2)
version = '%s.post%s+%s' % (real_number, commit_count, commit_hash)
else:
version = result
return version
else:
return '0.0.post%s' % len(
subprocess.check_output(
['git', 'rev-list', 'HEAD']).splitlines())
return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines())
return '0.0'
@ -64,6 +65,7 @@ class compile_translations(Command):
def run(self):
try:
from django.core.management import call_command
for path, dirs, files in os.walk('fargo'):
if 'locale' not in dirs:
continue

View File

@ -40,9 +40,9 @@ def app(request):
@pytest.fixture
def concurrency(settings):
'''Select a level of concurrency based on the db, sqlite3 is less robust
thant postgres due to its transaction lock timeout of 5 seconds.
'''
"""Select a level of concurrency based on the db, sqlite3 is less robust
thant postgres due to its transaction lock timeout of 5 seconds.
"""
if 'sqlite' in settings.DATABASES['default']['ENGINE']:
return 20
else:
@ -53,11 +53,13 @@ def concurrency(settings):
def private_settings(request):
import django.conf
from django.conf import UserSettingsHolder
old = django.conf.settings._wrapped
django.conf.settings._wrapped = UserSettingsHolder(old)
def finalizer():
django.conf.settings._wrapped = old
request.addfinalizer(finalizer)
return django.conf.settings
@ -65,6 +67,7 @@ def private_settings(request):
@pytest.fixture
def caplog(caplog):
import py.io
caplog.setLevel(logging.INFO)
caplog.handler.stream = py.io.TextIO()
caplog.handler.records = []
@ -73,11 +76,7 @@ def caplog(caplog):
@pytest.fixture
def john_doe(db):
user = User(
username='john.doe',
first_name='John',
last_name='Doe',
email='john.doe@example.com')
user = User(username='john.doe', first_name='John', last_name='Doe', email='john.doe@example.com')
user.set_password('john.doe')
user.save()
return user
@ -85,11 +84,7 @@ def john_doe(db):
@pytest.fixture
def jane_doe(db):
user = User(
username='jane.doe',
first_name='Jane',
last_name='Doe',
email='jane.doe@example.com')
user = User(username='jane.doe', first_name='Jane', last_name='Doe', email='jane.doe@example.com')
user.set_password('jane.doe')
user.save()
return user

View File

@ -34,16 +34,19 @@ def test_create_validation(settings, app, admin_user, john_doe, jane_doe):
assert models.Validation.objects.count() == 0
response = app.post_json(url, params=data, status=400)
assert response.json['result'] == 0
assert set(response.json['errors'].keys()) == set([field['varname'] for field in
schema['metadata']] + ['creator', 'origin'])
assert set(response.json['errors'].keys()) == set(
[field['varname'] for field in schema['metadata']] + ['creator', 'origin']
)
assert models.Validation.objects.count() == 0
data.update({
'personnes_concernees': 'John and Lisa Doe',
'annee': '2016',
'revenu_fiscal_de_reference': '32455',
'creator': 'FooBar',
'origin': 'wcs.example.com',
})
data.update(
{
'personnes_concernees': 'John and Lisa Doe',
'annee': '2016',
'revenu_fiscal_de_reference': '32455',
'creator': 'FooBar',
'origin': 'wcs.example.com',
}
)
response1 = app.post_json(url, params=data, status=201)
assert set(response1.json.keys()) == set(['result', 'data'])
assert response1.json['result'] == 1
@ -76,13 +79,14 @@ def test_push_document(app, admin_user, john_doe):
assert models.UserDocument.objects.count() == 0
assert models.Document.objects.count() == 0
assert response.json['result'] == 0
assert (set(response.json['errors'].keys())
== set(['origin', 'file_b64_content']))
data.update({
'origin': 'wcs',
'file_b64_content': base64.b64encode(b'coin').decode(),
'file_name': 'monfichier.pdf',
})
assert set(response.json['errors'].keys()) == set(['origin', 'file_b64_content'])
data.update(
{
'origin': 'wcs',
'file_b64_content': base64.b64encode(b'coin').decode(),
'file_name': 'monfichier.pdf',
}
)
response = app.post_json(url, params=data, status=200)
assert response.json['result'] == 1
assert models.Origin.objects.count() == 1
@ -93,10 +97,8 @@ def test_push_document(app, admin_user, john_doe):
assert models.UserDocument.objects.get().deletable_by_user is True
assert models.Document.objects.count() == 1
assert models.Document.objects.get().content.read() == b'coin'
assert (models.UserDocument.objects.get().document
== models.Document.objects.get())
assert (models.UserDocument.objects.get().origin
== models.Origin.objects.get())
assert models.UserDocument.objects.get().document == models.Document.objects.get()
assert models.UserDocument.objects.get().origin == models.Origin.objects.get()
data['file_b64_content'] = base64.b64encode(b'coin2').decode()
data['deletable_by_user'] = False

View File

@ -31,11 +31,7 @@ def test_cleanup(freezer, john_doe):
foo = Document.objects.create(content=ContentFile(b'foo', name='foo.txt'))
bar = Document.objects.create(content=ContentFile(b'bar', name='bar.txt'))
UserDocument.objects.create(user=john_doe,
document=foo,
filename='foo.txt',
title='',
description='')
UserDocument.objects.create(user=john_doe, document=foo, filename='foo.txt', title='', description='')
OAuth2TempFile.objects.create(document=bar, client=client, filename='bar.txt')
call_command('fargo-cleanup')

View File

@ -23,6 +23,7 @@ from fargo.fargo.models import Document
pytestmark = pytest.mark.django_db
def login(app, username='admin', password='admin', user=None):
login_page = app.get('/login/')
login_form = login_page.forms[0]
@ -36,6 +37,7 @@ def login(app, username='admin', password='admin', user=None):
assert resp.status_int == 302
return app
def test_document_delete(app):
f = ContentFile(b'A test file, ez pz.', 'test_file.txt')
doc = Document.objects.get_by_file(f)

View File

@ -34,7 +34,6 @@ pytestmark = pytest.mark.django_db
class FakedResponse(mock.Mock):
def json(self):
return json.loads(self.content)
@ -42,8 +41,11 @@ class FakedResponse(mock.Mock):
@pytest.fixture
def oauth2_client():
return OAuth2Client.objects.create(
client_name='test_oauth2', client_id='client-id', client_secret='client-secret',
redirect_uris='https://example.net/document https://doc.example.net/ https://example.com')
client_name='test_oauth2',
client_id='client-id',
client_secret='client-secret',
redirect_uris='https://example.net/document https://doc.example.net/ https://example.com',
)
def assert_error_redirect(url, error):
@ -53,11 +55,7 @@ def assert_error_redirect(url, error):
def test_get_document_oauth2(app, john_doe, oauth2_client, user_doc):
login(app, user=john_doe)
url = reverse('oauth2-authorize')
params = {
'client_secret': oauth2_client.client_secret,
'response_type': 'code',
'state': 'achipeachope'
}
params = {'client_secret': oauth2_client.client_secret, 'response_type': 'code', 'state': 'achipeachope'}
# test missing redirect_uri
resp = app.get(url, params={}, status=400)
assert resp.text == 'missing redirect_uri parameter'
@ -133,7 +131,8 @@ def test_put_document(app, john_doe, oauth2_client):
filename = 'éléphant.txt'
percent_encode_filename = quote(filename, safe='')
headers = {
'Content-disposition': 'attachment; filename="%s"; filename*=UTF-8\'\'%s' % (filename, percent_encode_filename)
'Content-disposition': 'attachment; filename="%s"; filename*=UTF-8\'\'%s'
% (filename, percent_encode_filename)
}
assert len(OAuth2TempFile.objects.all()) == 0
@ -162,15 +161,16 @@ def test_put_document(app, john_doe, oauth2_client):
assert OAuth2TempFile.objects.count() == 1
assert UserDocument.objects.count() == 1
assert OAuth2TempFile.objects.get().document == UserDocument.objects.get().document
assert UserDocument.objects.filter(user=john_doe, document=doc.document, filename=u'éléphant.txt').exists()
assert UserDocument.objects.filter(
user=john_doe, document=doc.document, filename=u'éléphant.txt'
).exists()
def test_confirm_put_document_file_exception(app, oauth2_client, john_doe, user_doc):
login(app, user=john_doe)
oauth_tmp_file = OAuth2TempFile.objects.create(
client=oauth2_client,
document=user_doc.document,
filename=user_doc.filename)
client=oauth2_client, document=user_doc.document, filename=user_doc.filename
)
url = reverse('oauth2-put-document-authorize', kwargs={'pk': 'fakemofo'})
url += '?%s' % urlencode({'redirect_uri': 'https://example.com'})
@ -192,7 +192,7 @@ def test_idp_authentication(mocked_post, settings, app, oauth2_client, john_doe,
'client_secret': 'fake',
'response_type': 'code',
'state': 'achipeachope',
'redirect': 'https://example.com/'
'redirect': 'https://example.com/',
}
params['redirect_uri'] = 'https://example.com'
resp = app.get(url, params=params)
@ -213,9 +213,7 @@ def test_idp_authentication(mocked_post, settings, app, oauth2_client, john_doe,
resp.json['detail'] == 'Invalid client_id/client_secret.'
# when remote idp fails to authenticate rp
settings.FARGO_IDP_URL = 'https://idp.example.org'
response = {
"result": 0, "errors": ["Invalid username/password."]
}
response = {"result": 0, "errors": ["Invalid username/password."]}
mocked_post.return_value = FakedResponse(content=json.dumps(response))
resp = app.post(url, params=params, status=401)
resp.json['detail'] == 'Invalid client_id/client_secret.'
@ -239,11 +237,9 @@ def test_command_create_client(db):
OAuth2Client.objects.all().delete()
call_command('oauth2-create-client',
'test',
'https://example.com/',
'--client-id=wtf',
'--client-secret=whocares')
call_command(
'oauth2-create-client', 'test', 'https://example.com/', '--client-id=wtf', '--client-secret=whocares'
)
client = OAuth2Client.objects.get()
assert client.client_name == 'test'
assert client.redirect_uris == 'https://example.com/'