From 46337000469b3fa21c42237e5f7b94905b335f1b Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Mon, 1 Aug 2022 19:28:39 +0200 Subject: [PATCH 1/2] add a binary file field (#66533) --- passerelle/base/models.py | 4 ++ passerelle/utils/forms.py | 76 ++++++++++++++++++++++++++++++++++++++ passerelle/utils/models.py | 33 +++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 passerelle/utils/forms.py create mode 100644 passerelle/utils/models.py diff --git a/passerelle/base/models.py b/passerelle/base/models.py index 2d8106d7..aa9c7af6 100644 --- a/passerelle/base/models.py +++ b/passerelle/base/models.py @@ -379,6 +379,8 @@ class BaseResource(models.Model): d[field.name] = None elif isinstance(field, SFTPField): d[field.name] = value and value.__json__() + elif isinstance(field, models.BinaryField): + d[field.name] = base64.b64encode(bytes(value)).decode() if value is not None else value else: raise Exception( 'export_json: field %s of ressource class %s is unsupported' % (field, self.__class__) @@ -457,6 +459,8 @@ class BaseResource(models.Model): if value: value = SFTP(**value) setattr(instance, field.attname, value) + elif isinstance(field, models.BinaryField): + setattr(instance, field.attname, base64.b64decode(value) if value is not None else value) else: raise Exception( 'import_json_real: field %s of ressource class ' '%s is unsupported' % (field, cls) diff --git a/passerelle/utils/forms.py b/passerelle/utils/forms.py new file mode 100644 index 00000000..d7b9e7fd --- /dev/null +++ b/passerelle/utils/forms.py @@ -0,0 +1,76 @@ +# passerelle - uniform access to multiple data sources and services +# Copyright (C) 2022 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django import forms + + +class BinaryFileInput(forms.ClearableFileInput): + def is_initial(self, value): + """ + Return whether value is considered to be initial value. + """ + return bool(value) + + def format_value(self, value): + """Format the size of the value in the db. + + We can't render it's name or url, but we'd like to give some information + as to wether this file is not empty/corrupt. + """ + if self.is_initial(value): + return f'{len(value)} bytes' + + def value_from_datadict(self, data, files, name): + """Return the file contents so they can be put in the db.""" + upload = super().value_from_datadict(data, files, name) + if upload: + return upload.read() + return upload + + +class BinaryFileField(forms.FileField): + widget = BinaryFileInput + + def __init__(self, *, max_length=None, **kwargs): + self.max_length = max_length + super().__init__(**kwargs) + + def to_python(self, data): + return data + + def clean(self, data, initial=None): + # False means the field value should be cleared; further validation is + # not needed. + if data is False: + if not self.required: + return None + # If the field is required, clearing is not possible (the widget + # shouldn't return False data in that case anyway). False is not + # in self.empty_value; if a False value makes it this far + # it should be validated from here on out as None (so it will be + # caught by the required check). + data = None + if not data and initial: + return initial + return super(forms.FileField, self).clean(data) + + def bound_data(self, data, initial): + if data is None: + return initial + return data + + def has_changed(self, initial, data): + return not self.disabled and data is not None diff --git a/passerelle/utils/models.py b/passerelle/utils/models.py new file mode 100644 index 00000000..ab6500e0 --- /dev/null +++ b/passerelle/utils/models.py @@ -0,0 +1,33 @@ +# passerelle - uniform access to multiple data sources and services +# Copyright (C) 2022 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.db import models + + +class BinaryFileField(models.BinaryField): + def __init__(self, *args, **kwargs): + kwargs.setdefault('editable', True) + super().__init__(*args, **kwargs) + + def formfield(self, **kwargs): + from .forms import BinaryFileField + + return super().formfield( + **{ + 'form_class': BinaryFileField, + **kwargs, + } + ) -- 2.36.1