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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,14 +15,21 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Document', name='Document',
fields=[ 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_filename', models.CharField(max_length=512, verbose_name='document filename')),
('document_file', models.FileField(upload_to=b'', verbose_name='file')), ('document_file', models.FileField(upload_to=b'', verbose_name='file')),
('creation', models.DateTimeField(auto_now_add=True, verbose_name='creation date')), ('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,), bases=(models.Model,),
), ),
] ]

View File

@ -12,7 +12,25 @@ import jsonfield.fields
class Migration(migrations.Migration): 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 initial = True
@ -24,7 +42,12 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Document', name='Document',
fields=[ 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')), ('content', models.FileField(upload_to=b'uploads/', max_length=300, verbose_name='file')),
('mime_type', models.CharField(max_length=256, blank=True)), ('mime_type', models.CharField(max_length=256, blank=True)),
('creation_date', models.DateTimeField(auto_now_add=True)), ('creation_date', models.DateTimeField(auto_now_add=True)),
@ -38,7 +61,10 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Origin', name='Origin',
fields=[ 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')), ('label', models.CharField(max_length=80, verbose_name='Label')),
('slug', models.SlugField(verbose_name='Slug')), ('slug', models.SlugField(verbose_name='Slug')),
], ],
@ -46,16 +72,40 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='UserDocument', name='UserDocument',
fields=[ 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')), ('filename', models.CharField(max_length=512, verbose_name='filename')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
('deletable_by_user', models.BooleanField(default=True, verbose_name='deletable by user')), ('deletable_by_user', models.BooleanField(default=True, verbose_name='deletable by user')),
('title', models.CharField(max_length=200, verbose_name='title', blank=True)), ('title', models.CharField(max_length=200, verbose_name='title', blank=True)),
('description', models.TextField(verbose_name='description', blank=True)), ('description', models.TextField(verbose_name='description', blank=True)),
('expiration_date', models.DateField(null=True, verbose_name='expiration date', 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)), 'document',
('user', models.ForeignKey(related_name='user_documents', verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), 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={ options={
'ordering': ('-created', 'user'), 'ordering': ('-created', 'user'),
@ -66,16 +116,32 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Validation', name='Validation',
fields=[ 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')), ('document_type', models.CharField(max_length=256, verbose_name='document type')),
('data', jsonfield.fields.JSONField(null=True, verbose_name='data')), ('data', jsonfield.fields.JSONField(null=True, verbose_name='data')),
('start', models.DateField(verbose_name='start date')), ('start', models.DateField(verbose_name='start date')),
('end', models.DateField(verbose_name='end date')), ('end', models.DateField(verbose_name='end date')),
('creator', models.CharField(max_length=256, verbose_name='creator')), ('creator', models.CharField(max_length=256, verbose_name='creator')),
('created', models.DateTimeField(verbose_name='creation date')), ('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 = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='document', 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( migrations.CreateModel(
name='UserDocument', name='UserDocument',
fields=[ 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')), ('filename', models.CharField(max_length=512, verbose_name='filename')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')), ('created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
], ],
@ -42,19 +45,31 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Validation', name='Validation',
fields=[ 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')), ('document_type', models.CharField(max_length=256, verbose_name='document type')),
('data', jsonfield.fields.JSONField(null=True, verbose_name='data')), ('data', jsonfield.fields.JSONField(null=True, verbose_name='data')),
('start', models.DateField(verbose_name='start date')), ('start', models.DateField(verbose_name='start date')),
('end', models.DateField(verbose_name='end date')), ('end', models.DateField(verbose_name='end date')),
('creator', models.CharField(max_length=256, verbose_name='creator')), ('creator', models.CharField(max_length=256, verbose_name='creator')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')), ('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( migrations.AlterModelOptions(
name='document', 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( migrations.RenameField(
model_name='document', model_name='document',
@ -80,18 +95,31 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='document', model_name='document',
name='content_hash', 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, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='userdocument', model_name='userdocument',
name='document', 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( migrations.AddField(
model_name='userdocument', model_name='userdocument',
name='user', 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), migrations.RunPython(noop, clear_document),
] ]

View File

@ -14,18 +14,22 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Origin', name='Origin',
fields=[ 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')), ('label', models.CharField(max_length=80, verbose_name='Label')),
('slug', models.SlugField(verbose_name='Slug')), ('slug', models.SlugField(verbose_name='Slug')),
], ],
options={ options={},
},
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.AddField( migrations.AddField(
model_name='userdocument', model_name='userdocument',
name='origin', 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, preserve_default=True,
), ),
] ]

View File

@ -21,6 +21,8 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='validation', model_name='validation',
name='user', 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( migrations.AlterField(
model_name='validation', model_name='validation',
name='user', 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( migrations.AddField(
model_name='validation', model_name='validation',
name='origin', 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( migrations.AlterField(
model_name='userdocument', model_name='userdocument',
name='user', 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( migrations.AlterField(
model_name='validation', model_name='validation',

View File

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

View File

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

View File

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

View File

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

View File

@ -41,8 +41,7 @@ class Command(BaseCommand):
f = ContentFile(file_object.read(), name=filename) f = ContentFile(file_object.read(), name=filename)
document = Document.objects.get_by_file(f) document = Document.objects.get_by_file(f)
oauth2_document = OAuth2TempFile.objects.create( oauth2_document = OAuth2TempFile.objects.create(
client=client, client=client, document=document, filename=filename
document=document, )
filename=filename)
uri = reverse('oauth2-put-document-authorize', args=[oauth2_document.pk]) uri = reverse('oauth2-put-document-authorize', args=[oauth2_document.pk])
self.stdout.write('https://localhost:8000' + make_url(uri, redirect_uri=redirect_uri)) 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( migrations.CreateModel(
name='OAuth2Authorize', name='OAuth2Authorize',
fields=[ 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)), ('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)), ('code', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('creation_date', models.DateTimeField(auto_now=True)), ('creation_date', models.DateTimeField(auto_now=True)),
@ -25,11 +28,22 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='OAuth2Client', name='OAuth2Client',
fields=[ 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_id', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('client_name', models.CharField(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( migrations.CreateModel(
@ -37,7 +51,12 @@ class Migration(migrations.Migration):
fields=[ fields=[
('hash_key', models.CharField(max_length=128, serialize=False, primary_key=True)), ('hash_key', models.CharField(max_length=128, serialize=False, primary_key=True)),
('filename', models.CharField(max_length=512)), ('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): 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 initial = True
@ -21,7 +27,10 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='OAuth2Authorize', name='OAuth2Authorize',
fields=[ 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)), ('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)), ('code', models.CharField(default=fargo.oauth2.models.generate_uuid, max_length=255)),
('creation_date', models.DateTimeField(auto_now_add=True)), ('creation_date', models.DateTimeField(auto_now_add=True)),
@ -35,11 +44,22 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='OAuth2Client', name='OAuth2Client',
fields=[ 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)), ('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_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={ options={
'ordering': ('client_name',), 'ordering': ('client_name',),
@ -50,11 +70,24 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='OAuth2TempFile', name='OAuth2TempFile',
fields=[ 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)), ('filename', models.CharField(max_length=512)),
('creation_date', models.DateTimeField(auto_now_add=True)), ('creation_date', models.DateTimeField(auto_now_add=True)),
('client', models.ForeignKey(to='oauth2.OAuth2Client', on_delete=models.CASCADE)), ('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={ options={
'ordering': ('creation_date',), 'ordering': ('creation_date',),

View File

@ -28,13 +28,17 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='oauth2authorize', model_name='oauth2authorize',
name='client', 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, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='oauth2tempfile', model_name='oauth2tempfile',
name='client', 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, preserve_default=False,
), ),
] ]

View File

@ -25,6 +25,8 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='oauth2tempfile', model_name='oauth2tempfile',
name='uuid', 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 = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='oauth2authorize', 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( migrations.AlterModelOptions(
name='oauth2client', 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( migrations.AlterModelOptions(
name='oauth2tempfile', 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 @python_2_unicode_compatible
class OAuth2Client(models.Model): class OAuth2Client(models.Model):
client_name = models.CharField(max_length=255) client_name = models.CharField(max_length=255)
redirect_uris = models.TextField( redirect_uris = models.TextField(verbose_name=_('redirect URIs'), validators=[validate_https_url])
verbose_name=_('redirect URIs'),
validators=[validate_https_url])
client_id = models.CharField(max_length=255, default=generate_uuid) client_id = models.CharField(max_length=255, default=generate_uuid)
client_secret = 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 @classmethod
def get_lifetime(cls): def get_lifetime(cls):
return max( return max(settings.FARGO_CODE_LIFETIME, settings.FARGO_ACCESS_TOKEN_LIFETIME)
settings.FARGO_CODE_LIFETIME,
settings.FARGO_ACCESS_TOKEN_LIFETIME)
def __repr__(self): def __repr__(self):
return 'OAuth2Authorize for document %r' % self.user_document return 'OAuth2Authorize for document %r' % self.user_document

View File

@ -16,8 +16,14 @@
from django.conf.urls import url from django.conf.urls import url
from .views import (authorize_get_document, get_document_token, get_document, from .views import (
authorize_put_document, put_document, download_put_document) authorize_get_document,
get_document_token,
get_document,
authorize_put_document,
put_document,
download_put_document,
)
urlpatterns = [ urlpatterns = [
url(r'get-document/authorize', authorize_get_document, name='oauth2-authorize'), 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.utils.timezone import now
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.urls import reverse from django.urls import reverse
from django.http import (HttpResponse, HttpResponseBadRequest, from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
HttpResponseRedirect)
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.generic import FormView, TemplateView, View from django.views.generic import FormView, TemplateView, View
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -99,12 +98,14 @@ class OAuth2AuthorizeView(FormView):
def form_valid(self, form): def form_valid(self, form):
document = form.cleaned_data['document'] document = form.cleaned_data['document']
authorization = OAuth2Authorize.objects.create(client=self.client, user_document=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"', logger.info(
self.request.user, u'user %s authorized client "%s" to get document "%s" (%s) with code "%s"',
self.client, self.request.user,
document, self.client,
document.pk, document,
authorization.code) document.pk,
authorization.code,
)
return self.redirect(code=authorization.code, state=self.state) return self.redirect(code=authorization.code, state=self.state)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -135,28 +136,32 @@ class GetDocumentTokenView(OAUTH2APIViewMixin):
if (now() - authorize.creation_date).total_seconds() > settings.FARGO_CODE_LIFETIME: if (now() - authorize.creation_date).total_seconds() > settings.FARGO_CODE_LIFETIME:
return self.error('invalid_grant', 'code is expired') return self.error('invalid_grant', 'code is expired')
logger.info(u'client "%s" resolved code "%s" to access token "%s"', logger.info(
request.user.oauth2_client, u'client "%s" resolved code "%s" to access token "%s"',
authorize.code, request.user.oauth2_client,
authorize.access_token) authorize.code,
return Response({ authorize.access_token,
'access_token': authorize.access_token, )
'expires': settings.FARGO_ACCESS_TOKEN_LIFETIME return Response(
}) {'access_token': authorize.access_token, 'expires': settings.FARGO_ACCESS_TOKEN_LIFETIME}
)
get_document_token = GetDocumentTokenView.as_view() get_document_token = GetDocumentTokenView.as_view()
def document_response(user_document): def document_response(user_document):
response = HttpResponse(content=user_document.document.content.chunks(), status=200, response = HttpResponse(
content_type='application/octet-stream') content=user_document.document.content.chunks(), status=200, content_type='application/octet-stream'
)
filename = user_document.filename filename = user_document.filename
ascii_filename = filename.encode('ascii', 'replace').decode() ascii_filename = filename.encode('ascii', 'replace').decode()
percent_encoded_filename = quote(filename.encode('utf8'), safe='') percent_encoded_filename = quote(filename.encode('utf8'), safe='')
response['Content-Disposition'] = 'attachment; filename="%s"; filename*=UTF-8\'\'%s' % (ascii_filename, response['Content-Disposition'] = 'attachment; filename="%s"; filename*=UTF-8\'\'%s' % (
percent_encoded_filename) ascii_filename,
percent_encoded_filename,
)
return response return response
@ -166,16 +171,17 @@ def get_document(request):
return HttpResponseBadRequest('http bearer authentication failed: invalid authorization header') return HttpResponseBadRequest('http bearer authentication failed: invalid authorization header')
user_document = oauth_authorize.user_document user_document = oauth_authorize.user_document
logger.info(u'client "%s" retrieved document "%s" (%s) with access token "%s"', logger.info(
oauth_authorize.client, u'client "%s" retrieved document "%s" (%s) with access token "%s"',
user_document, oauth_authorize.client,
user_document.pk, user_document,
oauth_authorize.access_token) user_document.pk,
oauth_authorize.access_token,
)
return document_response(user_document) return document_response(user_document)
class PutDocumentAPIView(OAUTH2APIViewMixin): class PutDocumentAPIView(OAUTH2APIViewMixin):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
filename, error = get_content_disposition_value(request) filename, error = get_content_disposition_value(request)
if error: if error:
@ -184,17 +190,18 @@ class PutDocumentAPIView(OAUTH2APIViewMixin):
f = ContentFile(request.body, name=filename) f = ContentFile(request.body, name=filename)
document = Document.objects.get_by_file(f) document = Document.objects.get_by_file(f)
oauth2_document = OAuth2TempFile.objects.create( oauth2_document = OAuth2TempFile.objects.create(
client=request.user.oauth2_client, client=request.user.oauth2_client, document=document, filename=filename
document=document, )
filename=filename)
uri = reverse('oauth2-put-document-authorize', args=[oauth2_document.pk]) uri = reverse('oauth2-put-document-authorize', args=[oauth2_document.pk])
response = Response() response = Response()
response['Location'] = uri response['Location'] = uri
logger.info(u'client "%s" uploaded document "%s" (%s)', logger.info(
request.user.oauth2_client, u'client "%s" uploaded document "%s" (%s)',
filename, request.user.oauth2_client,
oauth2_document.pk) filename,
oauth2_document.pk,
)
return response return response
@ -221,11 +228,13 @@ class OAuth2AuthorizePutView(TemplateView):
kwargs['filename'] = self.oauth2_document.filename kwargs['filename'] = self.oauth2_document.filename
kwargs['thumbnail_image'] = self.oauth2_document.document.thumbnail_image kwargs['thumbnail_image'] = self.oauth2_document.document.thumbnail_image
kwargs['oauth2_client'] = self.oauth2_document.client 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 # verify if document already exists
if not UserDocument.objects.filter( if not UserDocument.objects.filter(
user=self.request.user, user=self.request.user, document=self.oauth2_document.document
document=self.oauth2_document.document).exists(): ).exists():
kwargs['error_message'] = '' kwargs['error_message'] = ''
else: else:
kwargs['error_message'] = _('This document is already in your portfolio') kwargs['error_message'] = _('This document is already in your portfolio')
@ -246,16 +255,20 @@ class OAuth2AuthorizePutView(TemplateView):
UserDocument.objects.create( UserDocument.objects.create(
user=request.user, user=request.user,
document=self.oauth2_document.document, document=self.oauth2_document.document,
filename=self.oauth2_document.filename) filename=self.oauth2_document.filename,
logger.info(u'user %s accepted document "%s" (%s) from client "%s"', )
request.user, logger.info(
self.oauth2_document.filename, u'user %s accepted document "%s" (%s) from client "%s"',
self.oauth2_document.pk, request.user,
self.oauth2_document.client) self.oauth2_document.filename,
self.oauth2_document.pk,
self.oauth2_document.client,
)
return self.redirect() return self.redirect()
finally: finally:
self.oauth2_document.delete() self.oauth2_document.delete()
authorize_put_document = login_required(OAuth2AuthorizePutView.as_view()) 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']) oauth2_document = get_object_or_404(OAuth2TempFile, pk=kwargs['pk'])
return document_response(oauth2_document) return document_response(oauth2_document)
download_put_document = login_required(DownloadPutDocument.as_view()) 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, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
@ -143,6 +144,7 @@ LOGOUT_URL = '/logout/'
# Authentication settings # Authentication settings
try: try:
import mellon import mellon
INSTALLED_APPS = INSTALLED_APPS + ('mellon',) INSTALLED_APPS = INSTALLED_APPS + ('mellon',)
except ImportError: except ImportError:
mellon = None mellon = None
@ -182,11 +184,7 @@ FARGO_DOCUMENT_TYPES = [
'name': 'avis-d-imposition', 'name': 'avis-d-imposition',
'label': u'Avis d\'imposition', 'label': u'Avis d\'imposition',
'metadata': [ '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', 'label': u'Année',
'varname': 'annee', 'varname': 'annee',
@ -197,7 +195,7 @@ FARGO_DOCUMENT_TYPES = [
'label': u'Revenu fiscal de référence', 'label': u'Revenu fiscal de référence',
'varname': 'revenu_fiscal_de_reference', 'varname': 'revenu_fiscal_de_reference',
'type': 'string', 'type': 'string',
'validation': ' *[0-9]+ *' 'validation': ' *[0-9]+ *',
}, },
], ],
}, },
@ -227,9 +225,7 @@ LOGGING = {
'version': 1, 'version': 1,
'disable_existing_loggers': True, 'disable_existing_loggers': True,
'formatters': { 'formatters': {
'simple': { 'simple': {'format': '%(levelname)s %(asctime)s %(name)s: %(message)s'},
'format': '%(levelname)s %(asctime)s %(name)s: %(message)s'
},
}, },
'handlers': { 'handlers': {
'console': { 'console': {
@ -262,10 +258,9 @@ FARGO_CODE_LIFETIME = 300
FARGO_ACCESS_TOKEN_LIFETIME = 3600 FARGO_ACCESS_TOKEN_LIFETIME = 3600
FARGO_OAUTH2_TEMPFILE_LIFETIME = 86400 FARGO_OAUTH2_TEMPFILE_LIFETIME = 86400
local_settings_file = os.environ.get('FARGO_SETTINGS_FILE', local_settings_file = os.environ.get(
os.path.join( 'FARGO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')
os.path.dirname(__file__), )
'local_settings.py'))
if os.path.exists(local_settings_file): if os.path.exists(local_settings_file):
exec(open(local_settings_file).read()) 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.conf.urls import include, url
from django.contrib import admin from django.contrib import admin
from .fargo.views import (home, jsonp, json, download, pick, delete, upload, edit, from .fargo.views import (
remote_download, login, logout, pick_list, document_types, thumbnail) home,
from .fargo.api_views import (push_document, recent_documents, router) 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 = [ urlpatterns = [
url(r'^$', home, name='home'), url(r'^$', home, name='home'),
@ -30,18 +44,14 @@ urlpatterns = [
url(r'^(?P<pk>\d+)/edit/$', edit, name='edit'), url(r'^(?P<pk>\d+)/edit/$', edit, name='edit'),
url(r'^(?P<pk>\d+)/delete/$', delete, name='delete'), url(r'^(?P<pk>\d+)/delete/$', delete, name='delete'),
url(r'^(?P<pk>\d+)/pick/$', pick, name='pick'), url(r'^(?P<pk>\d+)/pick/$', pick, name='pick'),
url(r'^(?P<pk>\d+)/download/(?P<filename>[^/]*)$', download, url(r'^(?P<pk>\d+)/download/(?P<filename>[^/]*)$', download, name='download'),
name='download'), url(r'^(?P<pk>\d+)/thumbnail/(?P<filename>[^/]*)$', thumbnail, name='thumbnail'),
url(r'^(?P<pk>\d+)/thumbnail/(?P<filename>[^/]*)$', thumbnail,
name='thumbnail'),
url(r'^upload/$', upload, name='upload'), url(r'^upload/$', upload, name='upload'),
url(r'^remote-download/(?P<filename>[^/]*)$', remote_download, url(r'^remote-download/(?P<filename>[^/]*)$', remote_download, name='remote_download'),
name='remote_download'),
url(r'^admin/', admin.site.urls), url(r'^admin/', admin.site.urls),
url(r'^login/$', login, name='auth_login'), url(r'^login/$', login, name='auth_login'),
url(r'^logout/$', logout, name='auth_logout'), url(r'^logout/$', logout, name='auth_logout'),
url(r'^document-types/$', document_types, name='document_types'), 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/push/$', push_document, name='fargo-api-push-document'),
url(r'^api/documents/recently-added/$', recent_documents), url(r'^api/documents/recently-added/$', recent_documents),
url(r'^api/', include(router.urls)), url(r'^api/', include(router.urls)),
@ -50,6 +60,7 @@ urlpatterns = [
if settings.DEBUG and 'debug_toolbar' in settings.INSTALLED_APPS: if settings.DEBUG and 'debug_toolbar' in settings.INSTALLED_APPS:
import debug_toolbar import debug_toolbar
urlpatterns = [ urlpatterns = [
url(r'^__debug__/', include(debug_toolbar.urls)), url(r'^__debug__/', include(debug_toolbar.urls)),
] + urlpatterns ] + urlpatterns

View File

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

View File

@ -40,9 +40,9 @@ def app(request):
@pytest.fixture @pytest.fixture
def concurrency(settings): def concurrency(settings):
'''Select a level of concurrency based on the db, sqlite3 is less robust """Select a level of concurrency based on the db, sqlite3 is less robust
thant postgres due to its transaction lock timeout of 5 seconds. thant postgres due to its transaction lock timeout of 5 seconds.
''' """
if 'sqlite' in settings.DATABASES['default']['ENGINE']: if 'sqlite' in settings.DATABASES['default']['ENGINE']:
return 20 return 20
else: else:
@ -53,11 +53,13 @@ def concurrency(settings):
def private_settings(request): def private_settings(request):
import django.conf import django.conf
from django.conf import UserSettingsHolder from django.conf import UserSettingsHolder
old = django.conf.settings._wrapped old = django.conf.settings._wrapped
django.conf.settings._wrapped = UserSettingsHolder(old) django.conf.settings._wrapped = UserSettingsHolder(old)
def finalizer(): def finalizer():
django.conf.settings._wrapped = old django.conf.settings._wrapped = old
request.addfinalizer(finalizer) request.addfinalizer(finalizer)
return django.conf.settings return django.conf.settings
@ -65,6 +67,7 @@ def private_settings(request):
@pytest.fixture @pytest.fixture
def caplog(caplog): def caplog(caplog):
import py.io import py.io
caplog.setLevel(logging.INFO) caplog.setLevel(logging.INFO)
caplog.handler.stream = py.io.TextIO() caplog.handler.stream = py.io.TextIO()
caplog.handler.records = [] caplog.handler.records = []
@ -73,11 +76,7 @@ def caplog(caplog):
@pytest.fixture @pytest.fixture
def john_doe(db): def john_doe(db):
user = User( user = User(username='john.doe', first_name='John', last_name='Doe', email='john.doe@example.com')
username='john.doe',
first_name='John',
last_name='Doe',
email='john.doe@example.com')
user.set_password('john.doe') user.set_password('john.doe')
user.save() user.save()
return user return user
@ -85,11 +84,7 @@ def john_doe(db):
@pytest.fixture @pytest.fixture
def jane_doe(db): def jane_doe(db):
user = User( user = User(username='jane.doe', first_name='Jane', last_name='Doe', email='jane.doe@example.com')
username='jane.doe',
first_name='Jane',
last_name='Doe',
email='jane.doe@example.com')
user.set_password('jane.doe') user.set_password('jane.doe')
user.save() user.save()
return user 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 assert models.Validation.objects.count() == 0
response = app.post_json(url, params=data, status=400) response = app.post_json(url, params=data, status=400)
assert response.json['result'] == 0 assert response.json['result'] == 0
assert set(response.json['errors'].keys()) == set([field['varname'] for field in assert set(response.json['errors'].keys()) == set(
schema['metadata']] + ['creator', 'origin']) [field['varname'] for field in schema['metadata']] + ['creator', 'origin']
)
assert models.Validation.objects.count() == 0 assert models.Validation.objects.count() == 0
data.update({ data.update(
'personnes_concernees': 'John and Lisa Doe', {
'annee': '2016', 'personnes_concernees': 'John and Lisa Doe',
'revenu_fiscal_de_reference': '32455', 'annee': '2016',
'creator': 'FooBar', 'revenu_fiscal_de_reference': '32455',
'origin': 'wcs.example.com', 'creator': 'FooBar',
}) 'origin': 'wcs.example.com',
}
)
response1 = app.post_json(url, params=data, status=201) response1 = app.post_json(url, params=data, status=201)
assert set(response1.json.keys()) == set(['result', 'data']) assert set(response1.json.keys()) == set(['result', 'data'])
assert response1.json['result'] == 1 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.UserDocument.objects.count() == 0
assert models.Document.objects.count() == 0 assert models.Document.objects.count() == 0
assert response.json['result'] == 0 assert response.json['result'] == 0
assert (set(response.json['errors'].keys()) assert set(response.json['errors'].keys()) == set(['origin', 'file_b64_content'])
== set(['origin', 'file_b64_content'])) data.update(
data.update({ {
'origin': 'wcs', 'origin': 'wcs',
'file_b64_content': base64.b64encode(b'coin').decode(), 'file_b64_content': base64.b64encode(b'coin').decode(),
'file_name': 'monfichier.pdf', 'file_name': 'monfichier.pdf',
}) }
)
response = app.post_json(url, params=data, status=200) response = app.post_json(url, params=data, status=200)
assert response.json['result'] == 1 assert response.json['result'] == 1
assert models.Origin.objects.count() == 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.UserDocument.objects.get().deletable_by_user is True
assert models.Document.objects.count() == 1 assert models.Document.objects.count() == 1
assert models.Document.objects.get().content.read() == b'coin' assert models.Document.objects.get().content.read() == b'coin'
assert (models.UserDocument.objects.get().document assert models.UserDocument.objects.get().document == models.Document.objects.get()
== models.Document.objects.get()) assert models.UserDocument.objects.get().origin == models.Origin.objects.get()
assert (models.UserDocument.objects.get().origin
== models.Origin.objects.get())
data['file_b64_content'] = base64.b64encode(b'coin2').decode() data['file_b64_content'] = base64.b64encode(b'coin2').decode()
data['deletable_by_user'] = False 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')) foo = Document.objects.create(content=ContentFile(b'foo', name='foo.txt'))
bar = Document.objects.create(content=ContentFile(b'bar', name='bar.txt')) bar = Document.objects.create(content=ContentFile(b'bar', name='bar.txt'))
UserDocument.objects.create(user=john_doe, UserDocument.objects.create(user=john_doe, document=foo, filename='foo.txt', title='', description='')
document=foo,
filename='foo.txt',
title='',
description='')
OAuth2TempFile.objects.create(document=bar, client=client, filename='bar.txt') OAuth2TempFile.objects.create(document=bar, client=client, filename='bar.txt')
call_command('fargo-cleanup') call_command('fargo-cleanup')

View File

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

View File

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