diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2021-03-19 11:05:22 +0100 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2021-03-19 11:05:22 +0100 |
commit | e2d6c50f231f636fed362be37e7bf3319fc5d6b8 (patch) | |
tree | 5d7fde3628825aebeeef3d85d2dfcf09a52116de /ishtar_common | |
parent | e6af0225df8f539308bc3fd8c9dbc967bba5a807 (diff) | |
download | Ishtar-e2d6c50f231f636fed362be37e7bf3319fc5d6b8.tar.bz2 Ishtar-e2d6c50f231f636fed362be37e7bf3319fc5d6b8.zip |
Format - black: ishtar_common
Diffstat (limited to 'ishtar_common')
29 files changed, 11025 insertions, 8208 deletions
diff --git a/ishtar_common/__init__.py b/ishtar_common/__init__.py index 3d19ae8ff..9df6f44a8 100644 --- a/ishtar_common/__init__.py +++ b/ishtar_common/__init__.py @@ -9,4 +9,4 @@ _("username") _("email address") _("Related item") -default_app_config = 'ishtar_common.apps.IshtarCommonConfig' +default_app_config = "ishtar_common.apps.IshtarCommonConfig" diff --git a/ishtar_common/admin.py b/ishtar_common/admin.py index 4cae9e02c..bbd61f14b 100644 --- a/ishtar_common/admin.py +++ b/ishtar_common/admin.py @@ -30,8 +30,7 @@ from rest_framework.authtoken.admin import TokenAdmin from rest_framework.authtoken.models import Token from ajax_select import make_ajax_form -from ajax_select.fields import AutoCompleteSelectField, \ - AutoCompleteSelectMultipleField +from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField from django.conf import settings from django.conf.urls import url @@ -49,8 +48,13 @@ from django.contrib.gis.geos.error import GEOSException from django.core.cache import cache from django.core.serializers import serialize from django.core.urlresolvers import reverse -from django.db.models.fields import BooleanField, IntegerField, FloatField, \ - CharField, FieldDoesNotExist +from django.db.models.fields import ( + BooleanField, + IntegerField, + FloatField, + CharField, + FieldDoesNotExist, +) from django.db.models.fields.related import ForeignKey from django.forms import BaseInlineFormSet from django.http import HttpResponseRedirect, HttpResponse @@ -69,14 +73,15 @@ from ishtar_common.utils import get_cache, create_slug from ishtar_common import forms as common_forms from ishtar_common.serializers import restore_serialized, IMPORT_MODEL_LIST -from ishtar_common.serializers_utils import generic_get_results, \ - serialization_info +from ishtar_common.serializers_utils import generic_get_results, serialization_info from archaeological_files import forms as file_forms from archaeological_files_pdl import forms as file_pdl_forms from archaeological_operations import forms as operation_forms from archaeological_context_records import forms as context_record_forms -from archaeological_finds import forms as find_forms, \ - forms_treatments as treatment_forms +from archaeological_finds import ( + forms as find_forms, + forms_treatments as treatment_forms, +) from archaeological_warehouse import forms as warehouse_forms from ishtar_common.tasks import launch_export, launch_import @@ -85,16 +90,22 @@ from ishtar_common.tasks import launch_export, launch_import csrf_protect_m = method_decorator(csrf_protect) -ISHTAR_FORMS = [common_forms, file_pdl_forms, file_forms, operation_forms, - context_record_forms, find_forms, treatment_forms, - warehouse_forms] +ISHTAR_FORMS = [ + common_forms, + file_pdl_forms, + file_forms, + operation_forms, + context_record_forms, + find_forms, + treatment_forms, + warehouse_forms, +] class ImportGenericForm(forms.Form): csv_file = forms.FileField( _("CSV file"), - help_text=_("Only unicode encoding is managed - convert your" - " file first") + help_text=_("Only unicode encoding is managed - convert your" " file first"), ) @@ -102,28 +113,35 @@ def change_value(attribute, value, description): """ Action to change a specific value in a list """ + def _change_value(modeladmin, request, queryset): - for obj in queryset.order_by('pk'): + for obj in queryset.order_by("pk"): setattr(obj, attribute, value) obj.save() - url = reverse( - 'admin:%s_%s_changelist' % ( - modeladmin.model._meta.app_label, - modeladmin.model._meta.model_name) - ) + '?' + urllib.parse.urlencode(request.GET) + url = ( + reverse( + "admin:%s_%s_changelist" + % (modeladmin.model._meta.app_label, modeladmin.model._meta.model_name) + ) + + "?" + + urllib.parse.urlencode(request.GET) + ) return HttpResponseRedirect(url) + _change_value.short_description = description _change_value.__name__ = str(slugify(description)) return _change_value -def export_as_csv_action(description=_("Export selected as CSV file"), - fields=None, exclude=None, header=True): +def export_as_csv_action( + description=_("Export selected as CSV file"), fields=None, exclude=None, header=True +): """ This function returns an export csv action 'fields' and 'exclude' work like in django ModelForm 'header' is whether or not to output the column names as the first row """ + def export_as_csv(modeladmin, request, queryset): """ Generic csv export admin action. @@ -138,21 +156,22 @@ def export_as_csv_action(description=_("Export selected as CSV file"), excludeset = set(exclude) field_names = field_names - excludeset - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename=%s.csv' % \ - str(opts).replace('.', '_') + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = "attachment; filename=%s.csv" % str( + opts + ).replace(".", "_") writer = csv.writer(response) if header: writer.writerow(list(field_names)) - for obj in queryset.order_by('pk'): + for obj in queryset.order_by("pk"): row = [] for field in field_names: value = getattr(obj, field) - if hasattr(value, 'txt_idx'): - value = getattr(value, 'txt_idx') - elif hasattr(value, 'slug'): - value = getattr(value, 'txt_idx') + if hasattr(value, "txt_idx"): + value = getattr(value, "txt_idx") + elif hasattr(value, "slug"): + value = getattr(value, "txt_idx") elif value is None: value = "" else: @@ -161,24 +180,30 @@ def export_as_csv_action(description=_("Export selected as CSV file"), writer.writerow(row) return response + export_as_csv.short_description = description return export_as_csv def export_as_geojson_action( - geometry_field, description=_("Export selected as GeoJSON file"), - fields=None, exclude=None): + geometry_field, + description=_("Export selected as GeoJSON file"), + fields=None, + exclude=None, +): """ This function returns an export GeoJSON action 'fields' work like in django ModelForm """ + def export_as_geojson(modeladmin, request, queryset): """ Generic zipped geojson export admin action. """ opts = modeladmin.model._meta - field_names = set([field.name for field in opts.fields if - field.name != geometry_field]) + field_names = set( + [field.name for field in opts.fields if field.name != geometry_field] + ) if fields: fieldset = set(fields) field_names = field_names & fieldset @@ -186,11 +211,14 @@ def export_as_geojson_action( excludeset = set(exclude) field_names = field_names - excludeset - basename = str(opts).replace('.', '_') + basename = str(opts).replace(".", "_") geojson = serialize( - 'geojson', queryset.order_by('pk'), geometry_field=geometry_field, - fields=field_names).encode("utf-8") + "geojson", + queryset.order_by("pk"), + geometry_field=geometry_field, + fields=field_names, + ).encode("utf-8") in_memory = BytesIO() zip = zipfile.ZipFile(in_memory, "a") zip.writestr(basename + ".geojson", geojson) @@ -199,13 +227,12 @@ def export_as_geojson_action( for file in zip.filelist: file.create_system = 0 zip.close() - response = HttpResponse(content_type='application/zip') - response['Content-Disposition'] = 'attachment; filename={}.zip'.format( - basename - ) + response = HttpResponse(content_type="application/zip") + response["Content-Disposition"] = "attachment; filename={}.zip".format(basename) in_memory.seek(0) response.write(in_memory.read()) return response + export_as_geojson.short_description = description return export_as_geojson @@ -218,9 +245,9 @@ def serialize_action(dir_name, model_list): modellist = [modeladmin.model] opts = modeladmin.model._meta result = generic_get_results( - modellist, dir_name, - result_queryset={opts.object_name: queryset}) - basename = str(opts).replace('.', '_') + modellist, dir_name, result_queryset={opts.object_name: queryset} + ) + basename = str(opts).replace(".", "_") in_memory = BytesIO() zip = zipfile.ZipFile(in_memory, "a") for key in result.keys(): @@ -234,13 +261,12 @@ def serialize_action(dir_name, model_list): for file in zip.filelist: file.create_system = 0 zip.close() - response = HttpResponse(content_type='application/zip') - response['Content-Disposition'] = 'attachment; filename={}.zip'.format( - basename - ) + response = HttpResponse(content_type="application/zip") + response["Content-Disposition"] = "attachment; filename={}.zip".format(basename) in_memory.seek(0) response.write(in_memory.read()) return response + return _serialize_action @@ -263,24 +289,27 @@ class MergeForm(forms.Form): class MergeActionAdmin: def get_actions(self, request): action_dct = super(MergeActionAdmin, self).get_actions(request) - action_dct["merge_selected"] = (self.admin_merge, "merge_selected", - _("Merge selected items")) + action_dct["merge_selected"] = ( + self.admin_merge, + "merge_selected", + _("Merge selected items"), + ) return action_dct def admin_merge(self, modeladmin, request, queryset): return_url = reverse( - 'admin:%s_%s_changelist' % ( - self.model._meta.app_label, - self.model._meta.model_name) + "admin:%s_%s_changelist" + % (self.model._meta.app_label, self.model._meta.model_name) ) if not request.POST: return HttpResponseRedirect(return_url) - selected = request.POST.getlist('_selected_action', []) + selected = request.POST.getlist("_selected_action", []) if len(selected) < 2: messages.add_message( - request, messages.WARNING, - str(_("At least two items have to be selected.")) + request, + messages.WARNING, + str(_("At least two items have to be selected.")), ) return HttpResponseRedirect(return_url) @@ -292,10 +321,10 @@ class MergeActionAdmin: items[str(obj.pk)] = obj form = None - if 'apply' in request.POST: + if "apply" in request.POST: form = MergeForm(choices, request.POST, request.FILES) if form.is_valid(): - merge_in = items[form.cleaned_data['merge_in']] + merge_in = items[form.cleaned_data["merge_in"]] merged = [] for key, value in items.items(): if key == str(merge_in.pk): @@ -303,30 +332,40 @@ class MergeActionAdmin: merge_model_objects(merge_in, value) merged.append(str(value)) messages.add_message( - request, messages.INFO, - str(_("{} merged into {}.")).format(' ; '.join(merged), - str(merge_in)) + request, + messages.INFO, + str(_("{} merged into {}.")).format( + " ; ".join(merged), str(merge_in) + ), ) return HttpResponseRedirect(return_url) if not form: form = MergeForm(choices) return render( - request, 'admin/merge.html', - {'merge_form': form, 'current_action': 'merge_selected', - 'selected_items': selected} + request, + "admin/merge.html", + { + "merge_form": form, + "current_action": "merge_selected", + "selected_items": selected, + }, ) -TokenAdmin.raw_id_fields = ('user',) +TokenAdmin.raw_id_fields = ("user",) admin_site.register(Token, TokenAdmin) class HistorizedObjectAdmin(admin.ModelAdmin): - readonly_fields = ['history_creator', 'history_modifier', 'search_vector', - 'history_m2m'] + readonly_fields = [ + "history_creator", + "history_modifier", + "search_vector", + "history_m2m", + ] AJAX_FORM_DICT = { - 'lock_user': 'user', + "lock_user": "user", } def save_model(self, request, obj, form, change): @@ -335,20 +374,18 @@ class HistorizedObjectAdmin(admin.ModelAdmin): def get_readonly_fields(self, request, obj=None): if obj: # editing an existing object - return tuple(self.readonly_fields or []) + tuple(['imports']) + return tuple(self.readonly_fields or []) + tuple(["imports"]) return self.readonly_fields def get_exclude(self, request, obj=None): if not obj: - return tuple(self.exclude or []) + tuple(['imports']) + return tuple(self.exclude or []) + tuple(["imports"]) return self.exclude class MyGroupAdmin(GroupAdmin): class Media: - css = { - "all": ("media/admin.css",) - } + css = {"all": ("media/admin.css",)} admin_site.register(User, UserAdmin) @@ -360,13 +397,22 @@ class AdminIshtarSiteProfileForm(forms.ModelForm): class Meta: model = models.IshtarSiteProfile exclude = [] - default_center = PointField(label=_("Maps - default center"), - widget=OSMWidget) + + default_center = PointField(label=_("Maps - default center"), widget=OSMWidget) class IshtarSiteProfileAdmin(admin.ModelAdmin): - list_display = ('label', 'slug', 'active', 'files', 'context_record', - 'find', 'warehouse', 'mapping', 'preservation') + list_display = ( + "label", + "slug", + "active", + "files", + "context_record", + "find", + "warehouse", + "mapping", + "preservation", + ) model = models.IshtarSiteProfile form = AdminIshtarSiteProfileForm @@ -375,7 +421,10 @@ admin_site.register(models.IshtarSiteProfile, IshtarSiteProfileAdmin) class DepartmentAdmin(admin.ModelAdmin): - list_display = ('number', 'label',) + list_display = ( + "number", + "label", + ) model = models_common.Department @@ -383,14 +432,16 @@ admin_site.register(models_common.Department, DepartmentAdmin) class OrganizationAdmin(HistorizedObjectAdmin): - list_display = ('pk', 'name', 'organization_type') + list_display = ("pk", "name", "organization_type") list_filter = ("organization_type",) - search_fields = ('name',) - exclude = ('merge_key', 'merge_exclusion', 'merge_candidate', ) - model = models.Organization - form = make_ajax_form( - model, {'precise_town': 'town'} + search_fields = ("name",) + exclude = ( + "merge_key", + "merge_exclusion", + "merge_candidate", ) + model = models.Organization + form = make_ajax_form(model, {"precise_town": "town"}) admin_site.register(models.Organization, OrganizationAdmin) @@ -404,11 +455,15 @@ class ProfileInline(admin.TabularInline): class PersonAdmin(HistorizedObjectAdmin): - list_display = ('pk', 'name', 'surname', 'raw_name', 'email') + list_display = ("pk", "name", "surname", "raw_name", "email") list_filter = ("person_types",) - search_fields = ('name', 'surname', 'email', 'raw_name') - exclude = ('merge_key', 'merge_exclusion', 'merge_candidate', ) - form = make_ajax_form(models.Person, {'attached_to': 'organization'}) + search_fields = ("name", "surname", "email", "raw_name") + exclude = ( + "merge_key", + "merge_exclusion", + "merge_candidate", + ) + form = make_ajax_form(models.Person, {"attached_to": "organization"}) model = models.Person inlines = [ProfileInline] @@ -417,19 +472,18 @@ admin_site.register(models.Person, PersonAdmin) class AuthorAdmin(admin.ModelAdmin): - list_display = ['person', 'author_type'] + list_display = ["person", "author_type"] list_filter = ("author_type",) - search_fields = ('person__name', 'person__surname', - 'person__attached_to__name') + search_fields = ("person__name", "person__surname", "person__attached_to__name") model = models.Author - form = make_ajax_form(models.Author, {'person': 'person'}) + form = make_ajax_form(models.Author, {"person": "person"}) admin_site.register(models.Author, AuthorAdmin) class GlobalVarAdmin(admin.ModelAdmin): - list_display = ['slug', 'description', 'value'] + list_display = ["slug", "description", "value"] admin_site.register(models.GlobalVar, GlobalVarAdmin) @@ -441,12 +495,10 @@ class ChangeListForChangeView(ChangeList): Get the current list queryset parameters from _changelist_filters """ filtered_params = {} - lookup_params = super( - ChangeListForChangeView, self).get_filters_params(params) - if '_changelist_filters' in lookup_params: - field_names = [field.name for field in - self.model._meta.get_fields()] - params = lookup_params.pop('_changelist_filters') + lookup_params = super(ChangeListForChangeView, self).get_filters_params(params) + if "_changelist_filters" in lookup_params: + field_names = [field.name for field in self.model._meta.get_fields()] + params = lookup_params.pop("_changelist_filters") for param in params.split("&"): key, value = param.split("=") if not value or key not in field_names: @@ -457,24 +509,25 @@ class ChangeListForChangeView(ChangeList): class ImportActionAdmin(admin.ModelAdmin): change_list_template = "admin/gen_change_list.html" - import_keys = ['slug', 'txt_idx'] + import_keys = ["slug", "txt_idx"] def get_urls(self): urls = super(ImportActionAdmin, self).get_urls() my_urls = [ - url(r'^import-from-csv/$', self.import_generic), + url(r"^import-from-csv/$", self.import_generic), ] return my_urls + urls def import_generic(self, request): form = None - if 'apply' in request.POST: + if "apply" in request.POST: form = ImportGenericForm(request.POST, request.FILES) if form.is_valid(): - encoding = request.encoding or 'utf-8' - csv_file = TextIOWrapper(request.FILES['csv_file'].file, - encoding=encoding) + encoding = request.encoding or "utf-8" + csv_file = TextIOWrapper( + request.FILES["csv_file"].file, encoding=encoding + ) reader = csv.DictReader(csv_file) created, updated, missing_parent = 0, 0, [] for row in reader: @@ -486,19 +539,20 @@ class ImportActionAdmin(admin.ModelAdmin): if not slug_col: self.message_user( request, - str(_("The CSV file should at least have a " - "{} column")).format( - "/".join(self.import_keys))) + str( + _("The CSV file should at least have a " "{} column") + ).format("/".join(self.import_keys)), + ) return slug = row.pop(slug_col) - if 'id' in row: - row.pop('id') - if 'pk' in row: - row.pop('pk') + if "id" in row: + row.pop("id") + if "pk" in row: + row.pop("pk") for k in list(row.keys()): value = row[k] - if value == 'None': - value = '' + if value == "None": + value = "" try: field = self.model._meta.get_field(k) except FieldDoesNotExist: @@ -512,9 +566,9 @@ class ImportActionAdmin(admin.ModelAdmin): elif isinstance(field, FloatField): value = None if not value else float(value) elif isinstance(field, BooleanField): - if value in ('true', 'True', '1'): + if value in ("true", "True", "1"): value = True - elif value in ('false', 'False', '0'): + elif value in ("false", "False", "0"): value = False else: value = None @@ -522,80 +576,79 @@ class ImportActionAdmin(admin.ModelAdmin): if value: model = field.rel.to try: - value = model.objects.get( - **{slug_col: value} - ) + value = model.objects.get(**{slug_col: value}) except model.DoesNotExist: missing_parent.append(row.pop(k)) continue else: value = None row[k] = value - values = { - slug_col: slug, - 'defaults': row - } - obj, c = self.model.objects.get_or_create( - **values) + values = {slug_col: slug, "defaults": row} + obj, c = self.model.objects.get_or_create(**values) if c: created += 1 else: updated += 1 self.model.objects.filter(pk=obj.pk).update(**row) if created: - self.message_user( - request, - str(_("%d item(s) created.")) % created) + self.message_user(request, str(_("%d item(s) created.")) % created) if updated: - self.message_user( - request, - str(_("%d item(s) updated.")) % updated) + self.message_user(request, str(_("%d item(s) updated.")) % updated) if missing_parent: self.message_user( request, str(_("These parents are missing: {}")).format( - " ; ".join(missing_parent))) + " ; ".join(missing_parent) + ), + ) url = reverse( - 'admin:%s_%s_changelist' % ( - self.model._meta.app_label, self.model._meta.model_name) + "admin:%s_%s_changelist" + % (self.model._meta.app_label, self.model._meta.model_name) ) return HttpResponseRedirect(url) if not form: form = ImportGenericForm() return render( - request, 'admin/import_from_file.html', - {'file_form': form, 'current_action': 'import_generic'} + request, + "admin/import_from_file.html", + {"file_form": form, "current_action": "import_generic"}, ) class ImportGeoJsonForm(forms.Form): json_file = forms.FileField( _("Geojson file"), - help_text=_("Only unicode encoding is managed - convert your" - " file first. The file must be a geojson file or a zip " - "containing a geojson file.") + help_text=_( + "Only unicode encoding is managed - convert your" + " file first. The file must be a geojson file or a zip " + "containing a geojson file." + ), ) numero_insee_prefix = forms.CharField( - label=_("Prefix for numero INSEE"), max_length=20, required=False) + label=_("Prefix for numero INSEE"), max_length=20, required=False + ) numero_insee_name = forms.CharField( - label=_("Field name for numero INSEE"), max_length=200, - initial='numero_insee') + label=_("Field name for numero INSEE"), max_length=200, initial="numero_insee" + ) name_name = forms.CharField( - label=_("Field name for name"), max_length=200, initial='name') - UNIT_CHOICES = (('1', _("m2")), ('1000', _("km2"))) - surface_unit = forms.ChoiceField( - label=_("Surface unit"), choices=UNIT_CHOICES) + label=_("Field name for name"), max_length=200, initial="name" + ) + UNIT_CHOICES = (("1", _("m2")), ("1000", _("km2"))) + surface_unit = forms.ChoiceField(label=_("Surface unit"), choices=UNIT_CHOICES) surface_name = forms.CharField( - label=_("Field name for surface"), max_length=200, required=False) + label=_("Field name for surface"), max_length=200, required=False + ) year_name = forms.CharField( - label=_("Field name for year"), max_length=200, required=False, + label=_("Field name for year"), + max_length=200, + required=False, initial="year", - help_text=_("Not required for new town. Leave it empty when not " - "available.") + help_text=_("Not required for new town. Leave it empty when not " "available."), ) update = forms.BooleanField( - label=_("Update only geometry of existing towns"), required=False, - widget=forms.CheckboxInput + label=_("Update only geometry of existing towns"), + required=False, + widget=forms.CheckboxInput, ) @@ -603,7 +656,7 @@ class ImportGEOJSONActionAdmin(object): def get_urls(self): urls = super(ImportGEOJSONActionAdmin, self).get_urls() my_urls = [ - url(r'^import-from-geojson/$', self.import_geojson), + url(r"^import-from-geojson/$", self.import_geojson), ] return my_urls + urls @@ -612,66 +665,57 @@ class ImportGEOJSONActionAdmin(object): if key in feature: continue if trace_error: - error = str( - _("\"{}\" not found in feature {}") - ).format(key, idx) + error = str(_('"{}" not found in feature {}')).format(key, idx) self.message_user(request, error, level=messages.ERROR) return False values = {} for key in keys: - if not key.endswith('_name'): + if not key.endswith("_name"): continue if not keys[key]: - values[key[:-len("_name")]] = None + values[key[: -len("_name")]] = None continue - if keys[key] not in feature['properties']: + if keys[key] not in feature["properties"]: if trace_error: - error = str( - _("\"{}\" not found in properties of feature {}") - ).format(keys[key], idx) - self.message_user(request, error, - level=messages.ERROR) + error = str(_('"{}" not found in properties of feature {}')).format( + keys[key], idx + ) + self.message_user(request, error, level=messages.ERROR) return False - value = feature['properties'][keys[key]] - if key == 'numero_insee_name' and keys['insee_prefix']: - value = keys['insee_prefix'] + value - values[key[:-len("_name")]] = value + value = feature["properties"][keys[key]] + if key == "numero_insee_name" and keys["insee_prefix"]: + value = keys["insee_prefix"] + value + values[key[: -len("_name")]] = value try: - geom = GEOSGeometry(json.dumps(feature['geometry'])) + geom = GEOSGeometry(json.dumps(feature["geometry"])) except (GEOSException, GDALException): if trace_error: - error = str( - _("Bad geometry for feature {}") - ).format(idx) - self.message_user(request, error, - level=messages.ERROR) + error = str(_("Bad geometry for feature {}")).format(idx) + self.message_user(request, error, level=messages.ERROR) return False - if geom.geom_type == 'Point': - values['center'] = geom - elif geom.geom_type == 'MultiPolygon': - values['limit'] = geom - elif geom.geom_type == 'Polygon': - values['limit'] = MultiPolygon(geom) + if geom.geom_type == "Point": + values["center"] = geom + elif geom.geom_type == "MultiPolygon": + values["limit"] = geom + elif geom.geom_type == "Polygon": + values["limit"] = MultiPolygon(geom) else: if trace_error: - error = str( - _("Geometry {} not managed for towns - feature {}") - ).format(geom.geom_type, idx) - self.message_user(request, error, - level=messages.ERROR) + error = str(_("Geometry {} not managed for towns - feature {}")).format( + geom.geom_type, idx + ) + self.message_user(request, error, level=messages.ERROR) return False - if keys['surface_unit'] and values['surface']: + if keys["surface_unit"] and values["surface"]: try: - values['surface'] = keys['surface_unit'] * int( - values['surface']) + values["surface"] = keys["surface_unit"] * int(values["surface"]) except ValueError: if trace_error: - error = str( - _("Bad value for surface: {} - feature {}") - ).format(values['surface'], idx) - self.message_user(request, error, - level=messages.ERROR) + error = str(_("Bad value for surface: {} - feature {}")).format( + values["surface"], idx + ) + self.message_user(request, error, level=messages.ERROR) return False return values @@ -684,24 +728,21 @@ class ImportGEOJSONActionAdmin(object): def import_geojson_error(self, request, error, base_dct, tempdir=None): self.import_geojson_clean(tempdir) self.message_user(request, error, level=messages.ERROR) - return render( - request, 'admin/import_from_file.html', - base_dct) + return render(request, "admin/import_from_file.html", base_dct) def import_geojson(self, request): form = None - if 'apply' in request.POST: + if "apply" in request.POST: form = ImportGeoJsonForm(request.POST, request.FILES) if form.is_valid(): - json_file_obj = request.FILES['json_file'] + json_file_obj = request.FILES["json_file"] - base_dct = {'file_form': form, - 'current_action': 'import_geojson'} + base_dct = {"file_form": form, "current_action": "import_geojson"} tempdir = tempfile.mkdtemp() tmpfilename = tempdir + os.sep + "dest_file" - with open(tmpfilename, 'wb+') as tmpfile: + with open(tmpfilename, "wb+") as tmpfile: for chunk in json_file_obj.chunks(): tmpfile.write(chunk) json_filename = None @@ -716,40 +757,42 @@ class ImportGEOJSONActionAdmin(object): break if not json_filename: error = _("No json file found in zipfile") - return self.import_geojson_error(request, error, - base_dct, tempdir) + return self.import_geojson_error( + request, error, base_dct, tempdir + ) else: json_filename = tmpfilename keys = { - "numero_insee_name": request.POST.get('numero_insee_name'), - "name_name": request.POST.get('name_name'), - "surface_name": request.POST.get('surface_name', "") or "", - "year_name": request.POST.get('year_name', "") or "", - "update": request.POST.get('update', "") or "", - "insee_prefix": request.POST.get('numero_insee_prefix', - None) or '', - "surface_unit": int(request.POST.get('surface_unit')) + "numero_insee_name": request.POST.get("numero_insee_name"), + "name_name": request.POST.get("name_name"), + "surface_name": request.POST.get("surface_name", "") or "", + "year_name": request.POST.get("year_name", "") or "", + "update": request.POST.get("update", "") or "", + "insee_prefix": request.POST.get("numero_insee_prefix", None) or "", + "surface_unit": int(request.POST.get("surface_unit")), } with open(json_filename) as json_file_obj: json_file = json_file_obj.read() try: dct = json.loads(json_file) - assert 'features' in dct - assert dct['features'] + assert "features" in dct + assert dct["features"] except (ValueError, AssertionError): error = _("Bad geojson file") return self.import_geojson_error( - request, error, base_dct, tempdir) + request, error, base_dct, tempdir + ) error_count = 0 created = 0 updated = 0 - for idx, feat in enumerate(dct['features']): + for idx, feat in enumerate(dct["features"]): trace_error = True if error_count == 6: - self.message_user(request, _("Too many errors..."), - level=messages.ERROR) + self.message_user( + request, _("Too many errors..."), level=messages.ERROR + ) if error_count > 5: trace_error = False values = self.geojson_values( @@ -758,18 +801,17 @@ class ImportGEOJSONActionAdmin(object): if not values: error_count += 1 continue - num_insee = values.pop('numero_insee') - year = values.pop('year') or None + num_insee = values.pop("numero_insee") + year = values.pop("year") or None t, c = models_common.Town.objects.get_or_create( - numero_insee=num_insee, year=year, - defaults=values) + numero_insee=num_insee, year=year, defaults=values + ) if c: created += 1 else: modified = False for k in values: - if keys['update'] and k not in ["center", - "limit"]: + if keys["update"] and k not in ["center", "limit"]: continue if values[k] != getattr(t, k): setattr(t, k, values[k]) @@ -779,78 +821,81 @@ class ImportGEOJSONActionAdmin(object): t.save() if created: self.message_user( - request, - str(_("%d item(s) created.")) % created) + request, str(_("%d item(s) created.")) % created + ) if updated: self.message_user( - request, - str(_("%d item(s) updated.")) % updated) + request, str(_("%d item(s) updated.")) % updated + ) self.import_geojson_clean(tempdir) url = reverse( - 'admin:%s_%s_changelist' % ( - self.model._meta.app_label, - self.model._meta.model_name) + "admin:%s_%s_changelist" + % (self.model._meta.app_label, self.model._meta.model_name) ) return HttpResponseRedirect(url) if not form: form = ImportGeoJsonForm() return render( - request, 'admin/import_from_file.html', - {'file_form': form, 'current_action': 'import_geojson'}) + request, + "admin/import_from_file.html", + {"file_form": form, "current_action": "import_geojson"}, + ) class ImportJSONForm(forms.Form): json_file = forms.FileField( _("Zipped JSON file"), - help_text=_("Import from a zipped JSON file generated by Ishtar") + help_text=_("Import from a zipped JSON file generated by Ishtar"), ) class ImportJSONActionAdmin(admin.ModelAdmin): change_list_template = "admin/json_change_list.html" - import_keys = ['slug', 'txt_idx'] + import_keys = ["slug", "txt_idx"] def get_urls(self): urls = super(ImportJSONActionAdmin, self).get_urls() my_urls = [ - url(r'^import-from-json/$', self.import_json), + url(r"^import-from-json/$", self.import_json), ] return my_urls + urls def import_json(self, request): form = None - if 'apply' in request.POST: + if "apply" in request.POST: form = ImportJSONForm(request.POST, request.FILES) if form.is_valid(): with tempfile.TemporaryDirectory() as tmpdirname: filename = tmpdirname + os.sep + "export.zip" with open(filename, "wb+") as zipped_file: - for chunk in request.FILES['json_file'].chunks(): + for chunk in request.FILES["json_file"].chunks(): zipped_file.write(chunk) result = None result = restore_serialized(filename) try: result = restore_serialized(filename) except ValueError as e: - self.message_user(request, str(e), - level=messages.ERROR) + self.message_user(request, str(e), level=messages.ERROR) if result: for model, count in result: self.message_user( request, str(_("{} {}(s) created/updated.")).format( - count, model)) + count, model + ), + ) url = reverse( - 'admin:%s_%s_changelist' % ( - self.model._meta.app_label, self.model._meta.model_name) + "admin:%s_%s_changelist" + % (self.model._meta.app_label, self.model._meta.model_name) ) return HttpResponseRedirect(url) if not form: form = ImportJSONForm() return render( - request, 'admin/import_from_file.html', - {'file_form': form, 'current_action': 'import_json'} + request, + "admin/import_from_file.html", + {"file_form": form, "current_action": "import_json"}, ) @@ -858,25 +903,25 @@ class AdminRelatedTownForm(forms.ModelForm): class Meta: model = models_common.Town.children.through exclude = [] - from_town = AutoCompleteSelectField( - 'town', required=True, label=_("Parent")) + + from_town = AutoCompleteSelectField("town", required=True, label=_("Parent")) class AdminTownForm(forms.ModelForm): class Meta: model = models_common.Town - exclude = ['imports', 'departement'] - center = PointField(label=_("Center"), required=False, - widget=OSMWidget) - limit = MultiPolygonField(label=_("Limit"), required=False, - widget=OSMWidget) - children = AutoCompleteSelectMultipleField('town', required=False, - label=_("Town children")) + exclude = ["imports", "departement"] + + center = PointField(label=_("Center"), required=False, widget=OSMWidget) + limit = MultiPolygonField(label=_("Limit"), required=False, widget=OSMWidget) + children = AutoCompleteSelectMultipleField( + "town", required=False, label=_("Town children") + ) class TownParentInline(admin.TabularInline): model = models_common.Town.children.through - fk_name = 'to_town' + fk_name = "to_town" form = AdminRelatedTownForm verbose_name = _("Parent") verbose_name_plural = _("Parents") @@ -887,31 +932,38 @@ class TownAdmin(ImportGEOJSONActionAdmin, ImportActionAdmin): change_list_template = "admin/town_change_list.html" model = models_common.Town - list_display = ['name', 'year'] - search_fields = ['name'] - readonly_fields = ['cached_label'] - if settings.COUNTRY == 'fr': - list_display += ['numero_insee'] - search_fields += ['numero_insee', 'areas__label'] + list_display = ["name", "year"] + search_fields = ["name"] + readonly_fields = ["cached_label"] + if settings.COUNTRY == "fr": + list_display += ["numero_insee"] + search_fields += ["numero_insee", "areas__label"] list_filter = ("areas",) form = AdminTownForm inlines = [TownParentInline] - actions = [export_as_csv_action(exclude=['center', 'limit']), - export_as_geojson_action( - "limit", exclude=["center", "departement", "cached_label"])] - import_keys = ['slug', 'txt_idx', 'numero_insee'] + actions = [ + export_as_csv_action(exclude=["center", "limit"]), + export_as_geojson_action( + "limit", exclude=["center", "departement", "cached_label"] + ), + ] + import_keys = ["slug", "txt_idx", "numero_insee"] admin_site.register(models_common.Town, TownAdmin) class GeneralTypeAdmin(ImportActionAdmin, ImportJSONActionAdmin): - search_fields = ('label', 'txt_idx', 'comment',) - list_filter = ('available',) + search_fields = ( + "label", + "txt_idx", + "comment", + ) + list_filter = ("available",) save_on_top = True actions = [export_as_csv_action(), serialize_type_action] prepopulated_fields = {"txt_idx": ("label",)} - LIST_DISPLAY = ['label', 'txt_idx', 'available', 'comment'] + LIST_DISPLAY = ["label", "txt_idx", "available", "comment"] extra_list_display = [] def get_list_display(self, request): @@ -935,63 +987,85 @@ class GeneralTypeAdmin(ImportActionAdmin, ImportJSONActionAdmin): list_select_related = self.get_list_select_related(request) cl = ChangeListForChangeView( - request, self.model, list_display, - list_display_links, list_filter, self.date_hierarchy, - search_fields, list_select_related, self.list_per_page, - self.list_max_show_all, self.list_editable, self, + request, + self.model, + list_display, + list_display_links, + list_filter, + self.date_hierarchy, + search_fields, + list_select_related, + self.list_per_page, + self.list_max_show_all, + self.list_editable, + self, ) return cl.get_queryset(request) - def change_view(self, request, object_id, form_url='', extra_context=None): + def change_view(self, request, object_id, form_url="", extra_context=None): """ Next and previous button on the change view """ if not extra_context: extra_context = {} - ids = list(self.get_changelist_queryset(request).values('pk')) + ids = list(self.get_changelist_queryset(request).values("pk")) previous, current_is_reached, first = None, False, None - extra_context['get_attr'] = "" + extra_context["get_attr"] = "" if request.GET: - extra_context['get_attr'] = "?" + request.GET.urlencode() + extra_context["get_attr"] = "?" + request.GET.urlencode() for v in ids: - pk = str(v['pk']) + pk = str(v["pk"]) if pk == object_id: current_is_reached = True if previous: - extra_context['previous_item'] = previous + extra_context["previous_item"] = previous elif current_is_reached: - extra_context['next_item'] = pk + extra_context["next_item"] = pk break else: if not first: first = pk previous = pk - if 'previous_item' not in extra_context and \ - 'next_item' not in extra_context and first: + if ( + "previous_item" not in extra_context + and "next_item" not in extra_context + and first + ): # on modify current object do not match current criteria # next is the first item - extra_context['next_item'] = first + extra_context["next_item"] = first return super(GeneralTypeAdmin, self).change_view( - request, object_id, form_url, extra_context) + request, object_id, form_url, extra_context + ) -general_models = [models.SourceType, models.AuthorType, models.LicenseType, - models.Language, models.PersonType] +general_models = [ + models.SourceType, + models.AuthorType, + models.LicenseType, + models.Language, + models.PersonType, +] for model in general_models: admin_site.register(model, GeneralTypeAdmin) @admin.register(models.OrganizationType, site=admin_site) class PersonTypeAdmin(GeneralTypeAdmin): - LIST_DISPLAY = ['label', 'grammatical_gender', 'txt_idx', 'available', - 'comment'] + LIST_DISPLAY = ["label", "grammatical_gender", "txt_idx", "available", "comment"] @admin.register(models.TitleType, site=admin_site) class TitleType(GeneralTypeAdmin): - LIST_DISPLAY = ['label', 'long_title', 'grammatical_gender', 'txt_idx', - 'available', 'comment'] + LIST_DISPLAY = [ + "label", + "long_title", + "grammatical_gender", + "txt_idx", + "available", + "comment", + ] class CreateAreaForm(forms.Form): @@ -1001,31 +1075,33 @@ class CreateAreaForm(forms.Form): def __init__(self, *args, **kwargs): super(CreateAreaForm, self).__init__(*args, **kwargs) - self.fields["area"].choices = [('', '--')] + [ - (area.pk, area.label) - for area in models.Area.objects.order_by("reference")] + self.fields["area"].choices = [("", "--")] + [ + (area.pk, area.label) for area in models.Area.objects.order_by("reference") + ] def clean(self): - area_name = self.cleaned_data.get('area_name', "") - area = self.cleaned_data.get('area', 0) + area_name = self.cleaned_data.get("area_name", "") + area = self.cleaned_data.get("area", 0) if (not area_name and not area) or (area and area_name): - raise forms.ValidationError(_("Choose an area or set an area " - "reference.")) + raise forms.ValidationError( + _("Choose an area or set an area " "reference.") + ) return self.cleaned_data def clean_department_number(self): - value = self.cleaned_data.get('department_number', 0) + value = self.cleaned_data.get("department_number", 0) if value < 1 or (value > 95 and (value < 970 or value > 989)): raise forms.ValidationError(_("Invalid department number.")) return value def clean_area_name(self): - value = self.cleaned_data.get('area_name', '') + value = self.cleaned_data.get("area_name", "") if not value: return value if models.Area.objects.filter(label=value).count(): - raise forms.ValidationError(_("This name is already used by " - "another area.")) + raise forms.ValidationError( + _("This name is already used by " "another area.") + ) return value @@ -1035,64 +1111,68 @@ class CreateDepartmentActionAdmin(GeneralTypeAdmin): def get_urls(self): urls = super(CreateDepartmentActionAdmin, self).get_urls() my_urls = [ - url(r'^create-department/$', self.create_area), + url(r"^create-department/$", self.create_area), ] return my_urls + urls def create_area(self, request): form = None - if 'apply' in request.POST: + if "apply" in request.POST: form = CreateAreaForm(request.POST) if form.is_valid(): - area_name = form.cleaned_data.get("area_name", '') + area_name = form.cleaned_data.get("area_name", "") if area_name: slug = "dpt-" + area_name while models.Area.objects.filter(txt_idx=slug).count(): slug += "b" - area = models.Area.objects.create(label=area_name, - txt_idx=slug) + area = models.Area.objects.create(label=area_name, txt_idx=slug) self.message_user( - request, - str(_('Area "{}" created.')).format(area_name)) + request, str(_('Area "{}" created.')).format(area_name) + ) else: - area = models.Area.objects.get( - id=form.cleaned_data["area"]) - dpt_num = form.cleaned_data['department_number'] + area = models.Area.objects.get(id=form.cleaned_data["area"]) + dpt_num = form.cleaned_data["department_number"] dpt_num = "0" + str(dpt_num) if dpt_num < 10 else str(dpt_num) current_towns = [a.numero_insee for a in area.towns.all()] nb = 0 - for town in models.Town.objects.filter( - numero_insee__startswith=dpt_num).exclude( - numero_insee__in=current_towns).all(): + for town in ( + models.Town.objects.filter(numero_insee__startswith=dpt_num) + .exclude(numero_insee__in=current_towns) + .all() + ): area.towns.add(town) nb += 1 self.message_user( request, str(_('{} town(s) added to "{}".')).format( - nb, area_name or area.label)) + nb, area_name or area.label + ), + ) url = reverse( - 'admin:%s_%s_changelist' % ( - self.model._meta.app_label, self.model._meta.model_name) + "admin:%s_%s_changelist" + % (self.model._meta.app_label, self.model._meta.model_name) ) return HttpResponseRedirect(url) if not form: form = CreateAreaForm() return render( - request, 'admin/create_area_dpt.html', - {'form': form, 'current_action': 'create_area'} + request, + "admin/create_area_dpt.html", + {"form": form, "current_action": "create_area"}, ) + @admin.register(models.SupportType, site=admin_site) class SupportType(GeneralTypeAdmin): model = models.SupportType - form = make_ajax_form(model, {'document_types': 'source_type'}) + form = make_ajax_form(model, {"document_types": "source_type"}) @admin.register(models.Format, site=admin_site) class Format(GeneralTypeAdmin): model = models.Format - form = make_ajax_form(model, {'document_types': 'source_type'}) + form = make_ajax_form(model, {"document_types": "source_type"}) @admin.register(models.DocumentTag, site=admin_site) @@ -1101,29 +1181,28 @@ class DocumentTag(MergeActionAdmin, GeneralTypeAdmin): class AreaAdmin(CreateDepartmentActionAdmin): - list_display = ('label', 'reference', 'parent', 'available') - search_fields = ('label', 'reference') - list_filter = ('parent',) + list_display = ("label", "reference", "parent", "available") + search_fields = ("label", "reference") + list_filter = ("parent",) model = models.Area - form = make_ajax_form( - model, {'towns': 'town'} - ) + form = make_ajax_form(model, {"towns": "town"}) + admin_site.register(models.Area, AreaAdmin) class ProfileTypeAdmin(GeneralTypeAdmin): model = models.ProfileType - filter_vertical = ('groups',) + filter_vertical = ("groups",) admin_site.register(models.ProfileType, ProfileTypeAdmin) class ProfileTypeSummaryAdmin(admin.ModelAdmin): - change_list_template = 'admin/profiletype_summary_change_list.html' - search_fields = ('label',) - list_filter = ('available', 'label') + change_list_template = "admin/profiletype_summary_change_list.html" + search_fields = ("label",) + list_filter = ("available", "label") def has_add_permission(self, request, obj=None): return False @@ -1139,9 +1218,7 @@ class ProfileTypeSummaryAdmin(admin.ModelAdmin): except (AttributeError, KeyError): return response - profile_types = list( - qs.order_by("label") - ) + profile_types = list(qs.order_by("label")) rights = { profile_type.pk: [g.pk for g in profile_type.groups.all()] for profile_type in profile_types @@ -1151,17 +1228,15 @@ class ProfileTypeSummaryAdmin(admin.ModelAdmin): ok = mark_safe( '<img src="{}admin/img/icon-yes.svg" alt="True">'.format( settings.STATIC_URL - )) + ) + ) for group in models.Group.objects.order_by("name"): gp = [group.name] for profile_type in profile_types: - gp.append( - ok if group.pk in rights[profile_type.pk] - else "-") + gp.append(ok if group.pk in rights[profile_type.pk] else "-") groups.append(gp) - response.context_data.update({"profile_types": profile_types, - "groups": groups}) + response.context_data.update({"profile_types": profile_types, "groups": groups}) return response @@ -1173,7 +1248,7 @@ class ImporterDefaultValuesInline(admin.TabularInline): class ImporterDefaultAdmin(admin.ModelAdmin): - list_display = ('importer_type', 'target') + list_display = ("importer_type", "target") model = models.ImporterDefault inlines = (ImporterDefaultValuesInline,) @@ -1183,7 +1258,7 @@ admin_site.register(models.ImporterDefault, ImporterDefaultAdmin) def duplicate_importertype(modeladmin, request, queryset): res = [] - for obj in queryset.order_by('pk'): + for obj in queryset.order_by("pk"): old_pk = obj.pk obj.pk = None obj.slug = create_slug(models.ImporterType, obj.name) @@ -1224,15 +1299,20 @@ def duplicate_importertype(modeladmin, request, queryset): tg.save() res.append(str(obj)) messages.add_message( - request, messages.INFO, + request, + messages.INFO, str(_("{} importer type(s) duplicated: {}.")).format( - queryset.count(), " ; ".join(res)) + queryset.count(), " ; ".join(res) + ), + ) + url = ( + reverse( + "admin:%s_%s_changelist" + % (modeladmin.model._meta.app_label, modeladmin.model._meta.model_name) ) - url = reverse( - 'admin:%s_%s_changelist' % ( - modeladmin.model._meta.app_label, - modeladmin.model._meta.model_name) - ) + '?' + urllib.parse.urlencode(request.GET) + + "?" + + urllib.parse.urlencode(request.GET) + ) return HttpResponseRedirect(url) @@ -1242,34 +1322,37 @@ duplicate_importertype.short_description = _("Duplicate") def generate_libreoffice_template(modeladmin, request, queryset): if queryset.count() != 1: messages.add_message( - request, messages.ERROR, - str(_("Select only one importer.")) + request, messages.ERROR, str(_("Select only one importer.")) + ) + url = ( + reverse( + "admin:%s_%s_changelist" + % (modeladmin.model._meta.app_label, modeladmin.model._meta.model_name) + ) + + "?" + + urllib.parse.urlencode(request.GET) ) - url = reverse( - 'admin:%s_%s_changelist' % ( - modeladmin.model._meta.app_label, - modeladmin.model._meta.model_name) - ) + '?' + urllib.parse.urlencode(request.GET) return HttpResponseRedirect(url) importer_type = queryset.all()[0] dest_filename = importer_type.get_libreoffice_template() in_memory = BytesIO() - with open(dest_filename, 'rb') as fle: + with open(dest_filename, "rb") as fle: in_memory.write(fle.read()) filename = dest_filename.split(os.sep)[-1] response = HttpResponse( - content_type='application/vnd.oasis.opendocument.spreadsheet') - response['Content-Disposition'] = 'attachment; filename=%s' % \ - filename.replace(' ', '_') + content_type="application/vnd.oasis.opendocument.spreadsheet" + ) + response["Content-Disposition"] = "attachment; filename=%s" % filename.replace( + " ", "_" + ) in_memory.seek(0) response.write(in_memory.read()) return response -generate_libreoffice_template.short_description = \ - _("Export as libreoffice template") +generate_libreoffice_template.short_description = _("Export as libreoffice template") importer_type_actions = [duplicate_importertype] @@ -1277,28 +1360,27 @@ if settings.USE_LIBREOFFICE: importer_type_actions.append(generate_libreoffice_template) -serialize_importer_action = serialize_action("common_imports", - IMPORT_MODEL_LIST) +serialize_importer_action = serialize_action("common_imports", IMPORT_MODEL_LIST) serialize_importer_action.short_description = SERIALIZE_DESC @admin.register(models.ImporterType, site=admin_site) class ImporterTypeAdmin(ImportJSONActionAdmin): - list_display = ('name', 'associated_models', 'available') + list_display = ("name", "associated_models", "available") actions = importer_type_actions + [serialize_importer_action] - form = make_ajax_form(models.ImporterType, {'users': 'ishtaruser'}) + form = make_ajax_form(models.ImporterType, {"users": "ishtaruser"}) prepopulated_fields = {"slug": ("name",)} class RegexpAdmin(admin.ModelAdmin): - list_display = ('name', "regexp", 'description') + list_display = ("name", "regexp", "description") admin_site.register(models.Regexp, RegexpAdmin) class ValueFormaterAdmin(admin.ModelAdmin): - list_display = ('name', "format_string", 'description') + list_display = ("name", "format_string", "description") prepopulated_fields = {"slug": ("name",)} @@ -1307,7 +1389,7 @@ admin_site.register(models.ValueFormater, ValueFormaterAdmin) def duplicate_importercolumn(modeladmin, request, queryset): res = [] - for col in queryset.order_by('col_number'): + for col in queryset.order_by("col_number"): old_pk = col.pk col.pk = None col.label = (col.label or "") + " - duplicate" @@ -1315,7 +1397,8 @@ def duplicate_importercolumn(modeladmin, request, queryset): # get the next available col number col_nb = col.col_number + 1 while modeladmin.model.objects.filter( - col_number=col_nb, importer_type=col.importer_type).count(): + col_number=col_nb, importer_type=col.importer_type + ).count(): col_nb += 1 col.col_number = col_nb col.save() # create new @@ -1330,15 +1413,20 @@ def duplicate_importercolumn(modeladmin, request, queryset): tg.save() res.append(str(col)) messages.add_message( - request, messages.INFO, + request, + messages.INFO, str(_("{} importer column(s) duplicated: {}.")).format( - queryset.count(), " ; ".join(res)) + queryset.count(), " ; ".join(res) + ), + ) + url = ( + reverse( + "admin:%s_%s_changelist" + % (modeladmin.model._meta.app_label, modeladmin.model._meta.model_name) + ) + + "?" + + urllib.parse.urlencode(request.GET) ) - url = reverse( - 'admin:%s_%s_changelist' % ( - modeladmin.model._meta.app_label, - modeladmin.model._meta.model_name) - ) + '?' + urllib.parse.urlencode(request.GET) return HttpResponseRedirect(url) @@ -1346,23 +1434,28 @@ duplicate_importercolumn.short_description = _("Duplicate") def shift_right(modeladmin, request, queryset): - for col in queryset.order_by('-col_number'): + for col in queryset.order_by("-col_number"): # get the next available col number col_nb = col.col_number + 1 while modeladmin.model.objects.filter( - col_number=col_nb, importer_type=col.importer_type).count(): + col_number=col_nb, importer_type=col.importer_type + ).count(): col_nb += 1 col.col_number = col_nb col.save() messages.add_message( - request, messages.INFO, - str(_("{} importer column(s) right-shifted.")).format(queryset.count()) + request, + messages.INFO, + str(_("{} importer column(s) right-shifted.")).format(queryset.count()), + ) + url = ( + reverse( + "admin:%s_%s_changelist" + % (modeladmin.model._meta.app_label, modeladmin.model._meta.model_name) + ) + + "?" + + urllib.parse.urlencode(request.GET) ) - url = reverse( - 'admin:%s_%s_changelist' % ( - modeladmin.model._meta.app_label, - modeladmin.model._meta.model_name) - ) + '?' + urllib.parse.urlencode(request.GET) return HttpResponseRedirect(url) @@ -1371,7 +1464,7 @@ shift_right.short_description = _("Shift right") def shift_left(modeladmin, request, queryset): errors, oks = 0, 0 - for col in queryset.order_by('col_number'): + for col in queryset.order_by("col_number"): # get the next available col number if col.col_number == 1: errors += 1 @@ -1379,7 +1472,8 @@ def shift_left(modeladmin, request, queryset): col_nb = col.col_number - 1 error = False while modeladmin.model.objects.filter( - col_number=col_nb, importer_type=col.importer_type).count(): + col_number=col_nb, importer_type=col.importer_type + ).count(): col_nb -= 1 if col_nb <= 0: errors += 1 @@ -1390,20 +1484,26 @@ def shift_left(modeladmin, request, queryset): oks += 1 if oks: messages.add_message( - request, messages.INFO, - str(_("{} importer column(s) left-shifted.")).format(oks) + request, + messages.INFO, + str(_("{} importer column(s) left-shifted.")).format(oks), ) if errors: messages.add_message( - request, messages.ERROR, - str(_("{} importer column(s) not left-shifted: no " - "place available.")).format(errors) + request, + messages.ERROR, + str( + _("{} importer column(s) not left-shifted: no " "place available.") + ).format(errors), ) - url = reverse( - 'admin:%s_%s_changelist' % ( - modeladmin.model._meta.app_label, - modeladmin.model._meta.model_name) - ) + '?' + urllib.parse.urlencode(request.GET) + url = ( + reverse( + "admin:%s_%s_changelist" + % (modeladmin.model._meta.app_label, modeladmin.model._meta.model_name) + ) + + "?" + + urllib.parse.urlencode(request.GET) + ) return HttpResponseRedirect(url) @@ -1418,9 +1518,7 @@ class ImportTargetForm(forms.ModelForm): class Meta: model = models.ImportTarget exclude = [] - widgets = { - 'comment': forms.TextInput - } + widgets = {"comment": forms.TextInput} class ImportTargetInline(admin.TabularInline): @@ -1430,10 +1528,17 @@ class ImportTargetInline(admin.TabularInline): class ImporterColumnAdmin(admin.ModelAdmin): - list_display = ('label', 'importer_type', 'col_number', 'col_string', - 'description', 'targets_lbl', 'duplicate_fields_lbl', - 'required') - list_filter = ('importer_type',) + list_display = ( + "label", + "importer_type", + "col_number", + "col_string", + "description", + "targets_lbl", + "duplicate_fields_lbl", + "required", + ) + list_filter = ("importer_type",) inlines = (ImportTargetInline, ImporterDuplicateFieldInline) actions = [duplicate_importercolumn, shift_left, shift_right] @@ -1442,7 +1547,7 @@ admin_site.register(models.ImporterColumn, ImporterColumnAdmin) class ImporterModelAdmin(admin.ModelAdmin): - list_display = ('name', 'klass') + list_display = ("name", "klass") model = models.ImporterModel @@ -1450,35 +1555,39 @@ admin_site.register(models.ImporterModel, ImporterModelAdmin) class FormaterTypeAdmin(admin.ModelAdmin): - list_display = ('formater_type', 'options') + list_display = ("formater_type", "options") admin_site.register(models.FormaterType, FormaterTypeAdmin) class ImportAdmin(admin.ModelAdmin): - list_display = ('name', 'importer_type', 'imported_file', 'user', 'state', - 'creation_date') - form = make_ajax_form(models.Import, {'user': 'ishtaruser'}) + list_display = ( + "name", + "importer_type", + "imported_file", + "user", + "state", + "creation_date", + ) + form = make_ajax_form(models.Import, {"user": "ishtaruser"}) admin_site.register(models.Import, ImportAdmin) class TargetKeyGroupAdmin(admin.ModelAdmin): - list_display = ('name', 'all_user_can_use', 'all_user_can_modify', - 'available') - search_fields = ('name',) + list_display = ("name", "all_user_can_use", "all_user_can_modify", "available") + search_fields = ("name",) admin_site.register(models.TargetKeyGroup, TargetKeyGroupAdmin) class TargetKeyAdmin(admin.ModelAdmin): - list_display = ('target', 'importer_type', 'column_nb', 'key', - 'value', 'is_set') + list_display = ("target", "importer_type", "column_nb", "key", "value", "is_set") list_filter = ("is_set", "target__column__importer_type") - search_fields = ('target__target', 'value', 'key') + search_fields = ("target__target", "value", "key") admin_site.register(models.TargetKey, TargetKeyAdmin) @@ -1493,7 +1602,7 @@ admin_site.register(models.OperationType, OperationTypeAdmin) class SpatialReferenceSystemAdmin(GeneralTypeAdmin): - extra_list_display = ['order', 'srid'] + extra_list_display = ["order", "srid"] model = models.SpatialReferenceSystem @@ -1501,8 +1610,8 @@ admin_site.register(models.SpatialReferenceSystem, SpatialReferenceSystemAdmin) class ItemKeyAdmin(admin.ModelAdmin): - list_display = ('content_type', 'key', 'content_object', 'importer') - search_fields = ('key', ) + list_display = ("content_type", "key", "content_object", "importer") + search_fields = ("key",) admin_site.register(models.ItemKey, ItemKeyAdmin) @@ -1516,17 +1625,17 @@ class JsonContentTypeFormMixin(object): def __init__(self, *args, **kwargs): super(JsonContentTypeFormMixin, self).__init__(*args, **kwargs) choices = [] - for pk, label in self.fields['content_type'].choices: + for pk, label in self.fields["content_type"].choices: if not pk: choices.append((pk, label)) continue ct = ContentType.objects.get(pk=pk) model_class = ct.model_class() - if hasattr(model_class, 'data') and \ - not hasattr(model_class, 'history_type'): + if hasattr(model_class, "data") and not hasattr( + model_class, "history_type" + ): choices.append((pk, label)) - self.fields['content_type'].choices = sorted(choices, - key=lambda x: x[1]) + self.fields["content_type"].choices = sorted(choices, key=lambda x: x[1]) class JsonDataSectionForm(JsonContentTypeFormMixin, forms.ModelForm): @@ -1536,7 +1645,7 @@ class JsonDataSectionForm(JsonContentTypeFormMixin, forms.ModelForm): class JsonDataSectionAdmin(admin.ModelAdmin): - list_display = ['name', 'content_type', 'order'] + list_display = ["name", "content_type", "order"] form = JsonDataSectionForm @@ -1550,13 +1659,21 @@ class JsonDataFieldForm(JsonContentTypeFormMixin, forms.ModelForm): class JsonDataFieldAdmin(admin.ModelAdmin): - list_display = ['name', 'content_type', 'key', 'display', - 'value_type', 'search_index', 'order', 'section'] + list_display = [ + "name", + "content_type", + "key", + "display", + "value_type", + "search_index", + "order", + "section", + ] actions = [ - change_value('display', True, _("Display selected")), - change_value('display', False, _("Hide selected")) + change_value("display", True, _("Display selected")), + change_value("display", False, _("Hide selected")), ] - list_filter = ['value_type', 'search_index'] + list_filter = ["value_type", "search_index"] form = JsonDataFieldForm @@ -1564,7 +1681,7 @@ admin_site.register(models.JsonDataField, JsonDataFieldAdmin) def get_choices_form(): - cache_key, value = get_cache(models.CustomForm, ['associated-forms']) + cache_key, value = get_cache(models.CustomForm, ["associated-forms"]) if value: return value register, register_fields = models.CustomForm.register() @@ -1582,9 +1699,11 @@ class CustomFormForm(forms.ModelForm): class Meta: model = models.CustomForm exclude = [] + form = forms.ChoiceField(label=_("Form"), choices=get_choices_form) - users = AutoCompleteSelectMultipleField('ishtaruser', required=False, - label=_("Users")) + users = AutoCompleteSelectMultipleField( + "ishtaruser", required=False, label=_("Users") + ) class ExcludeFieldFormset(BaseInlineFormSet): @@ -1594,9 +1713,9 @@ class ExcludeFieldFormset(BaseInlineFormSet): return kwargs form = self.instance.get_form_class() if not form: - kwargs['choices'] = [] + kwargs["choices"] = [] return kwargs - kwargs['choices'] = [('', '--')] + form.get_custom_fields() + kwargs["choices"] = [("", "--")] + form.get_custom_fields() return kwargs @@ -1604,12 +1723,13 @@ class ExcludeFieldForm(forms.ModelForm): class Meta: model = models.ExcludedField exclude = [] + field = forms.ChoiceField(label=_("Field")) def __init__(self, *args, **kwargs): - choices = kwargs.pop('choices') + choices = kwargs.pop("choices") super(ExcludeFieldForm, self).__init__(*args, **kwargs) - self.fields['field'].choices = choices + self.fields["field"].choices = choices class ExcludeFieldInline(admin.TabularInline): @@ -1624,8 +1744,7 @@ class JsonFieldFormset(BaseInlineFormSet): kwargs = super(JsonFieldFormset, self).get_form_kwargs(index) if not self.instance or not self.instance.pk: return kwargs - kwargs['choices'] = [('', '--')] + \ - self.instance.get_available_json_fields() + kwargs["choices"] = [("", "--")] + self.instance.get_available_json_fields() return kwargs @@ -1635,9 +1754,9 @@ class JsonFieldForm(forms.ModelForm): exclude = [] def __init__(self, *args, **kwargs): - choices = kwargs.pop('choices') + choices = kwargs.pop("choices") super(JsonFieldForm, self).__init__(*args, **kwargs) - self.fields['json_field'].choices = choices + self.fields["json_field"].choices = choices class JsonFieldInline(admin.TabularInline): @@ -1648,10 +1767,25 @@ class JsonFieldInline(admin.TabularInline): class CustomFormAdmin(admin.ModelAdmin): - list_display = ['name', 'form', 'available', 'enabled', 'apply_to_all', - 'users_lbl', 'user_types_lbl'] - fields = ('name', 'form', 'available', 'enabled', 'apply_to_all', 'users', - 'user_types', 'profile_types') + list_display = [ + "name", + "form", + "available", + "enabled", + "apply_to_all", + "users_lbl", + "user_types_lbl", + ] + fields = ( + "name", + "form", + "available", + "enabled", + "apply_to_all", + "users", + "user_types", + "profile_types", + ) form = CustomFormForm inlines = [ExcludeFieldInline, JsonFieldInline] @@ -1659,12 +1793,11 @@ class CustomFormAdmin(admin.ModelAdmin): # no inline on creation if not obj: return [] - return super(CustomFormAdmin, self).get_inline_instances(request, - obj=obj) + return super(CustomFormAdmin, self).get_inline_instances(request, obj=obj) def get_readonly_fields(self, request, obj=None): if obj: - return ('form', "user_types") + return ("form", "user_types") return ("user_types",) @@ -1672,11 +1805,11 @@ admin_site.register(models.CustomForm, CustomFormAdmin) class AdministrationScriptAdmin(admin.ModelAdmin): - list_display = ['name', 'path'] + list_display = ["name", "path"] def get_readonly_fields(self, request, obj=None): if obj: - return ('path',) + return ("path",) return [] @@ -1684,15 +1817,26 @@ admin_site.register(models.AdministrationScript, AdministrationScriptAdmin) class AdministrationTaskAdmin(admin.ModelAdmin): - readonly_fields = ('state', 'creation_date', 'launch_date', - 'finished_date', "result", ) - list_display = ['script', 'state', 'creation_date', 'launch_date', - 'finished_date', "result"] - list_filter = ['script', 'state'] + readonly_fields = ( + "state", + "creation_date", + "launch_date", + "finished_date", + "result", + ) + list_display = [ + "script", + "state", + "creation_date", + "launch_date", + "finished_date", + "result", + ] + list_filter = ["script", "state"] def get_readonly_fields(self, request, obj=None): if obj: - return ("script", ) + self.readonly_fields + return ("script",) + self.readonly_fields return self.readonly_fields @@ -1701,23 +1845,22 @@ admin_site.register(models.AdministrationTask, AdministrationTaskAdmin) def launch_export_action(modeladmin, request, queryset): model = modeladmin.model - back_url = reverse( - 'admin:%s_%s_changelist' % ( - model._meta.app_label, - model._meta.model_name) - ) + '?' + urllib.parse.urlencode(request.GET) - if queryset.count() != 1: - messages.add_message( - request, messages.ERROR, str(_("Select only one task.")) + back_url = ( + reverse( + "admin:%s_%s_changelist" % (model._meta.app_label, model._meta.model_name) ) + + "?" + + urllib.parse.urlencode(request.GET) + ) + if queryset.count() != 1: + messages.add_message(request, messages.ERROR, str(_("Select only one task."))) return HttpResponseRedirect(back_url) export_task = queryset.all()[0] if export_task.state != "C": messages.add_message( - request, messages.ERROR, str( - _("Export already exported/scheduled.")) + request, messages.ERROR, str(_("Export already exported/scheduled.")) ) return HttpResponseRedirect(back_url) @@ -1734,12 +1877,19 @@ launch_export_action.short_description = _("Launch export") class ExportTaskAdmin(admin.ModelAdmin): readonly_fields = ("result", "result_info") - exclude = ('creation_date', 'launch_date', 'finished_date') - list_display = ["label", 'state', "result_info", "result", 'creation_date', - 'launch_date', 'finished_date'] - list_filter = ['state'] + exclude = ("creation_date", "launch_date", "finished_date") + list_display = [ + "label", + "state", + "result_info", + "result", + "creation_date", + "launch_date", + "finished_date", + ] + list_filter = ["state"] actions = [launch_export_action] - form = make_ajax_form(models.ExportTask, {'lock_user': 'user'}) + form = make_ajax_form(models.ExportTask, {"lock_user": "user"}) admin_site.register(models.ExportTask, ExportTaskAdmin) @@ -1747,23 +1897,22 @@ admin_site.register(models.ExportTask, ExportTaskAdmin) def launch_import_action(modeladmin, request, queryset): model = modeladmin.model - back_url = reverse( - 'admin:%s_%s_changelist' % ( - model._meta.app_label, - model._meta.model_name) - ) + '?' + urllib.parse.urlencode(request.GET) - if queryset.count() != 1: - messages.add_message( - request, messages.ERROR, str(_("Select only one task.")) + back_url = ( + reverse( + "admin:%s_%s_changelist" % (model._meta.app_label, model._meta.model_name) ) + + "?" + + urllib.parse.urlencode(request.GET) + ) + if queryset.count() != 1: + messages.add_message(request, messages.ERROR, str(_("Select only one task."))) return HttpResponseRedirect(back_url) import_task = queryset.all()[0] if import_task.state != "C": messages.add_message( - request, messages.ERROR, str( - _("Import already imported/scheduled.")) + request, messages.ERROR, str(_("Import already imported/scheduled.")) ) return HttpResponseRedirect(back_url) @@ -1779,28 +1928,32 @@ launch_import_action.short_description = _("Launch import") class ImportTaskAdmin(admin.ModelAdmin): - exclude = ('creation_date', 'launch_date', 'finished_date') - list_display = ['creation_date', "source", 'state', "import_user", - 'launch_date', 'finished_date'] - list_filter = ['state'] - form = make_ajax_form(models.ImportTask, {'import_user': 'user'}) + exclude = ("creation_date", "launch_date", "finished_date") + list_display = [ + "creation_date", + "source", + "state", + "import_user", + "launch_date", + "finished_date", + ] + list_filter = ["state"] + form = make_ajax_form(models.ImportTask, {"import_user": "user"}) actions = [launch_import_action] class Media: - js = ( - 'js/admin/archive_import.js', - ) + js = ("js/admin/archive_import.js",) admin_site.register(models.ImportTask, ImportTaskAdmin) class UserProfileAdmin(admin.ModelAdmin): - list_display = ['person', 'profile_type', 'area_labels'] - list_filter = ['profile_type'] - search_fields = ['person__raw_name'] + list_display = ["person", "profile_type", "area_labels"] + list_filter = ["profile_type"] + search_fields = ["person__raw_name"] model = models.UserProfile - form = make_ajax_form(model, {'areas': 'area'}) + form = make_ajax_form(model, {"areas": "area"}) admin_site.register(models.UserProfile, UserProfileAdmin) diff --git a/ishtar_common/alternative_configs.py b/ishtar_common/alternative_configs.py index 4e4cd66cf..ece6a632e 100644 --- a/ishtar_common/alternative_configs.py +++ b/ishtar_common/alternative_configs.py @@ -18,9 +18,7 @@ class ConfigDrassm(object): return basefind.external_id -ALTERNATE_CONFIGS = { - 'DRASSM': ConfigDrassm -} +ALTERNATE_CONFIGS = {"DRASSM": ConfigDrassm} ALTERNATE_CONFIGS_CHOICES = [ (k, choice.LABEL) for k, choice in ALTERNATE_CONFIGS.items() diff --git a/ishtar_common/apps.py b/ishtar_common/apps.py index f60f83724..7e2d624d3 100644 --- a/ishtar_common/apps.py +++ b/ishtar_common/apps.py @@ -6,11 +6,11 @@ from django.utils.translation import ugettext_lazy as _ class IshtarAdminSite(AdminSite): - site_header = _('Ishtar administration') + site_header = _("Ishtar administration") site_title = _("Ishtar administration") -admin_site = IshtarAdminSite(name='ishtaradmin') +admin_site = IshtarAdminSite(name="ishtaradmin") class TranslationOverloadConfig(AppConfig): @@ -19,30 +19,30 @@ class TranslationOverloadConfig(AppConfig): class ArchaeologicalContextRecordConfig(AppConfig): - name = 'archaeological_context_records' + name = "archaeological_context_records" verbose_name = _("Ishtar - Context record") class ArchaeologicalFilesConfig(AppConfig): - name = 'archaeological_files' + name = "archaeological_files" verbose_name = _("Ishtar - File") class ArchaeologicalFindsConfig(AppConfig): - name = 'archaeological_finds' + name = "archaeological_finds" verbose_name = _("Ishtar - Find") class ArchaeologicalOperationsConfig(AppConfig): - name = 'archaeological_operations' + name = "archaeological_operations" verbose_name = _("Ishtar - Operation") class ArchaeologicalWarehouseConfig(AppConfig): - name = 'archaeological_warehouse' + name = "archaeological_warehouse" verbose_name = _("Ishtar - Warehouse") class IshtarCommonConfig(AppConfig): - name = 'ishtar_common' + name = "ishtar_common" verbose_name = _("Ishtar - Common") diff --git a/ishtar_common/backend.py b/ishtar_common/backend.py index 916fe6e4a..e3d53c80e 100644 --- a/ishtar_common/backend.py +++ b/ishtar_common/backend.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2010-2017 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> @@ -38,23 +38,25 @@ class ObjectPermBackend(ModelBackend): if not model: # let it manage by the default backend return super(ObjectPermBackend, self).has_perm( - user_obj=user_obj, perm=perm, obj=obj) + user_obj=user_obj, perm=perm, obj=obj + ) try: ishtar_user = models.IshtarUser.objects.get(user_ptr=user_obj) except ObjectDoesNotExist: return False try: - is_ownperm = perm.split('.')[-1].split('_')[1] == 'own' + is_ownperm = perm.split(".")[-1].split("_")[1] == "own" except IndexError: is_ownperm = False - if ishtar_user.has_right('administrator', session=session): + if ishtar_user.has_right("administrator", session=session): return True - main_right = ishtar_user.person.has_right(perm, session=session) \ - or user_obj.has_perm(perm) + main_right = ishtar_user.person.has_right( + perm, session=session + ) or user_obj.has_perm(perm) if not main_right or not is_ownperm: return main_right if obj is None: - model_name = perm.split('_')[-1].lower() + model_name = perm.split("_")[-1].lower() model = None for modl in apps.get_models(): if modl.__name__.lower() == model_name: diff --git a/ishtar_common/context_processors.py b/ishtar_common/context_processors.py index 8caf3b34c..7fb1a486d 100644 --- a/ishtar_common/context_processors.py +++ b/ishtar_common/context_processors.py @@ -28,11 +28,13 @@ from .menus import Menu def get_base_context(request): - dct = {'URL_PATH': settings.URL_PATH, 'BASE_URL': '', - "ISHTAR_MAP_MAX_ITEMS": settings.ISHTAR_MAP_MAX_ITEMS} - if 'HTTP_HOST' in request.META: - dct['BASE_URL'] = "{}://{}".format(request.scheme, - request.META['HTTP_HOST']) + dct = { + "URL_PATH": settings.URL_PATH, + "BASE_URL": "", + "ISHTAR_MAP_MAX_ITEMS": settings.ISHTAR_MAP_MAX_ITEMS, + } + if "HTTP_HOST" in request.META: + dct["BASE_URL"] = "{}://{}".format(request.scheme, request.META["HTTP_HOST"]) try: dct["APP_NAME"] = Site.objects.get_current().name except Site.DoesNotExist: @@ -46,46 +48,49 @@ def get_base_context(request): request.session['MENU'] = menu """ # menu is now in cache - put it back in session later? current_action = None - if 'CURRENT_ACTION' in request.session: - dct['CURRENT_ACTION'] = request.session['CURRENT_ACTION'] - current_action = dct['CURRENT_ACTION'] - dct['CURRENT_PATH'] = request.path + if "CURRENT_ACTION" in request.session: + dct["CURRENT_ACTION"] = request.session["CURRENT_ACTION"] + current_action = dct["CURRENT_ACTION"] + dct["CURRENT_PATH"] = request.path - dct['SITE_PROFILE'] = get_current_profile() + dct["SITE_PROFILE"] = get_current_profile() # messages - dct['MESSAGES'] = [] - if not request.is_ajax() and 'messages' in request.session and \ - request.session['messages']: - for message, message_type in request.session['messages']: - dct['MESSAGES'].append((message, message_type)) - request.session['messages'] = [] - menu = Menu(request.user, current_action=current_action, - session=request.session) + dct["MESSAGES"] = [] + if ( + not request.is_ajax() + and "messages" in request.session + and request.session["messages"] + ): + for message, message_type in request.session["messages"]: + dct["MESSAGES"].append((message, message_type)) + request.session["messages"] = [] + menu = Menu(request.user, current_action=current_action, session=request.session) menu.init() - if hasattr(request.user, 'ishtaruser') and request.user.ishtaruser: - if request.user.ishtaruser.has_right('administrator', - session=request.session): - dct['ADMIN'] = True - if request.user.ishtaruser.current_profile and \ - request.user.ishtaruser.current_profile.display_pin_menu: - dct['DISPLAY_PIN_MENU'] = True + if hasattr(request.user, "ishtaruser") and request.user.ishtaruser: + if request.user.ishtaruser.has_right("administrator", session=request.session): + dct["ADMIN"] = True + if ( + request.user.ishtaruser.current_profile + and request.user.ishtaruser.current_profile.display_pin_menu + ): + dct["DISPLAY_PIN_MENU"] = True if menu.selected_idx is not None: - dct['current_theme'] = "theme-%d" % (menu.selected_idx + 1) - dct['MENU'] = menu + dct["current_theme"] = "theme-%d" % (menu.selected_idx + 1) + dct["MENU"] = menu menu.get_current_selection(request.path) - dct['JQUERY_URL'] = settings.JQUERY_URL - dct['JQUERY_UI_URL'] = settings.JQUERY_UI_URL - dct['COUNTRY'] = settings.COUNTRY - dct['VERSION'] = __version__ - dct['DEBUG'] = settings.DEBUG + dct["JQUERY_URL"] = settings.JQUERY_URL + dct["JQUERY_UI_URL"] = settings.JQUERY_UI_URL + dct["COUNTRY"] = settings.COUNTRY + dct["VERSION"] = __version__ + dct["DEBUG"] = settings.DEBUG medias = [DatePicker().media] - dct['EXTRA_CSS'] = "" - dct['EXTRA_JS'] = "" + dct["EXTRA_CSS"] = "" + dct["EXTRA_JS"] = "" for media in medias: - dct['EXTRA_CSS'] += "\n" + "\n".join(media.render_css()) - dct['EXTRA_JS'] += "\n" + "\n".join(media.render_js()) + dct["EXTRA_CSS"] += "\n" + "\n".join(media.render_css()) + dct["EXTRA_JS"] += "\n" + "\n".join(media.render_js()) if settings.EXTRA_VERSION: - dct['VERSION'] += "-" + str(settings.EXTRA_VERSION) + dct["VERSION"] += "-" + str(settings.EXTRA_VERSION) return dct diff --git a/ishtar_common/data_importer.py b/ishtar_common/data_importer.py index 108e52d7b..b777850c9 100644 --- a/ishtar_common/data_importer.py +++ b/ishtar_common/data_importer.py @@ -39,7 +39,7 @@ from django.utils.translation import ugettext_lazy as _ from ishtar_common.utils import debug_line_no, get_all_field_names, update_data -NEW_LINE_BREAK = '#####@@@#####' +NEW_LINE_BREAK = "#####@@@#####" RE_FILTER_CEDEX = re.compile("(.*) *(?: *CEDEX|cedex|Cedex|Cédex|cédex *\d*)") @@ -47,25 +47,42 @@ RE_FILTER_CEDEX = re.compile("(.*) *(?: *CEDEX|cedex|Cedex|Cédex|cédex *\d*)") def post_importer_action(func): def wrapper(self, context, value): return func(self, context, value) - wrapper.importer_trigger = 'post' + + wrapper.importer_trigger = "post" return wrapper def pre_importer_action(func): def wrapper(self, context, value): return func(self, context, value) - wrapper.importer_trigger = 'pre' + + wrapper.importer_trigger = "pre" return wrapper class ImportFormater(object): - def __init__(self, field_name, formater=None, required=True, through=None, - through_key=None, through_dict=None, - through_unicity_keys=None, duplicate_fields=None, regexp=None, - regexp_formater_args=None, force_value=None, - post_processing=False, concat=False, concat_str=False, - comment="", force_new=None, export_field_name=None, - value_format=None, label=""): + def __init__( + self, + field_name, + formater=None, + required=True, + through=None, + through_key=None, + through_dict=None, + through_unicity_keys=None, + duplicate_fields=None, + regexp=None, + regexp_formater_args=None, + force_value=None, + post_processing=False, + concat=False, + concat_str=False, + comment="", + force_new=None, + export_field_name=None, + value_format=None, + label="", + ): self.field_name = field_name if export_field_name: self.export_field_name = export_field_name @@ -117,28 +134,33 @@ class ImportFormater(object): def report_error(self, *args): return - def init(self, vals, output=None, choose_default=False, - import_instance=None, user=None): + def init( + self, vals, output=None, choose_default=False, import_instance=None, user=None + ): try: lst = iter(self.formater) except TypeError: lst = [self.formater] for formater in lst: if formater: - formater.check(vals, output, self.comment, - choose_default=choose_default, - import_instance=import_instance, - user=user) + formater.check( + vals, + output, + self.comment, + choose_default=choose_default, + import_instance=import_instance, + user=user, + ) def post_process(self, obj, context, value, owner=None): raise NotImplemented() class ImporterError(Exception): - STANDARD = 'S' - HEADER = 'H' + STANDARD = "S" + HEADER = "H" - def __init__(self, message, type='S'): + def __init__(self, message, type="S"): self.msg = message self.type = type @@ -148,13 +170,20 @@ class ImporterError(Exception): class Formater(object): def __init__(self, *args, **kwargs): - self.db_target = kwargs.get('db_target', None) + self.db_target = kwargs.get("db_target", None) def format(self, value): return value - def check(self, values, output=None, comment='', choose_default=False, - import_instance=None, user=None): + def check( + self, + values, + output=None, + comment="", + choose_default=False, + import_instance=None, + user=None, + ): return def init_db_target(self, user=None): @@ -163,17 +192,16 @@ class Formater(object): def _base_target_filter(self, user=None): # set for all users q_or = ( - Q(associated_import__isnull=True) & - Q(associated_user__isnull=True) & - Q(associated_group__isnull=True) + Q(associated_import__isnull=True) + & Q(associated_user__isnull=True) + & Q(associated_group__isnull=True) ) - if hasattr(self, 'import_instance') and self.import_instance: + if hasattr(self, "import_instance") and self.import_instance: # set for current import q_or = q_or | Q(associated_import=self.import_instance) if self.import_instance.associated_group: # set for associated group - q_or = q_or | Q( - associated_group=self.import_instance.associated_group) + q_or = q_or | Q(associated_group=self.import_instance.associated_group) if user: # set for current user q_or = q_or | Q(associated_user=user) @@ -192,17 +220,25 @@ class ChoiceChecker(object): def report_new(self, comment): if not self.new_keys: return - msg = "For \"%s\" these new associations have been made:\n" % comment - sys.stderr.write(msg.encode('utf-8')) + msg = 'For "%s" these new associations have been made:\n' % comment + sys.stderr.write(msg.encode("utf-8")) for k in self.new_keys: msg = '"%s";"%s"\n' % (k, self.new_keys[k]) - sys.stderr.write(msg.encode('utf-8')) + sys.stderr.write(msg.encode("utf-8")) class UnicodeFormater(Formater): - def __init__(self, max_length=None, clean=False, re_filter=None, - notnull=False, prefix='', db_target=None, - import_instance=None, many_split=None): + def __init__( + self, + max_length=None, + clean=False, + re_filter=None, + notnull=False, + prefix="", + db_target=None, + import_instance=None, + many_split=None, + ): self.max_length = max_length self.db_target = db_target self.clean = clean @@ -217,7 +253,7 @@ class UnicodeFormater(Formater): if type(value) != str: value = str(value.strip()) vals = [] - for v in value.split('\n'): + for v in value.split("\n"): v = v.strip() if v: vals.append(v) @@ -236,9 +272,12 @@ class UnicodeFormater(Formater): return if self.max_length and len(value) > self.max_length: raise ValueError( - _("\"%(value)s\" is too long. The max length is %(length)d " - "characters.") % {'value': value, - 'length': self.max_length}) + _( + '"%(value)s" is too long. The max length is %(length)d ' + "characters." + ) + % {"value": value, "length": self.max_length} + ) if self.notnull and not value: return if value: @@ -249,25 +288,23 @@ class UnicodeFormater(Formater): class BooleanFormater(Formater): def format(self, value): value = value.strip().upper() - if value in ('1', 'OUI', 'VRAI', 'YES', 'TRUE'): + if value in ("1", "OUI", "VRAI", "YES", "TRUE"): return True - if value in ('', '0', 'NON', 'FAUX', 'NO', 'FALSE'): + if value in ("", "0", "NON", "FAUX", "NO", "FALSE"): return False - raise ValueError(_("\"%(value)s\" not equal to yes or no") % { - 'value': value}) + raise ValueError(_('"%(value)s" not equal to yes or no') % {"value": value}) class FloatFormater(Formater): def format(self, value): - value = value.strip().replace(',', '.') - value = value.replace(' ', '') + value = value.strip().replace(",", ".") + value = value.replace(" ", "") if not value: return try: return float(value) except ValueError: - raise ValueError(_("\"%(value)s\" is not a float") % { - 'value': value}) + raise ValueError(_('"%(value)s" is not a float') % {"value": value}) class InseeFormater(Formater): @@ -276,11 +313,12 @@ class InseeFormater(Formater): The syntax "CodeINSEE-Year" is accepted (Ishtar trick) in order to manage old INSEE (year is the date of creation) """ - ERROR = _("\"{value}\" is not an appropriate INSEE code") + + ERROR = _('"{value}" is not an appropriate INSEE code') def format(self, value): value = value.strip() - exp = value.split('-') + exp = value.split("-") code = exp[0] try: int(code) @@ -293,7 +331,7 @@ class InseeFormater(Formater): elif len(exp) == 1: return code try: - datetime.datetime.strptime(exp[1], '%Y') + datetime.datetime.strptime(exp[1], "%Y") except ValueError: raise ValueError(str(self.ERROR).format(value)) return code + "-" + exp[1] @@ -308,8 +346,7 @@ class YearFormater(Formater): value = int(value) assert value > 0 and value < (datetime.date.today().year + 30) except (ValueError, AssertionError): - raise ValueError(_("\"%(value)s\" is not a valid date") % { - 'value': value}) + raise ValueError(_('"%(value)s" is not a valid date') % {"value": value}) return value @@ -322,28 +359,34 @@ class YearNoFuturFormater(Formater): value = int(value) assert value > 0 and value < datetime.date.today().year except (ValueError, AssertionError): - raise ValueError(_("\"%(value)s\" is not a valid date") % { - 'value': value}) + raise ValueError(_('"%(value)s" is not a valid date') % {"value": value}) return value class IntegerFormater(Formater): def format(self, value): value = value.strip() - value = value.replace(' ', '') + value = value.replace(" ", "") if not value: return try: return int(value) except ValueError: - raise ValueError(_("\"%(value)s\" is not an integer") % { - 'value': value}) + raise ValueError(_('"%(value)s" is not an integer') % {"value": value}) class StrChoiceFormater(Formater, ChoiceChecker): - def __init__(self, choices, strict=False, equiv_dict=None, model=None, - cli=False, many_split='', db_target=None, - import_instance=None): + def __init__( + self, + choices, + strict=False, + equiv_dict=None, + model=None, + cli=False, + many_split="", + db_target=None, + import_instance=None, + ): if not equiv_dict: equiv_dict = {} self.choices = list(choices) @@ -395,23 +438,30 @@ class StrChoiceFormater(Formater, ChoiceChecker): def prepare(self, value): return str(value).strip() - def _get_choices(self, comment=''): + def _get_choices(self, comment=""): msgstr = comment + " - " - msgstr += str(_("Choice for \"%s\" is not available. " - "Which one is relevant?\n")) + msgstr += str( + _('Choice for "%s" is not available. ' "Which one is relevant?\n") + ) idx = -1 for idx, choice in enumerate(self.choices): msgstr += "%d. %s\n" % (idx + 1, choice[1]) idx += 2 if self.create: - msgstr += str(_("%d. None of the above - create new")) % idx \ - + "\n" + msgstr += str(_("%d. None of the above - create new")) % idx + "\n" idx += 1 msgstr += str(_("%d. None of the above - skip")) % idx + "\n" return msgstr, idx - def check(self, values, output=None, comment='', choose_default=False, - import_instance=None, user=None): + def check( + self, + values, + output=None, + comment="", + choose_default=False, + import_instance=None, + user=None, + ): self.init_db_target(user) """ @@ -433,7 +483,7 @@ class StrChoiceFormater(Formater, ChoiceChecker): except IntegrityError: pass """ - if (not output or output == 'silent') and not choose_default: + if (not output or output == "silent") and not choose_default: return if self.many_split: new_values = [] @@ -446,7 +496,7 @@ class StrChoiceFormater(Formater, ChoiceChecker): value = self.prepare(value) if value in self.equiv_dict: continue - if output != 'cli' and not choose_default: + if output != "cli" and not choose_default: self.missings.add(value) continue msgstr, idx = self._get_choices(comment) @@ -455,7 +505,7 @@ class StrChoiceFormater(Formater, ChoiceChecker): res = 1 while res not in range(1, idx + 1): msg = msgstr % value - sys.stdout.write(msg.encode('utf-8')) + sys.stdout.write(msg.encode("utf-8")) sys.stdout.write("\n>>> ") res = input() try: @@ -472,14 +522,16 @@ class StrChoiceFormater(Formater, ChoiceChecker): self.new_keys[value] = v elif self.create and res == len(self.choices): self.equiv_dict[value] = self.new(base_value) - self.choices.append((self.equiv_dict[value].pk, - str(self.equiv_dict[value]))) + self.choices.append( + (self.equiv_dict[value].pk, str(self.equiv_dict[value])) + ) self.new_keys[value] = str(self.equiv_dict[value]) else: self.equiv_dict[value] = None if self.equiv_dict[value] and self.db_target: from ishtar_common.models import TargetKey - q = {'target': self.db_target, 'key': value} + + q = {"target": self.db_target, "key": value} query = TargetKey.objects.filter(**q) query = query.filter(self._base_target_filter(user)) if query.count(): @@ -488,29 +540,30 @@ class StrChoiceFormater(Formater, ChoiceChecker): target.is_set = True target.save() else: - q['associated_import'] = import_instance + q["associated_import"] = import_instance with transaction.atomic(): - q['value'] = self.equiv_dict[value] - q['is_set'] = True + q["value"] = self.equiv_dict[value] + q["is_set"] = True try: TargetKey.objects.create(**q) except IntegrityError: pass - if output == 'db' and self.db_target: + if output == "db" and self.db_target: from ishtar_common.models import TargetKey + for missing in self.missings: - q = {'target': self.db_target, 'key': missing} + q = {"target": self.db_target, "key": missing} query = TargetKey.objects.filter(**q) query = query.filter(self._base_target_filter(user)) if query.count(): continue with transaction.atomic(): - q['associated_import'] = import_instance + q["associated_import"] = import_instance try: TargetKey.objects.create(**q) except IntegrityError: pass - if output == 'cli': + if output == "cli": self.report_new(comment) def new(self, value): @@ -525,13 +578,20 @@ class StrChoiceFormater(Formater, ChoiceChecker): if not self.strict: value = slugify(value) if value in self.equiv_dict: - self.match_table[origin_value] = self.equiv_dict[value] or '' + self.match_table[origin_value] = self.equiv_dict[value] or "" return self.equiv_dict[value] class TypeFormater(StrChoiceFormater): - def __init__(self, model, cli=False, defaults=None, many_split=False, - db_target=None, import_instance=None): + def __init__( + self, + model, + cli=False, + defaults=None, + many_split=False, + db_target=None, + import_instance=None, + ): if not defaults: defaults = {} self.create = True @@ -559,20 +619,19 @@ class TypeFormater(StrChoiceFormater): def new(self, value): values = copy.copy(self.defaults) - values['label'] = value - values['txt_idx'] = slugify(value) - if 'order' in get_all_field_names(self.model): + values["label"] = value + values["txt_idx"] = slugify(value) + if "order" in get_all_field_names(self.model): order = 1 - q = self.model.objects.values('order').order_by('-order') + q = self.model.objects.values("order").order_by("-order") if q.count(): - order = q.all()[0]['order'] or 1 - values['order'] = order + order = q.all()[0]["order"] or 1 + values["order"] = order return self.model.objects.create(**values) class DateFormater(Formater): - def __init__(self, date_formats=None, db_target=None, - import_instance=None): + def __init__(self, date_formats=None, db_target=None, import_instance=None): if not date_formats: date_formats = ["%d/%m/%Y"] self.date_formats = date_formats @@ -590,8 +649,7 @@ class DateFormater(Formater): return datetime.datetime.strptime(value, date_format).date() except: continue - raise ValueError(_("\"%(value)s\" is not a valid date") % { - 'value': value}) + raise ValueError(_('"%(value)s" is not a valid date') % {"value": value}) class FileFormater(Formater): @@ -602,31 +660,38 @@ class FileFormater(Formater): if not value: return zp = zipfile.ZipFile(archive) - value = value.strip().replace('\\', '/') - items = value.replace('/', '_').split('.') - base_dir = settings.MEDIA_ROOT + 'imported' + value = value.strip().replace("\\", "/") + items = value.replace("/", "_").split(".") + base_dir = settings.MEDIA_ROOT + "imported" if not os.path.isdir(base_dir): os.mkdir(base_dir) - filename = base_dir + os.sep + \ - ".".join(items[:-1]) + '.' + items[-1] + filename = base_dir + os.sep + ".".join(items[:-1]) + "." + items[-1] try: - with open(filename, 'wb') as f: + with open(filename, "wb") as f: with zp.open(value) as z: f.write(z.read()) - f = open(filename, 'rb') + f = open(filename, "rb") my_file = File(f) # manually set the file size because of an issue with TempFile my_file.size = os.stat(filename).st_size return my_file except KeyError: - raise ValueError(_("\"%(value)s\" is not a valid path for the " - "given archive") % {'value': value}) + raise ValueError( + _('"%(value)s" is not a valid path for the ' "given archive") + % {"value": value} + ) class StrToBoolean(Formater, ChoiceChecker): - def __init__(self, choices=None, cli=False, strict=False, db_target=None, - import_instance=None): + def __init__( + self, + choices=None, + cli=False, + strict=False, + db_target=None, + import_instance=None, + ): if not choices: choices = {} self.dct = copy.copy(choices) @@ -656,14 +721,21 @@ class StrToBoolean(Formater, ChoiceChecker): value = slugify(value) return value - def check(self, values, output=None, comment='', choose_default=False, - import_instance=None, user=None): - if (not output or output == 'silent') and not choose_default: + def check( + self, + values, + output=None, + comment="", + choose_default=False, + import_instance=None, + user=None, + ): + if (not output or output == "silent") and not choose_default: return msgstr = comment + " - " - msgstr += str(_( - "Choice for \"%s\" is not available. " - "Which one is relevant?\n")) + msgstr += str( + _('Choice for "%s" is not available. ' "Which one is relevant?\n") + ) msgstr += "1. True\n" msgstr += "2. False\n" msgstr += "3. Empty\n" @@ -671,7 +743,7 @@ class StrToBoolean(Formater, ChoiceChecker): value = self.prepare(value) if value in self.dct: continue - if output != 'cli' and not choose_default: + if output != "cli" and not choose_default: self.missings.add(value) continue res = None @@ -679,7 +751,7 @@ class StrToBoolean(Formater, ChoiceChecker): res = 1 while res not in range(1, 4): msg = msgstr % value - sys.stdout.write(msg.encode('utf-8')) + sys.stdout.write(msg.encode("utf-8")) sys.stdout.write("\n>>> ") res = input() try: @@ -693,17 +765,21 @@ class StrToBoolean(Formater, ChoiceChecker): else: self.dct[value] = None self.new_keys[value] = str(self.dct[value]) - if output == 'db' and self.db_target: + if output == "db" and self.db_target: from ishtar_common.models import TargetKey + for missing in self.missings: try: - q = {'target': self.db_target, 'key': missing, - 'associated_import': import_instance} + q = { + "target": self.db_target, + "key": missing, + "associated_import": import_instance, + } if not TargetKey.objects.filter(**q).count(): TargetKey.objects.create(**q) except IntegrityError: pass - if output == 'cli': + if output == "cli": self.report_new(comment) def format(self, value): @@ -714,11 +790,12 @@ class StrToBoolean(Formater, ChoiceChecker): self.match_table[origin_value] = _(val) return self.dct[value] + logger = logging.getLogger(__name__) def get_object_from_path(obj, path): - for k in path.split('__')[:-1]: + for k in path.split("__")[:-1]: if not hasattr(obj, k): return obj = getattr(obj, k) @@ -726,8 +803,8 @@ def get_object_from_path(obj, path): class Importer(object): - SLUG = '' - NAME = '' + SLUG = "" + NAME = "" DESC = "" LINE_FORMAT = [] OBJECT_CLS = None @@ -737,22 +814,27 @@ class Importer(object): EXTRA_DEFAULTS = {} DEFAULTS = {} ERRORS = { - 'header_check': _( + "header_check": _( "The given file is not correct. Check the file " "format. If you use a CSV file: check that column separator " "and encoding are similar to the ones used by the reference " - "file."), - 'too_many_cols': _("Too many cols (%(user_col)d) when " - "maximum is %(ref_col)d"), - 'no_data': _("No data provided"), - 'value_required': _("Value is required"), - 'not_enough_cols': _("At least %d columns must be filled"), - 'regex_not_match': _("The regexp doesn't match."), - 'improperly_configured': _( + "file." + ), + "too_many_cols": _( + "Too many cols (%(user_col)d) when " "maximum is %(ref_col)d" + ), + "no_data": _("No data provided"), + "value_required": _("Value is required"), + "not_enough_cols": _("At least %d columns must be filled"), + "regex_not_match": _("The regexp doesn't match."), + "improperly_configured": _( "Forced creation is set for model {} but this model is not in the " - "list of models allowed to be created."), - 'does_not_exist_in_db': _("{} with values {} doesn't exist in the " - "database. Create it first or fix your source file."), + "list of models allowed to be created." + ), + "does_not_exist_in_db": _( + "{} with values {} doesn't exist in the " + "database. Create it first or fix your source file." + ), } def _create_models(self, force=False): @@ -761,6 +843,7 @@ class Importer(object): Not useful anymore? """ from ishtar_common import models + q = models.ImporterType.objects.filter(slug=self.SLUG) if not force and (not self.SLUG or q.count()): return @@ -768,45 +851,48 @@ class Importer(object): q.all()[0].delete() name = self.NAME if self.NAME else self.SLUG - model_name = self.OBJECT_CLS.__module__ + '.' + \ - self.OBJECT_CLS.__name__ + model_name = self.OBJECT_CLS.__module__ + "." + self.OBJECT_CLS.__name__ model_cls, c = models.ImporterModel.object.get_or_create( - klass=model_name, default={'name': self.OBJECT_CLS.__name__} + klass=model_name, default={"name": self.OBJECT_CLS.__name__} ) - unicity_keys = '' + unicity_keys = "" if self.UNICITY_KEYS: unicity_keys = ";".join(self.UNICITY_KEYS) importer = models.ImporterType.objects.create( - slug=self.SLUG, name=name, description=self.DESC, - associated_models=model_cls, unicity_keys=unicity_keys) + slug=self.SLUG, + name=name, + description=self.DESC, + associated_models=model_cls, + unicity_keys=unicity_keys, + ) for default in self.DEFAULTS: values = self.DEFAULTS[default] imp_default = models.ImporterDefault.objects.create( - importer_type=importer, - target='__'.join(default)) + importer_type=importer, target="__".join(default) + ) for key in values: - if key in ('history_modifier',): + if key in ("history_modifier",): continue value = values[key] - if hasattr(value, 'txt_idx') and value.txt_idx: + if hasattr(value, "txt_idx") and value.txt_idx: value = value.txt_idx - elif hasattr(value, 'pk') and value.pk: + elif hasattr(value, "pk") and value.pk: value = value.pk if callable(value): value = value() models.ImporterDefaultValues.objects.create( - default_target=imp_default, - target=key, - value=value) + default_target=imp_default, target=key, value=value + ) for idx, line in enumerate(self.line_format): idx += 1 if not line: continue column = models.ImporterColumn.objects.create( - importer_type=importer, col_number=idx) + importer_type=importer, col_number=idx + ) targets = line.field_name if type(targets) not in (list, tuple): targets = [targets] @@ -817,64 +903,73 @@ class Importer(object): formater = formaters[idx_target] formater_name = formater.__class__.__name__ if formater_name not in models.IMPORTER_TYPES_DCT: - formater_name = 'UnknowType' - options = '' - if formater_name == 'TypeFormater': - options = formater.model.__module__ + '.' + \ - formater.model.__name__ - elif formater_name == 'UnicodeFormater': - options = str(formater.max_length or '') - elif formater_name == 'DateFormater': + formater_name = "UnknowType" + options = "" + if formater_name == "TypeFormater": + options = formater.model.__module__ + "." + formater.model.__name__ + elif formater_name == "UnicodeFormater": + options = str(formater.max_length or "") + elif formater_name == "DateFormater": options = formater.date_formats[0] - formater_model, created = \ - models.FormaterType.objects.get_or_create( - formater_type=formater_name, options=options.strip(), - many_split=getattr(formater, 'many_split', None) or '') + formater_model, created = models.FormaterType.objects.get_or_create( + formater_type=formater_name, + options=options.strip(), + many_split=getattr(formater, "many_split", None) or "", + ) regexp_filter = None - if getattr(formater, 'regexp', None): - regexp_filter, created = \ - models.Regexp.objects.get_or_create( - regexp=formater.regex, - defaults={'name': "Default name"}) + if getattr(formater, "regexp", None): + regexp_filter, created = models.Regexp.objects.get_or_create( + regexp=formater.regex, defaults={"name": "Default name"} + ) models.ImportTarget.objects.get_or_create( - column=column, target=target, formater_type=formater_model, - force_new=getattr(formater, 'force_new', False), - concat=getattr(formater, 'concat', False), - concat_str=getattr(formater, 'concat_str', ''), + column=column, + target=target, + formater_type=formater_model, + force_new=getattr(formater, "force_new", False), + concat=getattr(formater, "concat", False), + concat_str=getattr(formater, "concat_str", ""), regexp_filter=regexp_filter, - comment=line.comment) + comment=line.comment, + ) return True def _get_improperly_conf_error(self, model): from ishtar_common.models import ImporterModel + cls_name = model.__module__ + "." + model.__name__ q = ImporterModel.objects.filter(klass=cls_name) if q.count(): cls_name = q.all()[0].name - return ImporterError( - str(self.ERRORS['improperly_configured']).format(cls_name)) + return ImporterError(str(self.ERRORS["improperly_configured"]).format(cls_name)) def _get_does_not_exist_in_db_error(self, model, data): from ishtar_common.models import ImporterModel + cls_name = model.__module__ + "." + model.__name__ q = ImporterModel.objects.filter(klass=cls_name) if q.count(): cls_name = q.all()[0].name - values = ", ".join( - ["{}: {}".format(k, data[k]) for k in data] - ) + values = ", ".join(["{}: {}".format(k, data[k]) for k in data]) raise ImporterError( - str(self.ERRORS['does_not_exist_in_db']).format(cls_name, values)) + str(self.ERRORS["does_not_exist_in_db"]).format(cls_name, values) + ) - def __init__(self, skip_lines=0, reference_header=None, - check_col_num=False, test=False, history_modifier=None, - output='silent', import_instance=None, - conservative_import=False): + def __init__( + self, + skip_lines=0, + reference_header=None, + check_col_num=False, + test=False, + history_modifier=None, + output="silent", + import_instance=None, + conservative_import=False, + ): """ - * skip_line must be set if the data provided has got headers lines. - * a reference_header can be provided to perform a data compliance - check. It can be useful to warn about bad parsing. - * test doesn't write in the database + * skip_line must be set if the data provided has got headers lines. + * a reference_header can be provided to perform a data compliance + check. It can be useful to warn about bad parsing. + * test doesn't write in the database """ self.skip_lines = skip_lines self.reference_header = reference_header @@ -918,14 +1013,15 @@ class Importer(object): self.history_modifier = self.import_instance.user.user_ptr else: # import made by the CLI: get the first admin - self.history_modifier = User.objects.filter( - is_superuser=True).order_by('pk')[0] + self.history_modifier = User.objects.filter(is_superuser=True).order_by( + "pk" + )[0] def post_processing(self, idx_line, item): # force django based post-processing for the item item = item.__class__.objects.get(pk=item.pk) item.save() - if hasattr(item, 'RELATED_POST_PROCESS'): + if hasattr(item, "RELATED_POST_PROCESS"): for related_key in item.RELATED_POST_PROCESS: for related in getattr(item, related_key).all(): related.save() @@ -937,8 +1033,7 @@ class Importer(object): self.errors.append((idx_line, None, msg)) return item - def initialize(self, table, output='silent', choose_default=False, - user=None): + def initialize(self, table, output="silent", choose_default=False, user=None): """ copy vals in columns and initialize formaters * output: @@ -948,7 +1043,7 @@ class Importer(object): (further exploitation by web interface) - user: associated user """ - assert output in ('silent', 'cli', 'db') + assert output in ("silent", "cli", "db") vals = [] for idx_line, line in enumerate(table): if self.skip_lines > idx_line: @@ -969,22 +1064,34 @@ class Importer(object): db_targets = [] for field_name in field_names: db_targets.append( - self.DB_TARGETS["{}-{}".format( - idx + 1, field_name)]) + self.DB_TARGETS["{}-{}".format(idx + 1, field_name)] + ) formater.reinit_db_target(db_targets, user=user) - formater.init(vals[idx], output, choose_default=choose_default, - import_instance=self.import_instance, - user=user) + formater.init( + vals[idx], + output, + choose_default=choose_default, + import_instance=self.import_instance, + user=user, + ) def get_formaters(self): return self.line_format - def importation(self, table, initialize=True, choose_default=False, - user=None, line_to_process=None, simulate=False): + def importation( + self, + table, + initialize=True, + choose_default=False, + user=None, + line_to_process=None, + simulate=False, + ): if initialize: - self.initialize(table, self.output, - choose_default=choose_default, user=user) + self.initialize( + table, self.output, choose_default=choose_default, user=user + ) self.simulate = simulate return self._importation(table, line_to_process=line_to_process) @@ -996,13 +1103,14 @@ class Importer(object): return self.DB_TARGETS = {} from ishtar_common.models import ImporterColumn, ImportTarget + for idx, line in enumerate(self.line_format): idx += 1 if not line: continue col = ImporterColumn.objects.get( - importer_type=self.import_instance.importer_type, - col_number=idx) + importer_type=self.import_instance.importer_type, col_number=idx + ) formater = line.formater targets = line.field_name if type(formater) not in (list, tuple): @@ -1012,18 +1120,26 @@ class Importer(object): tg = target if type(target) == list and type(target[0]) == list: tg = target[0] - self.DB_TARGETS["{}-{}".format(idx, tg)] = \ - ImportTarget.objects.get(column=col, target=tg) + self.DB_TARGETS["{}-{}".format(idx, tg)] = ImportTarget.objects.get( + column=col, target=tg + ) @classmethod def _field_name_to_data_dict( - cls, field_name, value, data, force_value=False, concat=False, - concat_str="", force_new=False): + cls, + field_name, + value, + data, + force_value=False, + concat=False, + concat_str="", + force_new=False, + ): field_names = field_name if type(field_names) not in (list, tuple): field_names = [field_name] for field_name in field_names: - keys = field_name.split('__') + keys = field_name.split("__") current_data = data for idx, key in enumerate(keys): if idx == (len(keys) - 1): # last @@ -1040,23 +1156,26 @@ class Importer(object): if not value: continue current_data[key] = ( - current_data[key] + (concat_str or "")) \ - if current_data[key] else "" + (current_data[key] + (concat_str or "")) + if current_data[key] + else "" + ) current_data[key] += value elif force_value and value: - if concat_str and key in current_data \ - and current_data[key]: - current_data[key] = str(current_data[key]) + \ - concat_str + str(value) + if concat_str and key in current_data and current_data[key]: + current_data[key] = ( + str(current_data[key]) + concat_str + str(value) + ) else: current_data[key] = value elif key not in current_data or not current_data[key]: current_data[key] = value elif concat_str: - current_data[key] = str(current_data[key]) +\ - concat_str + str(value) + current_data[key] = ( + str(current_data[key]) + concat_str + str(value) + ) if force_new: - current_data['__force_new'] = True + current_data["__force_new"] = True elif key not in current_data: current_data[key] = {} current_data = current_data[key] @@ -1066,10 +1185,12 @@ class Importer(object): self.match_table = {} table = list(table) if not table or not table[0]: - raise ImporterError(self.ERRORS['no_data'], ImporterError.HEADER) + raise ImporterError(self.ERRORS["no_data"], ImporterError.HEADER) if self.check_col_num and len(table[0]) > len(self.line_format): - raise ImporterError(self.ERRORS['too_many_cols'] % { - 'user_col': len(table[0]), 'ref_col': len(self.line_format)}) + raise ImporterError( + self.ERRORS["too_many_cols"] + % {"user_col": len(table[0]), "ref_col": len(self.line_format)} + ) self.errors = [] self.validity = [] self.number_imported = 0 @@ -1083,15 +1204,16 @@ class Importer(object): # min col number to be filled self.min_col_number = len(self.line_format) - idx_last_col # check the conformity with the reference header - if self.reference_header and \ - self.skip_lines and \ - self.reference_header != table[0]: - raise ImporterError(self.ERRORS['header_check'], - type=ImporterError.HEADER) + if ( + self.reference_header + and self.skip_lines + and self.reference_header != table[0] + ): + raise ImporterError(self.ERRORS["header_check"], type=ImporterError.HEADER) self.now = datetime.datetime.now() start = datetime.datetime.now() total = len(table) - if self.output == 'cli': + if self.output == "cli": sys.stdout.write("\n") results = [] for idx_line, line in enumerate(table): @@ -1101,7 +1223,7 @@ class Importer(object): continue if idx_line > line_to_process: return results - if self.output == 'cli': + if self.output == "cli": left = None if idx_line > 10: ellapsed = datetime.datetime.now() - start @@ -1111,7 +1233,7 @@ class Importer(object): txt = "\r* %d/%d" % (idx_line + 1, total) if left: txt += " (%d seconds left)" % left - sys.stdout.write(txt.encode('utf-8')) + sys.stdout.write(txt.encode("utf-8")) sys.stdout.flush() try: results.append(self._line_processing(idx_line, line)) @@ -1132,8 +1254,11 @@ class Importer(object): if not line: self.validity.append([]) return - if not self.simulate and self.import_instance and \ - not self.import_instance.has_changes(idx_line): + if ( + not self.simulate + and self.import_instance + and not self.import_instance.has_changes(idx_line) + ): self.validity.append(line) return @@ -1148,8 +1273,7 @@ class Importer(object): self.current_csv_line = line n = datetime.datetime.now() - logger.debug('%s - Processing line %d' % (str(n - self.now), - idx_line)) + logger.debug("%s - Processing line %d" % (str(n - self.now), idx_line)) self.now = n n2 = n self.c_errors = False @@ -1163,19 +1287,23 @@ class Importer(object): self.validity.append(c_row) if not self.c_errors and (idx_col + 1) < self.min_col_number: self.c_errors = True - self.errors.append(( - idx_line + 1, idx_col + 1, - self.ERRORS['not_enough_cols'] % self.min_col_number)) + self.errors.append( + ( + idx_line + 1, + idx_col + 1, + self.ERRORS["not_enough_cols"] % self.min_col_number, + ) + ) if self.c_errors: return n = datetime.datetime.now() - logger.debug('* %s - Cols read' % (str(n - n2))) + logger.debug("* %s - Cols read" % (str(n - n2))) n2 = n if self.test: return # manage unicity of items (mainly for updates) - if 'history_modifier' in get_all_field_names(self.OBJECT_CLS): - data['history_modifier'] = self.history_modifier + if "history_modifier" in get_all_field_names(self.OBJECT_CLS): + data["history_modifier"] = self.history_modifier self.new_objects, self.updated_objects = [], [] self.ambiguous_objects, self.not_find_objects = [], [] @@ -1189,8 +1317,7 @@ class Importer(object): if self.import_instance: self.import_instance.add_imported_line(self.idx_line) - if self.import_instance and hasattr(obj, 'imports') \ - and created: + if self.import_instance and hasattr(obj, "imports") and created: obj.imports.add(self.import_instance) if created: @@ -1198,17 +1325,18 @@ class Importer(object): else: self.number_updated += 1 - if not created and 'defaults' in data: - for k in data['defaults']: - setattr(obj, k, data['defaults'][k]) + if not created and "defaults" in data: + for k in data["defaults"]: + setattr(obj, k, data["defaults"][k]) obj.save() n = datetime.datetime.now() - logger.debug('* %s - Item saved' % (str(n - n2))) + logger.debug("* %s - Item saved" % (str(n - n2))) n2 = n for formater, value in self._throughs: n = datetime.datetime.now() - logger.debug('* %s - Processing formater %s' % (str(n - n2), - formater.field_name)) + logger.debug( + "* %s - Processing formater %s" % (str(n - n2), formater.field_name) + ) n2 = n data = {} if formater.through_dict: @@ -1218,38 +1346,41 @@ class Importer(object): data[formater.field_name] = value through_cls = formater.through if formater.through_unicity_keys: - data['defaults'] = {} + data["defaults"] = {} for k in list(data.keys()): - if k not in formater.through_unicity_keys \ - and k != 'defaults': - data['defaults'][k] = data.pop(k) + if k not in formater.through_unicity_keys and k != "defaults": + data["defaults"][k] = data.pop(k) created = False - if '__force_new' in data: - if self.MODEL_CREATION_LIMIT and \ - through_cls not in self.MODEL_CREATION_LIMIT: + if "__force_new" in data: + if ( + self.MODEL_CREATION_LIMIT + and through_cls not in self.MODEL_CREATION_LIMIT + ): raise self._get_improperly_conf_error(through_cls) - created = data.pop('__force_new') + created = data.pop("__force_new") t_obj = through_cls.objects.create(**data) else: - if not self.MODEL_CREATION_LIMIT or \ - through_cls in self.MODEL_CREATION_LIMIT: + if ( + not self.MODEL_CREATION_LIMIT + or through_cls in self.MODEL_CREATION_LIMIT + ): t_obj, created = through_cls.objects.get_or_create(**data) else: get_data = data.copy() - if 'defaults' in get_data: - get_data.pop('defaults') + if "defaults" in get_data: + get_data.pop("defaults") try: t_obj = through_cls.objects.get(**get_data) except through_cls.DoesNotExist: raise self._get_does_not_exist_in_db_error( - through_cls, get_data) - if not created and 'defaults' in data: + through_cls, get_data + ) + if not created and "defaults" in data: t_obj = t_obj.__class__.objects.get(pk=t_obj.pk) - for k in data['defaults']: - setattr(t_obj, k, data['defaults'][k]) + for k in data["defaults"]: + setattr(t_obj, k, data["defaults"][k]) t_obj.save() - if self.import_instance and hasattr(t_obj, 'imports') \ - and created: + if self.import_instance and hasattr(t_obj, "imports") and created: t_obj.imports.add(self.import_instance) if not obj: return data @@ -1270,35 +1401,37 @@ class Importer(object): self._post_processing.append((formater, val)) if not formater or not formater.field_name: - c_row.append(_('Not imported')) + c_row.append(_("Not imported")) return if formater.regexp: # multiline regexp is a mess... - val = val.replace('\n', NEW_LINE_BREAK) + val = val.replace("\n", NEW_LINE_BREAK) match = formater.regexp.match(val) if not match: if formater.required: self.errors.append( - (idx_line + 1, idx_col + 1, - self.ERRORS['value_required'])) + (idx_line + 1, idx_col + 1, self.ERRORS["value_required"]) + ) self.c_errors = True elif not val.strip(): c_row.append("") return - val = val.replace(NEW_LINE_BREAK, '\n') + val = val.replace(NEW_LINE_BREAK, "\n") self.errors.append( - (idx_line + 1, idx_col + 1, - str(self.ERRORS['regex_not_match']) + val)) + ( + idx_line + 1, + idx_col + 1, + str(self.ERRORS["regex_not_match"]) + val, + ) + ) c_row.append("") return val_group = [] for g in formater.regexp.findall(val): if isinstance(g, (tuple, list)): g = "".join(g) - val_group.append( - g.replace(NEW_LINE_BREAK, '\n') if g else '' - ) + val_group.append(g.replace(NEW_LINE_BREAK, "\n") if g else "") val = "".join(val_group) field_names = formater.field_name @@ -1314,7 +1447,7 @@ class Importer(object): func = getattr(self, func) values = [val] - many_values = getattr(func, 'many_split', None) + many_values = getattr(func, "many_split", None) if many_values: values = re.split(func.many_split, values[0]) # filter empty entries on m2m such as "my-value & " @@ -1341,8 +1474,8 @@ class Importer(object): if self.DB_TARGETS: formater.import_instance = self.import_instance formater.reinit_db_target( - self.DB_TARGETS["{}-{}".format(idx_col + 1, field_name)], - idx_fields) + self.DB_TARGETS["{}-{}".format(idx_col + 1, field_name)], idx_fields + ) for idx, v in enumerate(values): try: """ @@ -1354,7 +1487,7 @@ class Importer(object): value = func.format(*args) else: """ - if getattr(func, 'need_archive', False): + if getattr(func, "need_archive", False): value = func.format(v, archive=self.archive) else: value = func.format(v) @@ -1362,7 +1495,7 @@ class Importer(object): if formater.required: self.c_errors = True self.errors.append((idx_line + 1, idx_col + 1, str(e))) - c_values.append('') + c_values.append("") return if formater.value_format and value is not None and value != "": value = formater.value_format.format(value) @@ -1371,7 +1504,7 @@ class Importer(object): # later self.to_be_close.append(value) formated_values.append(value) - if hasattr(func, 'match_table'): + if hasattr(func, "match_table"): if field_name not in self.match_table: self.match_table[field_name] = {} self.match_table[field_name].update(func.match_table) @@ -1390,8 +1523,9 @@ class Importer(object): c_values.append(" ; ".join([str(v) for v in printed_values])) if value is None and formater.required: self.c_errors = True - self.errors.append((idx_line + 1, idx_col + 1, - self.ERRORS['value_required'])) + self.errors.append( + (idx_line + 1, idx_col + 1, self.ERRORS["value_required"]) + ) return field_names = [field_name] @@ -1402,11 +1536,11 @@ class Importer(object): # duplicate fields are only for the first occurrence for duplicate_field in formater.duplicate_fields: if type(duplicate_field[0]) in (list, tuple): - duplicate_field, force_new, concat, conc_str = \ - duplicate_field[0] + duplicate_field, force_new, concat, conc_str = duplicate_field[ + 0 + ] else: - duplicate_field, force_new, concat, conc_str = \ - duplicate_field + duplicate_field, force_new, concat, conc_str = duplicate_field field_names += [duplicate_field] force_news += [force_new] concats += [concat] @@ -1417,13 +1551,17 @@ class Importer(object): else: for idx, f_name in enumerate(field_names): self._field_name_to_data_dict( - f_name, value, data, formater.force_value, - force_new=force_news[idx], concat=concats[idx], - concat_str=concat_str[idx]) + f_name, + value, + data, + formater.force_value, + force_new=force_news[idx], + concat=concats[idx], + concat_str=concat_str[idx], + ) c_row.append(" ; ".join([v for v in c_values])) - def _get_field_m2m(self, attribute, data, c_path, new_created, - field_object): + def _get_field_m2m(self, attribute, data, c_path, new_created, field_object): """ Manage and m2m field from raw data @@ -1439,13 +1577,13 @@ class Importer(object): many_values = data.pop(attribute) model = None - if hasattr(field_object, 'rel'): + if hasattr(field_object, "rel"): model = field_object.rel.to - elif hasattr(field_object, 'related_model'): + elif hasattr(field_object, "related_model"): model = field_object.related_model - elif hasattr(field_object, 'to'): + elif hasattr(field_object, "to"): model = field_object.to - elif hasattr(field_object, 'model'): + elif hasattr(field_object, "model"): model = field_object.model if type(many_values) not in (list, tuple): many_values = [many_values] @@ -1498,34 +1636,31 @@ class Importer(object): field_names = get_all_field_names(model) for v in vals: - if 'history_modifier' in field_names: - if 'defaults' not in v: - v['defaults'] = {} - v['defaults']['history_modifier'] = \ - self.history_modifier + if "history_modifier" in field_names: + if "defaults" not in v: + v["defaults"] = {} + v["defaults"]["history_modifier"] = self.history_modifier m2m_m2ms = [] c_c_path = c_path[:] for k in list(v.keys()): if k not in field_names: continue - self.get_field(model, k, v, m2m_m2ms, c_c_path, - new_created) - if '__force_new' in v: - created = v.pop('__force_new') - key = ";".join(["{}-{}".format(k, v[k]) - for k in sorted(v.keys())]) + self.get_field(model, k, v, m2m_m2ms, c_c_path, new_created) + if "__force_new" in v: + created = v.pop("__force_new") + key = ";".join(["{}-{}".format(k, v[k]) for k in sorted(v.keys())]) # only one forced creation - if attribute in new_created \ - and key in new_created[attribute]: + if attribute in new_created and key in new_created[attribute]: continue if attribute not in new_created: new_created[attribute] = [] new_created[attribute].append(key) - has_values = bool([1 for k in v - if v[k] and k != "defaults"]) + has_values = bool([1 for k in v if v[k] and k != "defaults"]) if has_values: - if self.MODEL_CREATION_LIMIT and \ - model not in self.MODEL_CREATION_LIMIT: + if ( + self.MODEL_CREATION_LIMIT + and model not in self.MODEL_CREATION_LIMIT + ): raise self._get_improperly_conf_error(model) if "defaults" in v: default_values = v.pop("defaults") @@ -1536,7 +1671,7 @@ class Importer(object): else: continue else: - v['defaults'] = v.get('defaults', {}) + v["defaults"] = v.get("defaults", {}) extra_fields = {} # "File" type is a temp object and can be different # for the same filename - it must be treated @@ -1544,33 +1679,32 @@ class Importer(object): for field in model._meta.fields: k = field.name # attr_class is a FileField attribute - if hasattr(field, 'attr_class') and k in v: + if hasattr(field, "attr_class") and k in v: extra_fields[k] = v.pop(k) created = False - if not self.MODEL_CREATION_LIMIT or \ - model in self.MODEL_CREATION_LIMIT: + if ( + not self.MODEL_CREATION_LIMIT + or model in self.MODEL_CREATION_LIMIT + ): try: - v, created = model.objects.get_or_create( - **v) + v, created = model.objects.get_or_create(**v) except FieldError as e: raise ImporterError( - str( - _("Importer configuration error: " - "\"{}\".")).format(e)) + str(_("Importer configuration error: " '"{}".')).format( + e + ) + ) except Exception as e: - msg = str( - _("Import error: {} - \"{}\".") - ).format(model, e) + msg = str(_('Import error: {} - "{}".')).format(model, e) raise ImporterError(msg) else: get_v = v.copy() - if 'defaults' in get_v: - get_v.pop('defaults') + if "defaults" in get_v: + get_v.pop("defaults") try: v = model.objects.get(**get_v) except model.DoesNotExist: - raise self._get_does_not_exist_in_db_error( - model, get_v) + raise self._get_does_not_exist_in_db_error(model, get_v) changed = False for k in extra_fields.keys(): if extra_fields[k]: @@ -1583,8 +1717,7 @@ class Importer(object): objs = [objs] for obj in objs: getattr(v, att).add(obj) - if self.import_instance \ - and hasattr(v, 'imports') and created: + if self.import_instance and hasattr(v, "imports") and created: v.imports.add(self.import_instance) m2ms.append((attribute, v)) return m2ms @@ -1600,15 +1733,16 @@ class Importer(object): :return: None """ func = getattr(cls, attribute) - if func.importer_trigger == 'pre': + if func.importer_trigger == "pre": func(data, data[attribute]) - elif func.importer_trigger == 'post': - self._item_post_processing.append([attribute, data, - data[attribute]]) + elif func.importer_trigger == "post": + self._item_post_processing.append([attribute, data, data[attribute]]) else: - logger.warning("Unknow importer_trigger '{}' for '{}'".format( - func.importer_trigger, attribute - )) + logger.warning( + "Unknow importer_trigger '{}' for '{}'".format( + func.importer_trigger, attribute + ) + ) data.pop(attribute) def get_field(self, cls, attribute, data, m2ms, c_path, new_created): @@ -1624,53 +1758,62 @@ class Importer(object): multiple creation :return: None """ - if hasattr(cls, attribute) and \ - getattr(getattr(cls, attribute), 'importer_trigger', None): + if hasattr(cls, attribute) and getattr( + getattr(cls, attribute), "importer_trigger", None + ): # importer trigger self._set_importer_trigger(cls, attribute, data) return - if attribute == 'data': # json field + if attribute == "data": # json field # no need to do anything return - if attribute == 'get_default': + if attribute == "get_default": # force evaluation of default value for this field return try: field_object = cls._meta.get_field(attribute) except FieldDoesNotExist: - raise ImporterError(str( - _("Importer configuration error: field \"{}\" does not exist " - "for {}.")).format(attribute, cls._meta.verbose_name)) + raise ImporterError( + str( + _( + 'Importer configuration error: field "{}" does not exist ' + "for {}." + ) + ).format(attribute, cls._meta.verbose_name) + ) if field_object.many_to_many: try: - m2ms += self._get_field_m2m(attribute, data, c_path, - new_created, field_object) + m2ms += self._get_field_m2m( + attribute, data, c_path, new_created, field_object + ) except Exception as e: self.errors.append((self.idx_line, None, str(e))) return - if not hasattr(field_object, 'rel') or not field_object.rel: + if not hasattr(field_object, "rel") or not field_object.rel: return if type(data[attribute]) == list: # extract the first item from list # be careful if the list has more than one item this is arbitrary if len(data[attribute]) > 1: logger.warning( - 'Import {}: {} has many when only one is expected. Get ' - 'the first one but it is not OK!'.format( - self.import_instance, attribute)) + "Import {}: {} has many when only one is expected. Get " + "the first one but it is not OK!".format( + self.import_instance, attribute + ) + ) data[attribute] = data[attribute][0] return if not isinstance(data[attribute], dict): # we treat only dict formated values return # put history_modifier for every created item - if 'history_modifier' in get_all_field_names(field_object.rel.to): - data[attribute]['history_modifier'] = \ - self.history_modifier + if "history_modifier" in get_all_field_names(field_object.rel.to): + data[attribute]["history_modifier"] = self.history_modifier try: c_path.append(attribute) data[attribute], created = self.get_object( - field_object.rel.to, data[attribute].copy(), c_path) + field_object.rel.to, data[attribute].copy(), c_path + ) except ImporterError as msg: self.errors.append((self.idx_line, None, msg)) data[attribute] = None @@ -1684,8 +1827,8 @@ class Importer(object): return data, False is_empty = not bool( - [k for k in data if k not in ('history_modifier', 'defaults') - and data[k]]) + [k for k in data if k not in ("history_modifier", "defaults") and data[k]] + ) if is_empty: # if no value, no creation return None, False @@ -1704,29 +1847,29 @@ class Importer(object): continue if not data[attribute]: if hasattr(cls, attribute) and getattr( - getattr(cls, attribute), 'importer_trigger', None): + getattr(cls, attribute), "importer_trigger", None + ): data.pop(attribute) continue field_object = cls._meta.get_field(attribute) if field_object.many_to_many: data.pop(attribute) continue - if attribute != '__force_new': - self.get_field(cls, attribute, data, m2ms, c_c_path, - new_created) + if attribute != "__force_new": + self.get_field(cls, attribute, data, m2ms, c_c_path, new_created) except (ValueError, IntegrityError, FieldDoesNotExist) as e: try: message = str(e) except (UnicodeDecodeError, UnicodeDecodeError): - message = '' + message = "" try: data = str(data) except UnicodeDecodeError: - data = '' + data = "" raise ImporterError( "Erreur d'import %s %s, contexte : %s, erreur : %s" - % (str(cls), str("__".join(path)), - str(data), message)) + % (str(cls), str("__".join(path)), str(data), message) + ) # image field is not serialized image, associated_file = None, None @@ -1744,7 +1887,7 @@ class Importer(object): for k in list(create_dict.keys()): # filter unnecessary default values but not the json field - if isinstance(create_dict[k], dict) and k != 'data': + if isinstance(create_dict[k], dict) and k != "data": if self.simulate: create_dict[k] = _("* created *") else: @@ -1763,10 +1906,8 @@ class Importer(object): if (k not in data or not data[k]) and self._defaults[path][k]: defaults[k] = self._defaults[path][k] - if 'history_modifier' in create_dict: - defaults.update({ - 'history_modifier': create_dict.pop('history_modifier') - }) + if "history_modifier" in create_dict: + defaults.update({"history_modifier": create_dict.pop("history_modifier")}) created = False post_save_keys = [] @@ -1782,14 +1923,16 @@ class Importer(object): if getattr(dct[key], "post_save", True): dct.pop(key) post_save_keys.append(key) - if '__force_new' in dct: - created = dct.pop('__force_new') + if "__force_new" in dct: + created = dct.pop("__force_new") if not [k for k in dct if dct[k] is not None]: return None, created new_dct = defaults.copy() new_dct.update(dct) - if self.MODEL_CREATION_LIMIT and \ - cls not in self.MODEL_CREATION_LIMIT: + if ( + self.MODEL_CREATION_LIMIT + and cls not in self.MODEL_CREATION_LIMIT + ): raise self._get_improperly_conf_error(cls) if not self.simulate: obj = cls.objects.create(**new_dct) @@ -1799,8 +1942,7 @@ class Importer(object): # manage UNICITY_KEYS - only level 1 if not path and self.UNICITY_KEYS: for k in list(dct.keys()): - if k not in self.UNICITY_KEYS \ - and k != 'defaults': + if k not in self.UNICITY_KEYS and k != "defaults": if dct[k]: defaults[k] = dct.pop(k) else: @@ -1815,68 +1957,72 @@ class Importer(object): if self.simulate: q = cls.objects.filter(**dct) if not q.count(): - if self.MODEL_CREATION_LIMIT and \ - cls not in self.MODEL_CREATION_LIMIT: - self.not_find_objects.append( - (path, cls, dct) - ) + if ( + self.MODEL_CREATION_LIMIT + and cls not in self.MODEL_CREATION_LIMIT + ): + self.not_find_objects.append((path, cls, dct)) return _("* match not find *"), False dct.update(defaults) self.new_objects.append([path, cls, dct]) created = True elif q.count() > 1: - self.ambiguous_objects.append( - (path, list(q.all()), dct) - ) + self.ambiguous_objects.append((path, list(q.all()), dct)) if q.count() > 10: - return _("* the query match more than 10 " - "results*"), False + return ( + _("* the query match more than 10 " "results*"), + False, + ) else: - return str(_(" or ")).join( - [str(item) for item in q.all()] - ), False + return ( + str(_(" or ")).join( + [str(item) for item in q.all()] + ), + False, + ) else: - self.updated_objects.append( - [path, q.all()[0], dct, {}]) - dct['defaults'] = defaults.copy() + self.updated_objects.append([path, q.all()[0], dct, {}]) + dct["defaults"] = defaults.copy() else: if not dct and not defaults: obj = None else: - if not self.MODEL_CREATION_LIMIT or \ - cls in self.MODEL_CREATION_LIMIT: - dct['defaults'] = defaults.copy() + if ( + not self.MODEL_CREATION_LIMIT + or cls in self.MODEL_CREATION_LIMIT + ): + dct["defaults"] = defaults.copy() obj, created = cls.objects.get_or_create(**dct) else: try: obj = cls.objects.get(**dct) - dct['defaults'] = defaults.copy() + dct["defaults"] = defaults.copy() except cls.DoesNotExist: - raise self._get_does_not_exist_in_db_error( - cls, dct) + raise self._get_does_not_exist_in_db_error(cls, dct) if not created and not path and self.UNICITY_KEYS: updated_dct = {} if self.conservative_import: - for k in dct['defaults']: - new_val = dct['defaults'][k] - if new_val is None or new_val == '': + for k in dct["defaults"]: + new_val = dct["defaults"][k] + if new_val is None or new_val == "": continue val = getattr(obj, k) - if val is None or val == '': + if val is None or val == "": updated_dct[k] = new_val - elif k in self.concats \ - and type(val) == str \ - and type(new_val) == str: + elif ( + k in self.concats + and type(val) == str + and type(new_val) == str + ): updated_dct[k] = val + "\n" + new_val else: - for k in dct['defaults']: - new_val = dct['defaults'][k] - if new_val is None or new_val == '': + for k in dct["defaults"]: + new_val = dct["defaults"][k] + if new_val is None or new_val == "": continue - if obj and k == 'data': - updated_dct[k] = update_data(obj.data, - new_val) + if obj and k == "data": + updated_dct[k] = update_data(obj.data, new_val) else: updated_dct[k] = new_val if updated_dct: @@ -1886,16 +2032,19 @@ class Importer(object): for k in updated_dct: setattr(obj, k, updated_dct[k]) obj.save() - if not self.simulate and self.import_instance and \ - hasattr(obj, 'imports') and created: + if ( + not self.simulate + and self.import_instance + and hasattr(obj, "imports") + and created + ): obj.imports.add(self.import_instance) - except (ValueError, IntegrityError, DatabaseError, - GEOSException) as e: + except (ValueError, IntegrityError, DatabaseError, GEOSException) as e: raise IntegrityError(str(e)) except cls.MultipleObjectsReturned as e: created = False - if 'defaults' in dct: - dct.pop('defaults') + if "defaults" in dct: + dct.pop("defaults") raise IntegrityError(str(e)) # obj = cls.objects.filter(**dct).all()[0] for key in post_save_keys: @@ -1914,13 +2063,15 @@ class Importer(object): for v in values: related_model = getattr(obj, attr) # an intermediary model is used - if hasattr(related_model, 'through') and \ - not related_model.through._meta.auto_created: + if ( + hasattr(related_model, "through") + and not related_model.through._meta.auto_created + ): # try to create it with default attributes inter_model = related_model.through target_name, item_name = None, None for field in inter_model._meta.get_fields(): - rel_model = getattr(field, 'related_model', None) + rel_model = getattr(field, "related_model", None) # assume that the first found is correct... if rel_model == v.__class__: target_name = field.name @@ -1948,8 +2099,7 @@ class Importer(object): current_data[item] = {} current_data = current_data[item] for key, value in m2ms: - if not isinstance(value, list) and \ - not isinstance(value, tuple): + if not isinstance(value, list) and not isinstance(value, tuple): value = [value] current_data[key] = value @@ -1957,34 +2107,36 @@ class Importer(object): return dct, True else: # defaults are not presented as matching data - dct.pop('defaults') + dct.pop("defaults") return self.updated_objects[-1][1], False if m2ms: # force post save script obj.save() - if hasattr(obj, 'fix'): + if hasattr(obj, "fix"): # post save/m2m specific fix obj.fix() except IntegrityError as e: try: message = str(e) except (UnicodeDecodeError, UnicodeDecodeError): - message = '' + message = "" try: data = str(data) except UnicodeDecodeError: - data = '' + data = "" raise ImporterError( "Erreur d'import %s %s, contexte : %s, erreur : %s" - % (str(cls), str("__".join(path)), - str(data), message)) + % (str(cls), str("__".join(path)), str(data), message) + ) return obj, created def _format_csv_line(self, values, empty="-"): - return '"' + '","'.join( - [(v and str(v).replace('"', '""')) or empty - for v in values]) + '"' + return ( + '"' + + '","'.join([(v and str(v).replace('"', '""')) or empty for v in values]) + + '"' + ) def _get_csv(self, rows, header=None, empty="-"): if not rows: @@ -1999,14 +2151,13 @@ class Importer(object): return "\n".join(csv_v) def get_csv_errors(self): - return self._get_csv( - self.errors, header=[_("line"), _("col"), _("error")]) + return self._get_csv(self.errors, header=[_("line"), _("col"), _("error")]) def get_csv_result(self): return self._get_csv(self.validity) def get_csv_matches(self): - header = [_('field'), _('source'), _('result')] + header = [_("field"), _("source"), _("result")] values = [] for field in self.match_table: for source in self.match_table[field]: @@ -2022,10 +2173,12 @@ class Importer(object): return if value not in choices_dct.values(): raise ValueError( - _("\"%(value)s\" not in %(values)s") % { - 'value': value, - 'values': ", ".join( - [val for val in choices_dct.values()]) - }) + _('"%(value)s" not in %(values)s') + % { + "value": value, + "values": ", ".join([val for val in choices_dct.values()]), + } + ) return value + return function diff --git a/ishtar_common/forms.py b/ishtar_common/forms.py index db627789f..f5ddee48e 100644 --- a/ishtar_common/forms.py +++ b/ishtar_common/forms.py @@ -49,7 +49,7 @@ from ishtar_common.utils import MultiValueDict class NamedUrlSessionFormWizard(forms.Form): - def __init__(self, form_list, condition_list=None, url_name=''): + def __init__(self, form_list, condition_list=None, url_name=""): if not condition_list: condition_list = {} self.form_list = dict(form_list) @@ -65,14 +65,14 @@ def my_reverse(*args, **kwargs): """ Custom reverse method in order to evaluate lazy args """ - if 'args' in kwargs: + if "args" in kwargs: my_args = [] - for arg in kwargs['args']: + for arg in kwargs["args"]: if callable(arg): my_args.append(str(arg())) else: my_args.append(str(arg)) - kwargs['args'] = my_args + kwargs["args"] = my_args return reverse(*args, **kwargs) @@ -82,14 +82,15 @@ regexp_name = re.compile(r"^[\.,:/\w\-'\"() \&\[\]@]+$", re.UNICODE) name_validator = validators.RegexValidator( regexp_name, _("Enter a valid name consisting of letters, spaces and hyphens."), - 'invalid') + "invalid", +) def file_size_validator(value): limit = (settings.MAX_UPLOAD_SIZE * 1024 * 1024) - 100 if value.size > limit: raise ValidationError( - str(_('File too large. Size should not exceed {} Mo.')).format( + str(_("File too large. Size should not exceed {} Mo.")).format( settings.MAX_UPLOAD_SIZE ) ) @@ -99,9 +100,10 @@ class FloatField(forms.FloatField): """ Allow the use of comma for separating float fields """ + def clean(self, value): if value and isinstance(value, str): - value = value.replace(',', '.').replace('%', '') + value = value.replace(",", ".").replace("%", "") return super(FloatField, self).clean(value) @@ -117,22 +119,23 @@ class FinalDeleteForm(FinalForm): def get_readonly_clean(key): def func(self): - instance = getattr(self, 'instance', None) + instance = getattr(self, "instance", None) if instance and getattr(instance, key): return getattr(instance, key) else: return self.cleaned_data[key] + return func JSON_VALUE_TYPES_FIELDS = { - 'T': (forms.CharField, None), - 'LT': (forms.CharField, forms.Textarea), - 'I': (forms.IntegerField, None), - 'F': (FloatField, None), - 'D': (DateField, None), - 'B': (forms.NullBooleanField, None), - 'C': (widgets.Select2DynamicField, None), + "T": (forms.CharField, None), + "LT": (forms.CharField, forms.Textarea), + "I": (forms.IntegerField, None), + "F": (FloatField, None), + "D": (DateField, None), + "B": (forms.NullBooleanField, None), + "C": (widgets.Select2DynamicField, None), } @@ -141,14 +144,14 @@ class BSForm(object): for k in self.fields: widget = self.fields[k].widget # manage bs decoration - if not hasattr(widget, 'NO_FORM_CONTROL'): - cls = 'form-control' - if 'class' in widget.attrs: - if 'form-control' in widget.attrs['class']: - cls = widget.attrs['class'] + if not hasattr(widget, "NO_FORM_CONTROL"): + cls = "form-control" + if "class" in widget.attrs: + if "form-control" in widget.attrs["class"]: + cls = widget.attrs["class"] else: - cls = widget.attrs['class'] + " " + cls - widget.attrs['class'] = cls + cls = widget.attrs["class"] + " " + cls + widget.attrs["class"] = cls # 32 bits max value if isinstance(self.fields[k], forms.IntegerField): has_max = any( @@ -158,17 +161,18 @@ class BSForm(object): if not has_max: self.fields[k].validators.append( - validators.MaxValueValidator(2147483647)) + validators.MaxValueValidator(2147483647) + ) # manage datepicker if not isinstance(widget, DatePicker): continue lang = translation.get_language() - widget.options['language'] = lang + widget.options["language"] = lang if lang in DATE_FORMAT: - widget.options['format'] = DATE_FORMAT[lang] - if 'autoclose' not in widget.options: - widget.options['autoclose'] = 'true' - widget.options['todayHighlight'] = 'true' + widget.options["format"] = DATE_FORMAT[lang] + if "autoclose" not in widget.options: + widget.options["autoclose"] = "true" + widget.options["todayHighlight"] = "true" class CustomForm(BSForm): @@ -179,9 +183,9 @@ class CustomForm(BSForm): def __init__(self, *args, **kwargs): self.current_user = None - if 'user' in kwargs: + if "user" in kwargs: try: - self.current_user = kwargs.pop('user').ishtaruser + self.current_user = kwargs.pop("user").ishtaruser except AttributeError: pass super(CustomForm, self).__init__(*args, **kwargs) @@ -189,10 +193,9 @@ class CustomForm(BSForm): self.custom_form_ordering() def custom_form_ordering(self): - available, excluded, json_fields = self.check_custom_form( - self.current_user) + available, excluded, json_fields = self.check_custom_form(self.current_user) for exc in excluded: - if hasattr(self, 'fields'): + if hasattr(self, "fields"): self.remove_field(exc) else: # formset @@ -206,12 +209,12 @@ class CustomForm(BSForm): order += 1 new_fields[order] = (key, field) - if not hasattr(self, 'fields'): # formset + if not hasattr(self, "fields"): # formset return field_items, field_hidden_items = [], [] for key, field in self.fields.items(): - if getattr(field.widget, 'is_hidden', None): + if getattr(field.widget, "is_hidden", None): field_hidden_items.append((key, field)) else: field_items.append((key, field)) @@ -260,10 +263,10 @@ class CustomForm(BSForm): :param key: data key :return: tuple of choices (id, value) """ - app_name = cls.__module__.split('.')[0] + app_name = cls.__module__.split(".")[0] if app_name == "archaeological_files_pdl": app_name = "archaeological_files" - model_name = cls.form_slug.split("-")[0].replace('_', "") + model_name = cls.form_slug.split("-")[0].replace("_", "") ct_class = apps.get_model(app_name, model_name) return ct_class._get_dynamic_choices(key) @@ -277,31 +280,38 @@ class CustomForm(BSForm): fields = [] is_search = "search_vector" in cls.base_fields q = custom_form.json_fields.values( - 'label', 'help_text', 'order', 'json_field__key', - 'json_field__value_type', 'json_field__name', - ).order_by('order') + "label", + "help_text", + "order", + "json_field__key", + "json_field__value_type", + "json_field__name", + ).order_by("order") for field in q.all(): - key = "data__" + field['json_field__key'] + key = "data__" + field["json_field__key"] field_cls, widget = forms.CharField, None - if field['json_field__value_type'] in JSON_VALUE_TYPES_FIELDS: + if field["json_field__value_type"] in JSON_VALUE_TYPES_FIELDS: field_cls, widget = JSON_VALUE_TYPES_FIELDS[ - field['json_field__value_type']] - if is_search and field['json_field__value_type'] == "LT": + field["json_field__value_type"] + ] + if is_search and field["json_field__value_type"] == "LT": widget = None - attrs = {'label': field['label'] or field['json_field__name'], - 'required': False} - if field['help_text']: - attrs['help_text'] = field['help_text'] + attrs = { + "label": field["label"] or field["json_field__name"], + "required": False, + } + if field["help_text"]: + attrs["help_text"] = field["help_text"] if widget: - attrs['widget'] = widget() + attrs["widget"] = widget() if field_cls == widgets.Select2DynamicField: - attrs['choices'] = cls._get_dynamic_choices(key) + attrs["choices"] = cls._get_dynamic_choices(key) f = field_cls(**attrs) - kls = 'form-control' - if 'class' in f.widget.attrs: - kls = f.widget.attrs['class'] + " " + kls - f.widget.attrs['class'] = kls - f.alt_name = slugify(attrs['label']) + kls = "form-control" + if "class" in f.widget.attrs: + kls = f.widget.attrs["class"] + " " + kls + f.widget.attrs["class"] = kls + f.alt_name = slugify(attrs["label"]) fields.append((field["order"] or 1, key, f)) return fields @@ -314,26 +324,25 @@ class CustomForm(BSForm): """ if not current_user: return True, [], [] - base_q = {"form": cls.form_slug, 'available': True} + base_q = {"form": cls.form_slug, "available": True} # order is important : try for user, profile type, user type then all query_dicts = [] if current_user: dct = base_q.copy() - dct.update({'users__pk': current_user.pk}) + dct.update({"users__pk": current_user.pk}) query_dicts = [dct] if current_user.current_profile: dct = base_q.copy() pt = current_user.current_profile.profile_type.pk - dct.update( - {'profile_types__pk': pt}) + dct.update({"profile_types__pk": pt}) query_dicts.append(dct) for user_type in current_user.person.person_types.all(): dct = base_q.copy() - dct.update({'user_types__pk': user_type.pk}), + dct.update({"user_types__pk": user_type.pk}), query_dicts.append(dct) dct = base_q.copy() - dct.update({'apply_to_all': True}) + dct.update({"apply_to_all": True}) query_dicts.append(dct) form = None for query_dict in query_dicts: @@ -360,7 +369,7 @@ class CustomForm(BSForm): Get fields than can be customized: excluded, re-ordered (WIP) or re-labeled (WIP) """ - if hasattr(cls, 'base_fields'): + if hasattr(cls, "base_fields"): fields = cls.base_fields else: # formset @@ -371,9 +380,12 @@ class CustomForm(BSForm): field = fields[key] # cannot customize display of required (except in search form) and # hidden field, search_vector and field with no label - if ('search_vector' not in keys and field.required) or \ - key == 'search_vector' or field.widget.is_hidden or \ - not field.label: + if ( + ("search_vector" not in keys and field.required) + or key == "search_vector" + or field.widget.is_hidden + or not field.label + ): continue customs.append((key, field.label)) return sorted(customs, key=lambda x: x[1]) @@ -384,12 +396,12 @@ class CustomFormSearch(forms.Form): def __init__(self, *args, **kwargs): user = None - if 'user' in kwargs: - user = kwargs.pop('user') + if "user" in kwargs: + user = kwargs.pop("user") super(CustomFormSearch, self).__init__(*args, **kwargs) self.request_user = user - if user and 'pk' in self.fields: - self.fields['pk'].widget.user = user + if user and "pk" in self.fields: + self.fields["pk"].widget.user = user class LockForm(object): @@ -416,25 +428,25 @@ class LockForm(object): except model.DoesNotExist: raise forms.ValidationError(_("Invalid selection.")) if item.is_locked(self.request_user): - raise forms.ValidationError(_("This item is locked " - "for edition.")) + raise forms.ValidationError(_("This item is locked " "for edition.")) return self.cleaned_data class MultiSearchForm(CustomFormSearch): SEARCH_AND_SELECT = True - pk_key = 'pks' + pk_key = "pks" associated_models = {} def __init__(self, *args, **kwargs): super(MultiSearchForm, self).__init__(*args, **kwargs) if "pk" not in self.fields: - raise NotImplementedError("A \"pk\" field must be defined") + raise NotImplementedError('A "pk" field must be defined') if self.pk_key not in self.associated_models: - raise NotImplementedError("\"{}\" must be defined in " - "associated_models".format(self.pk_key)) - self.fields['pk'].required = True - self.fields[self.pk_key] = self.fields.pop('pk') + raise NotImplementedError( + '"{}" must be defined in ' "associated_models".format(self.pk_key) + ) + self.fields["pk"].required = True + self.fields[self.pk_key] = self.fields.pop("pk") @classmethod def get_current_model(cls): @@ -453,21 +465,20 @@ class MultiSearchForm(CustomFormSearch): if not data or cls.pk_key not in data or not data[cls.pk_key]: continue pks = data[cls.pk_key] - for pk in str(pks).split(','): + for pk in str(pks).split(","): if not pk: continue try: - items.append( - str(current_model.objects.get(pk=int(pk))) - ) + items.append(str(current_model.objects.get(pk=int(pk)))) except (current_model.DoesNotExist, ValueError): continue return [ - ("", - mark_safe( - "<ul class='compact'><li>" + "</li><li>".join(items) + - "</li></ul>" - )) + ( + "", + mark_safe( + "<ul class='compact'><li>" + "</li><li>".join(items) + "</li></ul>" + ), + ) ] @@ -476,17 +487,18 @@ class FormSet(CustomForm, BaseFormSet): def __init__(self, *args, **kwargs): self.readonly = False - if 'readonly' in kwargs: - self.readonly = kwargs.pop('readonly') + if "readonly" in kwargs: + self.readonly = kwargs.pop("readonly") self.can_delete = False # no extra fields - if 'data' in kwargs: + if "data" in kwargs: prefix = "" if "prefix" in kwargs: - prefix = kwargs['prefix'] - if prefix + '-INITIAL_FORMS' in kwargs['data']: - kwargs['data'][prefix + '-TOTAL_FORMS'] = \ - kwargs["data"][prefix + '-INITIAL_FORMS'] + prefix = kwargs["prefix"] + if prefix + "-INITIAL_FORMS" in kwargs["data"]: + kwargs["data"][prefix + "-TOTAL_FORMS"] = kwargs["data"][ + prefix + "-INITIAL_FORMS" + ] super(FormSet, self).__init__(*args, **kwargs) def check_duplicate(self, key_names, error_msg="", check_null=False): @@ -500,9 +512,10 @@ class FormSet(CustomForm, BaseFormSet): form = self.forms[i] if not form.is_valid(): continue - item = [key_name in form.cleaned_data and - form.cleaned_data[key_name] - for key_name in key_names] + item = [ + key_name in form.cleaned_data and form.cleaned_data[key_name] + for key_name in key_names + ] if not check_null and not [v for v in item if v]: continue if item in items: @@ -514,13 +527,13 @@ class FormSet(CustomForm, BaseFormSet): if self.readonly: for k in form.fields: # django 1.9: use disabled - form.fields[k].widget.attrs['readonly'] = True + form.fields[k].widget.attrs["readonly"] = True clean = get_readonly_clean(k) - clean.__name__ = 'clean_' + k - clean.__doc__ = 'autogenerated: clean_' + k + clean.__name__ = "clean_" + k + clean.__doc__ = "autogenerated: clean_" + k setattr(form, clean.__name__, types.MethodType(clean, form)) if self.can_delete: - form.fields[DELETION_FIELD_NAME].label = '' + form.fields[DELETION_FIELD_NAME].label = "" form.fields[DELETION_FIELD_NAME].widget = self.delete_widget() def _should_delete_form(self, form): @@ -531,10 +544,12 @@ class FormSet(CustomForm, BaseFormSet): if form.cleaned_data.get(DELETION_FIELD_NAME, False): return True if not form.cleaned_data or not [ - key for key in form.cleaned_data - if key != DELETION_FIELD_NAME and - form.cleaned_data[key] is not None and - form.cleaned_data[key] != '']: + key + for key in form.cleaned_data + if key != DELETION_FIELD_NAME + and form.cleaned_data[key] is not None + and form.cleaned_data[key] != "" + ]: form.cleaned_data[DELETION_FIELD_NAME] = True return True return False @@ -552,10 +567,7 @@ class FieldType(object): self.extra_args = extra_args def get_choices(self, initial=None): - args = { - 'empty_first': not self.is_multiple, - 'initial': initial - } + args = {"empty_first": not self.is_multiple, "initial": initial} if self.extra_args: args.update(self.extra_args) return self.model.get_types(**args) @@ -579,12 +591,14 @@ class FormHeader(object): if self.help_message: help_message = """ <div class="alert alert-info" role="alert">{}</div>""".format( - self.help_message) + self.help_message + ) if not self.collapse: return mark_safe( "<h{level}>{label}</h{level}>{help_message}".format( - label=self.label, level=self.level, - help_message=help_message)) + label=self.label, level=self.level, help_message=help_message + ) + ) html = """<div id="collapse-parent-{slug}" class="collapse-form"> <div class="card"> <div class="card-header" id="collapse-head-{slug}"> @@ -604,30 +618,36 @@ class FormHeader(object): data-parent="#colapse-parent-{slug}"> <div class="card-body"> {help_message} -""".format(label=self.label, slug=slugify(self.label), level=self.level, - help_message=help_message) +""".format( + label=self.label, + slug=slugify(self.label), + level=self.level, + help_message=help_message, + ) return mark_safe(html) def render_end(self): if not self.collapse: return "" - return mark_safe(""" + return mark_safe( + """ </div> </div> </div> - </div>""") + </div>""" + ) class IshtarForm(forms.Form, BSForm): TYPES = [] # FieldType list CONDITIONAL_FIELDS = [] # dynamic conditions on field display - # can be dynamic with "get_conditional_fields" + # can be dynamic with "get_conditional_fields" PROFILE_FILTER = {} # profile key associated to field list HEADERS = {} # field key associated to FormHeader instance # permission check for widget options, ex: forms_common.DocumentForm OPTIONS_PERMISSIONS = {} SITE_KEYS = {} # archaeological sites fields and associated translation key - # to manage translation + # to manage translation def __init__(self, *args, **kwargs): super(IshtarForm, self).__init__(*args, **kwargs) @@ -641,7 +661,7 @@ class IshtarForm(forms.Form, BSForm): for field_key in self.PROFILE_FILTER[profile_key]: if field_key in self.fields.keys(): self.fields.pop(field_key) - if getattr(self, 'confirm', False): + if getattr(self, "confirm", False): return for field in self.TYPES: self._init_type(field) @@ -655,8 +675,9 @@ class IshtarForm(forms.Form, BSForm): for field_name, permissions, options in self.OPTIONS_PERMISSIONS: if field_name not in self.fields or not getattr(self, "user", None): continue - if not [True for permission in permissions - if self.user.has_perm(permission)]: + if not [ + True for permission in permissions if self.user.has_perm(permission) + ]: continue for option, value in options.items(): setattr(self.fields[field_name].widget, option, value) @@ -679,8 +700,7 @@ class IshtarForm(forms.Form, BSForm): return self.current_header def extra_render(self): - return (self.get_conditional() or "") + ( - self.get_conditional_filters() or "") + return (self.get_conditional() or "") + (self.get_conditional_filters() or "") HIDE_JS_TEMPLATE = """ var %(id)s_item_show_list = ['%(item_list)s']; @@ -712,7 +732,7 @@ class IshtarForm(forms.Form, BSForm): def get_conditional(self): conditional_fields = self.CONDITIONAL_FIELDS - if hasattr(self, 'get_conditional_fields'): + if hasattr(self, "get_conditional_fields"): conditional_fields = self.get_conditional_fields() if not conditional_fields or not self.TYPES: return @@ -725,17 +745,18 @@ class IshtarForm(forms.Form, BSForm): continue model = type_dict[condition_field] condition_ids = [ - str(item.pk) for item in model.objects.filter( - **{condition_attr: condition_val}).all() + str(item.pk) + for item in model.objects.filter( + **{condition_attr: condition_val} + ).all() ] name = self.prefix + "-" + condition_field - target_names = [ - self.prefix + "-" + name for name in target_names - ] + target_names = [self.prefix + "-" + name for name in target_names] if not condition_ids: - html += self.HIDE_JS_TEMPLATE % { + html += self.HIDE_JS_TEMPLATE % { "item_list": "','".join(target_names), - "id": name.replace("-", "_")} + "id": name.replace("-", "_"), + } continue html += self.CONDITIONAL_JS_TEMPLATE % { "id": name.replace("-", "_"), @@ -781,10 +802,13 @@ class IshtarForm(forms.Form, BSForm): """ def get_conditional_filters(self): - if not hasattr(self, 'get_conditional_filter_fields'): + if not hasattr(self, "get_conditional_filter_fields"): return - conditional_fields, excluded_fields, all_values = \ - self.get_conditional_filter_fields() + ( + conditional_fields, + excluded_fields, + all_values, + ) = self.get_conditional_filter_fields() types = [typ.key for typ in self.TYPES] html = "" @@ -802,8 +826,7 @@ class IshtarForm(forms.Form, BSForm): if idx: filter_list += ",\n" filter_list += ' "%s": {\n' % input_pk - for idx2, output in enumerate( - conditional_fields[input_key][input_pk]): + for idx2, output in enumerate(conditional_fields[input_key][input_pk]): if idx2: filter_list += ",\n" if output[0] in excluded_fields: @@ -816,7 +839,7 @@ class IshtarForm(forms.Form, BSForm): "id": cidx, "name": name, "filter_list": filter_list, - "prefix": self.prefix or "" + "prefix": self.prefix or "", } html += "var %s_other_widget_list = [" % cidx for idx, k in enumerate(all_values): @@ -848,7 +871,7 @@ class TableSelect(IshtarForm): def __init__(self, *args, **kwargs): super(TableSelect, self).__init__(*args, **kwargs) alt_names = {} - if hasattr(self, '_model'): + if hasattr(self, "_model"): if hasattr(self._model, "get_alt_names"): alt_names = self._model.get_alt_names() @@ -860,14 +883,13 @@ class TableSelect(IshtarForm): for k in self.fields: self.fields[k].required = False # no field is required for search - cls = 'form-control' - if k == 'search_vector': + cls = "form-control" + if k == "search_vector": cls += " search-vector" - self.fields[k].widget.attrs['class'] = cls - self.fields[k].alt_name = alt_names[k].search_key \ - if k in alt_names else k + self.fields[k].widget.attrs["class"] = cls + self.fields[k].alt_name = alt_names[k].search_key if k in alt_names else k key = list(self.fields.keys())[0] - self.fields[key].widget.attrs['autofocus'] = 'autofocus' + self.fields[key].widget.attrs["autofocus"] = "autofocus" def get_input_ids(self): return list(self.fields.keys()) @@ -877,24 +899,30 @@ class HistorySelect(CustomForm, TableSelect): history_creator = forms.IntegerField( label=_("Created by"), widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-user'), - associated_model=User), required=False + reverse_lazy("autocomplete-user"), associated_model=User + ), + required=False, ) history_modifier = forms.IntegerField( label=_("Last modified by"), widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-user'), - associated_model=User), required=False + reverse_lazy("autocomplete-user"), associated_model=User + ), + required=False, ) modified_before = forms.DateField( - label=_("Modified before"), widget=DatePicker, - required=False) + label=_("Modified before"), widget=DatePicker, required=False + ) modified_after = forms.DateField( - label=_("Modified after"), widget=DatePicker, - required=False) + label=_("Modified after"), widget=DatePicker, required=False + ) _explicit_ordering = True - CURRENT_FIELDS = ["history_creator", "history_modifier", - "modified_before", "modified_after"] + CURRENT_FIELDS = [ + "history_creator", + "history_modifier", + "modified_before", + "modified_after", + ] def __init__(self, *args, **kwargs): super(HistorySelect, self).__init__(*args, **kwargs) @@ -913,21 +941,25 @@ class HistorySelect(CustomForm, TableSelect): class DocumentItemSelect(HistorySelect): documents__image__isnull = forms.NullBooleanField(label=_("Has an image?")) documents__associated_file__isnull = forms.NullBooleanField( - label=_("Has an attached file?")) + label=_("Has an attached file?") + ) documents__associated_url__isnull = forms.NullBooleanField( - label=_("Has a web address?")) + label=_("Has a web address?") + ) CURRENT_FIELDS = [ - 'documents__image__isnull', - 'documents__associated_file__isnull', - 'documents__associated_url__isnull', - "history_creator", "history_modifier", - "modified_before", "modified_after" + "documents__image__isnull", + "documents__associated_file__isnull", + "documents__associated_url__isnull", + "history_creator", + "history_modifier", + "modified_before", + "modified_after", ] _explicit_ordering = True def get_now(): - format = formats.get_format('DATE_INPUT_FORMATS')[0] + format = formats.get_format("DATE_INPUT_FORMATS")[0] return datetime.datetime.now().strftime(format) @@ -936,10 +968,10 @@ class ClosingDateFormSelection(IshtarForm): end_date = DateField(label=_("Closing date")) def __init__(self, *args, **kwargs): - if 'initial' not in kwargs: - kwargs['initial'] = {} - if not kwargs['initial'].get('end_date', None): - kwargs['initial']['end_date'] = datetime.date.today() + if "initial" not in kwargs: + kwargs["initial"] = {} + if not kwargs["initial"].get("end_date", None): + kwargs["initial"]["end_date"] = datetime.date.today() super(ClosingDateFormSelection, self).__init__(*args, **kwargs) @@ -948,12 +980,22 @@ def has_map(): def get_form_selection( - class_name, label, key, model, base_form, get_url, - not_selected_error=_("You should select an item."), new=False, - new_message=_("Add a new item"), get_full_url=None, - gallery=False, map=False, multi=False, base_form_select=None, - alt_pk_field=None - ): + class_name, + label, + key, + model, + base_form, + get_url, + not_selected_error=_("You should select an item."), + new=False, + new_message=_("Add a new item"), + get_full_url=None, + gallery=False, + map=False, + multi=False, + base_form_select=None, + alt_pk_field=None, +): """ Generate a class selection form class_name -- name of the class @@ -967,20 +1009,22 @@ def get_form_selection( gallery -- display a gallery map -- display a map """ - attrs = {'_main_key': key, - '_not_selected_error': not_selected_error, - 'form_label': label, - 'associated_models': {key: model}, - 'currents': {key: model}} + attrs = { + "_main_key": key, + "_not_selected_error": not_selected_error, + "form_label": label, + "associated_models": {key: model}, + "currents": {key: model}, + } widget_kwargs = {"new": new, "new_message": new_message} if get_full_url: - widget_kwargs['source_full'] = reverse_lazy(get_full_url) + widget_kwargs["source_full"] = reverse_lazy(get_full_url) if gallery: - widget_kwargs['gallery'] = True + widget_kwargs["gallery"] = True if map: - widget_kwargs['map'] = models.profile_mapping() + widget_kwargs["map"] = models.profile_mapping() if multi: - widget_kwargs['multiple_select'] = True + widget_kwargs["multiple_select"] = True field = forms.CharField valid = models.valid_ids else: @@ -990,12 +1034,15 @@ def get_form_selection( if alt_pk_field: key = alt_pk_field attrs[key] = field( - label="", required=False, + label="", + required=False, validators=[valid(model)], - widget=widgets.DataTable(reverse_lazy(get_url), base_form, model, - **widget_kwargs)) + widget=widgets.DataTable( + reverse_lazy(get_url), base_form, model, **widget_kwargs + ), + ) - attrs['SEARCH_AND_SELECT'] = True + attrs["SEARCH_AND_SELECT"] = True if not base_form_select: base_form_select = forms.Form if not isinstance(base_form_select, (tuple, list)): @@ -1012,7 +1059,7 @@ def get_data_from_formset(data): for k in data: if not data[k]: continue - keys = k.split('-') + keys = k.split("-") if len(keys) < 3: continue try: @@ -1032,30 +1079,30 @@ class ManageOldType(IshtarForm): init_data is used to manage deactivated items in list when editing old data """ - prefix = kwargs.get('prefix') or '' + prefix = kwargs.get("prefix") or "" self.init_data = {} - if 'data' in kwargs and kwargs['data']: - for k in kwargs['data']: + if "data" in kwargs and kwargs["data"]: + for k in kwargs["data"]: if prefix not in k: continue - new_k = k[len(prefix) + 1:] - if hasattr(kwargs['data'], 'getlist'): - items = kwargs['data'].getlist(k) + new_k = k[len(prefix) + 1 :] + if hasattr(kwargs["data"], "getlist"): + items = kwargs["data"].getlist(k) else: - items = [kwargs['data'][k]] + items = [kwargs["data"][k]] for val in items: if not val: continue if new_k not in self.init_data: self.init_data[new_k] = [] self.init_data[new_k].append(val) - if 'initial' in kwargs and kwargs['initial']: - for k in kwargs['initial']: + if "initial" in kwargs and kwargs["initial"]: + for k in kwargs["initial"]: if k not in self.init_data or not self.init_data[k]: - if hasattr(kwargs['initial'], 'getlist'): - items = kwargs['initial'].getlist(k) + if hasattr(kwargs["initial"], "getlist"): + items = kwargs["initial"].getlist(k) else: - items = [kwargs['initial'][k]] + items = [kwargs["initial"][k]] for val in items: if not val: continue @@ -1071,7 +1118,8 @@ class ManageOldType(IshtarForm): if field.key not in self.fields: return self.fields[field.key].choices = field.get_choices( - initial=self.init_data.get(field.key)) + initial=self.init_data.get(field.key) + ) self.fields[field.key].help_text = field.get_help() @@ -1088,33 +1136,31 @@ class QAForm(CustomForm, ManageOldType): ) def __init__(self, *args, **kwargs): - self.items = kwargs.pop('items') - self.confirm = kwargs.pop('confirm') + self.items = kwargs.pop("items") + self.confirm = kwargs.pop("confirm") super(QAForm, self).__init__(*args, **kwargs) for k in list(self.fields.keys()): if self.MULTI and k in self.SINGLE_FIELDS: self.fields.pop(k) continue if self.confirm: - if 'data' not in kwargs or not kwargs['data'].get(k, None): + if "data" not in kwargs or not kwargs["data"].get(k, None): self.fields.pop(k) continue - if getattr(self.fields[k].widget, 'allow_multiple_selected', - None): + if getattr(self.fields[k].widget, "allow_multiple_selected", None): self.fields[k].widget = forms.MultipleHiddenInput() else: self.fields[k].widget = forms.HiddenInput() - if k in kwargs['data'] and kwargs['data'][k]: + if k in kwargs["data"] and kwargs["data"][k]: if hasattr(self, "_get_" + k): - value = getattr( - self, "_get_" + k)(kwargs['data'][k]) + value = getattr(self, "_get_" + k)(kwargs["data"][k]) if value is None: self.fields.pop(k) continue self.fields[k].rendered_value = value elif hasattr(self.fields[k], "choices"): values = [] - for v in kwargs['data'].getlist(k): + for v in kwargs["data"].getlist(k): dct_choices = {} for key, value in self.fields[k].choices: if isinstance(value, (list, tuple)): @@ -1125,22 +1171,21 @@ class QAForm(CustomForm, ManageOldType): values.append(str(dct_choices[v])) elif int(v) in list(dct_choices.keys()): values.append(str(dct_choices[int(v)])) - self.fields[k].rendered_value = mark_safe( - " ; ".join(values)) + self.fields[k].rendered_value = mark_safe(" ; ".join(values)) if k not in self.REPLACE_FIELDS: - self.fields[k].label = str(self.fields[k].label) + \ - str(_(" - append to existing")) + self.fields[k].label = str(self.fields[k].label) + str( + _(" - append to existing") + ) else: - self.fields[k].label = str(self.fields[k].label) + \ - str(_(" - replace")) + self.fields[k].label = str(self.fields[k].label) + str(_(" - replace")) def _set_value(self, item, base_key): value = self.cleaned_data[base_key] if not value: return - key = base_key[len(self.PREFIX):] + key = base_key[len(self.PREFIX) :] field = item._meta.get_field(key) - if getattr(field, 'related_model', None): + if getattr(field, "related_model", None): is_list = isinstance(value, (list, tuple)) if not is_list: value = [value] @@ -1150,7 +1195,7 @@ class QAForm(CustomForm, ManageOldType): v = field.related_model.objects.get(pk=v) new_value.append(v) value = new_value if is_list else new_value[0] - if getattr(field, 'many_to_many', None): + if getattr(field, "many_to_many", None): if type(value) not in (list, tuple): value = [value] for v in value: @@ -1184,8 +1229,8 @@ class QAForm(CustomForm, ManageOldType): def save(self, items, user): for item in items: for base_key in self.cleaned_data: - if hasattr(self, '_set_' + base_key): - getattr(self, '_set_' + base_key)(item, user) + if hasattr(self, "_set_" + base_key): + getattr(self, "_set_" + base_key)(item, user) else: self._set_value(item, base_key) item.history_modifier = user @@ -1197,16 +1242,17 @@ class DocumentGenerationForm(forms.Form): """ Form to generate document by choosing the template """ + _associated_model = None # ex: AdministrativeAct # ex: 'archaeological_operations.models.AdministrativeAct' - _associated_object_name = '' + _associated_object_name = "" document_template = forms.ChoiceField(label=_("Template"), choices=[]) def __init__(self, *args, **kwargs): super(DocumentGenerationForm, self).__init__(*args, **kwargs) - self.fields['document_template'].choices = \ - models.DocumentTemplate.get_tuples( - dct={'associated_model__klass': self._associated_object_name}) + self.fields["document_template"].choices = models.DocumentTemplate.get_tuples( + dct={"associated_model__klass": self._associated_object_name} + ) def save(self, object_pk): try: @@ -1215,7 +1261,8 @@ class DocumentGenerationForm(forms.Form): return try: template = models.DocumentTemplate.objects.get( - pk=self.cleaned_data.get('document_template')) + pk=self.cleaned_data.get("document_template") + ) except models.DocumentTemplate.DoesNotExist: return return template.publish(c_object) diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py index 0be35b2b2..dd3f33a74 100644 --- a/ishtar_common/forms_common.py +++ b/ishtar_common/forms_common.py @@ -47,14 +47,29 @@ from . import models from . import widgets from bootstrap_datepicker.widgets import DatePicker from ishtar_common.templatetags.link_to_window import simple_link_to_window -from .forms import FinalForm, FormSet, reverse_lazy, name_validator, \ - TableSelect, ManageOldType, CustomForm, FieldType, FormHeader, \ - FormSetWithDeleteSwitches, BSForm, get_data_from_formset, \ - file_size_validator, HistorySelect, CustomFormSearch, QAForm, IshtarForm, \ - MultiSearchForm, LockForm +from .forms import ( + FinalForm, + FormSet, + reverse_lazy, + name_validator, + TableSelect, + ManageOldType, + CustomForm, + FieldType, + FormHeader, + FormSetWithDeleteSwitches, + BSForm, + get_data_from_formset, + file_size_validator, + HistorySelect, + CustomFormSearch, + QAForm, + IshtarForm, + MultiSearchForm, + LockForm, +) from ishtar_common.data_importer import ImporterError -from ishtar_common.utils import is_downloadable, clean_session_cache, \ - max_size_help +from ishtar_common.utils import is_downloadable, clean_session_cache, max_size_help from archaeological_operations.models import Operation from archaeological_context_records.models import ContextRecord @@ -70,53 +85,65 @@ def get_town_field(label=_("Town"), required=True): "the department code is generally sufficient to get the appropriate " "result.</p>\n<p class='example'>For instance type \"saint denis 93\"" " for getting the french town Saint-Denis in the Seine-Saint-Denis " - "department.</p>") + "department.</p>" + ) # !FIXME hard_link, reverse_lazy doen't seem to work with formsets return forms.IntegerField( widget=widgets.JQueryAutoComplete( - "/" + settings.URL_PATH + 'autocomplete-town', - associated_model=models.Town), - validators=[models.valid_id(models.Town)], label=label, - help_text=mark_safe(help_text), required=required) + "/" + settings.URL_PATH + "autocomplete-town", associated_model=models.Town + ), + validators=[models.valid_id(models.Town)], + label=label, + help_text=mark_safe(help_text), + required=required, + ) def get_advanced_town_field(label=_("Town"), required=True): # !FIXME hard_link, reverse_lazy doen't seem to work with formsets return forms.IntegerField( widget=widgets.JQueryTown( - "/" + settings.URL_PATH + 'autocomplete-advanced-town'), - validators=[models.valid_id(models.Town)], label=label, - required=required) + "/" + settings.URL_PATH + "autocomplete-advanced-town" + ), + validators=[models.valid_id(models.Town)], + label=label, + required=required, + ) def get_person_field(label=_("Person"), required=True, person_types=[]): # !FIXME hard_link, reverse_lazy doen't seem to work with formsets widget = None - url = "/" + settings.URL_PATH + 'autocomplete-person' + url = "/" + settings.URL_PATH + "autocomplete-person" if person_types: person_types = [ str(models.PersonType.objects.get(txt_idx=person_type).pk) - for person_type in person_types] - url += "/" + '_'.join(person_types) + for person_type in person_types + ] + url += "/" + "_".join(person_types) widget = widgets.JQueryAutoComplete(url, associated_model=models.Person) - return forms.IntegerField(widget=widget, label=label, required=required, - validators=[models.valid_id(models.Person)]) + return forms.IntegerField( + widget=widget, + label=label, + required=required, + validators=[models.valid_id(models.Person)], + ) class NewItemForm(forms.Form): def __init__(self, *args, **kwargs): self.limits = {} - if 'limits' in kwargs: - limits = kwargs.pop('limits') + if "limits" in kwargs: + limits = kwargs.pop("limits") if limits: - for item in limits.split(';'): - key, values = item.split('__') - self.limits[key] = values.split('-') + for item in limits.split(";"): + key, values = item.split("__") + self.limits[key] = values.split("-") super(NewItemForm, self).__init__(*args, **kwargs) def limit_fields(self): for key in self.limits: - if key in self.fields and hasattr(self.fields[key], 'choices'): + if key in self.fields and hasattr(self.fields[key], "choices"): new_choices = [ (value, lbl) for value, lbl in self.fields[key].choices @@ -129,70 +156,87 @@ class NewItemForm(forms.Form): class NewImportForm(BSForm, forms.ModelForm): - error_css_class = 'error' - required_css_class = 'required' + error_css_class = "error" + required_css_class = "required" imported_images_link = forms.URLField( - label=_("Associated images (web link to a zip file)"), - required=False + label=_("Associated images (web link to a zip file)"), required=False ) class Meta: model = models.Import fields = ( - 'name', 'importer_type', 'imported_file', 'encoding', - 'csv_sep', 'imported_images', 'imported_images_link', - 'associated_group', 'conservative_import', 'skip_lines' + "name", + "importer_type", + "imported_file", + "encoding", + "csv_sep", + "imported_images", + "imported_images_link", + "associated_group", + "conservative_import", + "skip_lines", ) def __init__(self, *args, **kwargs): - user = kwargs.pop('user') + user = kwargs.pop("user") super(NewImportForm, self).__init__(*args, **kwargs) groups = models.TargetKeyGroup.objects.filter(available=True) if not user.is_superuser: groups = groups.filter(all_user_can_use=True) if not groups.count(): - self.fields.pop('associated_group') + self.fields.pop("associated_group") else: - self.fields['associated_group'].choices = [(None, '--')] + \ - [(g.pk, str(g)) for g in groups.all()] - self.fields['importer_type'].choices = [('', '--')] + [ - (imp.pk, imp.name) for imp in models.ImporterType.objects.filter( - available=True - ) + self.fields["associated_group"].choices = [(None, "--")] + [ + (g.pk, str(g)) for g in groups.all() + ] + self.fields["importer_type"].choices = [("", "--")] + [ + (imp.pk, imp.name) + for imp in models.ImporterType.objects.filter(available=True) ] - self.fields['imported_file'].validators = [file_size_validator] - self.fields['imported_images'].validators = [file_size_validator] + self.fields["imported_file"].validators = [file_size_validator] + self.fields["imported_images"].validators = [file_size_validator] self._post_init() def clean(self): data = self.cleaned_data - if data.get('conservative_import', None) \ - and data.get('importer_type') \ - and not data.get('importer_type').unicity_keys: + if ( + data.get("conservative_import", None) + and data.get("importer_type") + and not data.get("importer_type").unicity_keys + ): raise forms.ValidationError( - _("This import type have no unicity type defined. " - "Conservative import is not possible.")) - if data.get('imported_images_link', None) \ - and data.get('imported_images', None): + _( + "This import type have no unicity type defined. " + "Conservative import is not possible." + ) + ) + if data.get("imported_images_link", None) and data.get("imported_images", None): raise forms.ValidationError( - _("You put either a file or a download link for images " - "but not both.")) + _( + "You put either a file or a download link for images " + "but not both." + ) + ) return data def clean_imported_images_link(self): - value = self.cleaned_data.get('imported_images_link', None) + value = self.cleaned_data.get("imported_images_link", None) if value: try: assert is_downloadable(value) except (AssertionError, requests.exceptions.RequestException): raise forms.ValidationError( - _("Invalid link or no file is available for this link.")) + _("Invalid link or no file is available for this link.") + ) return value def save(self, user, commit=True): self.instance.user = user - imported_images_link = self.cleaned_data.pop('imported_images_link') \ - if 'imported_images_link' in self.cleaned_data else None + imported_images_link = ( + self.cleaned_data.pop("imported_images_link") + if "imported_images_link" in self.cleaned_data + else None + ) item = super(NewImportForm, self).save(commit) if not imported_images_link: return item @@ -202,7 +246,7 @@ class NewImportForm(BSForm, forms.ModelForm): if not block: break ntf.write(block) - file_name = imported_images_link.split('/')[-1] + file_name = imported_images_link.split("/")[-1] item.imported_images.save(file_name, File(ntf)) return item @@ -210,103 +254,114 @@ class NewImportForm(BSForm, forms.ModelForm): class TargetKeyForm(forms.ModelForm): class Meta: model = models.TargetKey - fields = ('target', 'key', 'value') + fields = ("target", "key", "value") widgets = { - 'key': forms.TextInput(attrs={'readonly': 'readonly'}), + "key": forms.TextInput(attrs={"readonly": "readonly"}), } - target = widgets.SelectReadonlyField( - model=models.ImportTarget, label=_("Target")) - value = widgets.Select2SimpleField( - label=_("Value"), required=False - ) - remember = forms.ChoiceField(label=_("Remember"), choices=[], - required=False) - NULL_VALUE = '<NONE>' + + target = widgets.SelectReadonlyField(model=models.ImportTarget, label=_("Target")) + value = widgets.Select2SimpleField(label=_("Value"), required=False) + remember = forms.ChoiceField(label=_("Remember"), choices=[], required=False) + NULL_VALUE = "<NONE>" def __init__(self, *args, **kwargs): - self.user = kwargs.pop('user') + self.user = kwargs.pop("user") super(TargetKeyForm, self).__init__(*args, **kwargs) - instance = getattr(self, 'instance', None) + instance = getattr(self, "instance", None) self.associated_import = None if instance and instance.pk: model = instance.target.associated_model - if model and \ - self.user.has_perm('{}.change_{}'.format( - model._meta.app_label, model._meta.model_name)) and \ - hasattr(model, 'admin_url'): + if ( + model + and self.user.has_perm( + "{}.change_{}".format(model._meta.app_label, model._meta.model_name) + ) + and hasattr(model, "admin_url") + ): self.admin_url = instance.target.associated_model.admin_url() self.associated_import = instance.associated_import - self.fields['target'].choices = [(instance.target.pk, - instance.target.verbose_name)] - self.fields['key'].widget.attrs['readonly'] = True - self.fields['key'].widget.attrs['title'] = str(instance) - self.fields['value'].choices = list( - instance.target.get_choices()) - self.fields['value'].choices.insert( - 1, (self.NULL_VALUE, _("Set to NULL"))) - - self.fields['key'].required = False - self.fields['target'].required = False - self.fields['value'].required = False + self.fields["target"].choices = [ + (instance.target.pk, instance.target.verbose_name) + ] + self.fields["key"].widget.attrs["readonly"] = True + self.fields["key"].widget.attrs["title"] = str(instance) + self.fields["value"].choices = list(instance.target.get_choices()) + self.fields["value"].choices.insert(1, (self.NULL_VALUE, _("Set to NULL"))) + + self.fields["key"].required = False + self.fields["target"].required = False + self.fields["value"].required = False choices = [ - ('import', _("this import only")), - ('me', _("me")), + ("import", _("this import only")), + ("me", _("me")), ] - if self.associated_import and self.associated_import.associated_group \ - and (self.associated_import.associated_group.all_user_can_modify - or self.user.is_superuser): + if ( + self.associated_import + and self.associated_import.associated_group + and ( + self.associated_import.associated_group.all_user_can_modify + or self.user.is_superuser + ) + ): choices += [ - ('group', str(_("the current group: {}")).format( - self.associated_import.associated_group))] + ( + "group", + str(_("the current group: {}")).format( + self.associated_import.associated_group + ), + ) + ] if self.user.is_superuser: - choices += [('all', _("all users"))] - self.fields['remember'].choices = choices - self.fields['remember'].widget.attrs['class'] = 'auto' + choices += [("all", _("all users"))] + self.fields["remember"].choices = choices + self.fields["remember"].widget.attrs["class"] = "auto" self.remember_choices = choices def clean_target(self): - instance = getattr(self, 'instance', None) + instance = getattr(self, "instance", None) if instance and instance.pk: return instance.target else: - return self.cleaned_data['target'] + return self.cleaned_data["target"] def clean_key(self): - instance = getattr(self, 'instance', None) + instance = getattr(self, "instance", None) if instance and instance.pk: return instance.key else: - return self.cleaned_data['key'] + return self.cleaned_data["key"] def save(self, commit=True): try: super(TargetKeyForm, self).save(commit) except ImporterError: return - if not self.cleaned_data.get('value') or not self.user: + if not self.cleaned_data.get("value") or not self.user: return - if self.cleaned_data['value'] == self.NULL_VALUE: + if self.cleaned_data["value"] == self.NULL_VALUE: self.instance.value = None self.instance.is_set = True - can_edit_group = \ - self.associated_import and \ - self.associated_import.associated_group and \ - (self.associated_import.associated_group.all_user_can_modify - or self.user.is_superuser) + can_edit_group = ( + self.associated_import + and self.associated_import.associated_group + and ( + self.associated_import.associated_group.all_user_can_modify + or self.user.is_superuser + ) + ) - remember = self.cleaned_data.get('remember') - if remember == 'import' and self.associated_import: + remember = self.cleaned_data.get("remember") + if remember == "import" and self.associated_import: self.instance.associated_import = self.associated_import self.instance.associated_user = None self.instance.associated_group = None - elif remember == 'group' and can_edit_group: + elif remember == "group" and can_edit_group: self.instance.associated_import = None self.instance.associated_user = None - self.instance.associated_group = \ - self.associated_import.associated_group - elif remember == 'all' and self.user.is_superuser: + self.instance.associated_group = self.associated_import.associated_group + elif remember == "all" and self.user.is_superuser: self.instance.associated_import = None self.instance.associated_user = None self.instance.associated_group = None @@ -320,63 +375,62 @@ class TargetKeyForm(forms.ModelForm): class TargetKeyFormset(BaseModelFormSet): def __init__(self, *args, **kwargs): - self.user = kwargs.pop('user') + self.user = kwargs.pop("user") super(TargetKeyFormset, self).__init__(*args, **kwargs) def get_form_kwargs(self, index): kwargs = super(TargetKeyFormset, self).get_form_kwargs(index) - kwargs['user'] = self.user + kwargs["user"] = self.user return kwargs class OrganizationForm(ManageOldType, NewItemForm): - prefix = 'organization' + prefix = "organization" form_label = _("Organization") - associated_models = {'organization_type': models.OrganizationType, - "precise_town": models.Town} - name = forms.CharField( - label=_("Name"), max_length=300, validators=[name_validator]) - organization_type = forms.ChoiceField(label=_("Organization type"), - choices=[]) + associated_models = { + "organization_type": models.OrganizationType, + "precise_town": models.Town, + } + name = forms.CharField(label=_("Name"), max_length=300, validators=[name_validator]) + organization_type = forms.ChoiceField(label=_("Organization type"), choices=[]) url = forms.URLField(label=_("Web address"), required=False) grammatical_gender = forms.ChoiceField( label=_("Grammatical gender"), - choices=[('', '--')] + list(models.GENDER), - required=False, help_text=_("Can be used by templates")) - address = forms.CharField(label=_("Address"), widget=forms.Textarea, - required=False) - address_complement = forms.CharField(label=_("Address complement"), - widget=forms.Textarea, required=False) - postal_code = forms.CharField(label=_("Postal code"), max_length=10, - required=False) - town = forms.CharField(label=_("Town (freeform)"), max_length=30, - required=False) + choices=[("", "--")] + list(models.GENDER), + required=False, + help_text=_("Can be used by templates"), + ) + address = forms.CharField(label=_("Address"), widget=forms.Textarea, required=False) + address_complement = forms.CharField( + label=_("Address complement"), widget=forms.Textarea, required=False + ) + postal_code = forms.CharField(label=_("Postal code"), max_length=10, required=False) + town = forms.CharField(label=_("Town (freeform)"), max_length=30, required=False) precise_town = get_town_field(required=False) - country = forms.CharField(label=_("Country"), max_length=30, - required=False) + country = forms.CharField(label=_("Country"), max_length=30, required=False) email = forms.EmailField(label=_("Email"), required=False) phone = forms.CharField(label=_("Phone"), max_length=18, required=False) - mobile_phone = forms.CharField(label=_("Mobile phone"), max_length=18, - required=False) + mobile_phone = forms.CharField( + label=_("Mobile phone"), max_length=18, required=False + ) def __init__(self, *args, **kwargs): super(OrganizationForm, self).__init__(*args, **kwargs) - self.fields['organization_type'].choices = \ - models.OrganizationType.get_types( - initial=self.init_data.get('organization_type')) - self.fields['organization_type'].help_text = \ - models.OrganizationType.get_help() + self.fields["organization_type"].choices = models.OrganizationType.get_types( + initial=self.init_data.get("organization_type") + ) + self.fields["organization_type"].help_text = models.OrganizationType.get_help() self.limit_fields() def save(self, user, item=None): dct = self.cleaned_data - dct['history_modifier'] = user - dct['organization_type'] = models.OrganizationType.objects.get( - pk=dct['organization_type']) + dct["history_modifier"] = user + dct["organization_type"] = models.OrganizationType.objects.get( + pk=dct["organization_type"] + ) if dct["precise_town"]: try: - dct["precise_town"] = models.Town.objects.get( - pk=dct["precise_town"]) + dct["precise_town"] = models.Town.objects.get(pk=dct["precise_town"]) except models.Town.DoesNotExist: dct.pop("precise_town") if not item: @@ -393,77 +447,81 @@ class OrganizationSelect(CustomForm, TableSelect): _model = models.Organization search_vector = forms.CharField( - label=_("Full text search"), widget=widgets.SearchWidget( - 'ishtar-common', 'organization' - )) + label=_("Full text search"), + widget=widgets.SearchWidget("ishtar-common", "organization"), + ) name = forms.CharField(label=_("Name"), max_length=300) organization_type = forms.ChoiceField(label=_("Type"), choices=[]) def __init__(self, *args, **kwargs): super(OrganizationSelect, self).__init__(*args, **kwargs) - self.fields['organization_type'].choices = \ - models.OrganizationType.get_types() + self.fields["organization_type"].choices = models.OrganizationType.get_types() class OrganizationFormSelection(CustomFormSearch): SEARCH_AND_SELECT = True form_label = _("Organization search") - associated_models = {'pk': models.Organization} - currents = {'pk': models.Organization} + associated_models = {"pk": models.Organization} + currents = {"pk": models.Organization} pk = forms.IntegerField( label="", widget=widgets.DataTable( - reverse_lazy('get-organization'), OrganizationSelect, + reverse_lazy("get-organization"), + OrganizationSelect, models.Organization, - source_full=reverse_lazy('get-organization-full')), - validators=[models.valid_id(models.Organization)]) + source_full=reverse_lazy("get-organization-full"), + ), + validators=[models.valid_id(models.Organization)], + ) class OrganizationFormMultiSelection(MultiSearchForm): form_label = _("Organization search") - associated_models = {'pks': models.Organization} + associated_models = {"pks": models.Organization} pk = forms.CharField( label="", required=True, widget=widgets.DataTable( - reverse_lazy('get-organization'), OrganizationSelect, + reverse_lazy("get-organization"), + OrganizationSelect, models.Organization, multiple_select=True, - source_full=reverse_lazy('get-organization-full')), - validators=[models.valid_ids(models.Organization)]) + source_full=reverse_lazy("get-organization-full"), + ), + validators=[models.valid_ids(models.Organization)], + ) class QAOrganizationFormMulti(QAForm): form_admin_name = _("Organization - Quick action - Modify") form_slug = "organization-quickaction-modify" - base_models = ['qa_organization_type'] + base_models = ["qa_organization_type"] associated_models = { - 'qa_organization_type': models.OrganizationType, + "qa_organization_type": models.OrganizationType, } MULTI = True - REPLACE_FIELDS = [ - 'qa_organization_type', - 'qa_grammatical_gender' - ] + REPLACE_FIELDS = ["qa_organization_type", "qa_grammatical_gender"] qa_organization_type = forms.ChoiceField( label=_("Organization type"), required=False ) qa_grammatical_gender = forms.ChoiceField( label=_("Grammatical gender"), - choices=[('', '--')] + list(models.GENDER), - required=False, help_text=_("Can be used by templates")) + choices=[("", "--")] + list(models.GENDER), + required=False, + help_text=_("Can be used by templates"), + ) TYPES = [ - FieldType('qa_organization_type', models.OrganizationType), + FieldType("qa_organization_type", models.OrganizationType), ] class ManualMerge(object): def clean_to_merge(self): - value = self.cleaned_data.get('to_merge', None) or [] + value = self.cleaned_data.get("to_merge", None) or [] if value: - value = value.split(',') + value = value.split(",") values = [] for val in value: try: @@ -471,15 +529,14 @@ class ManualMerge(object): except ValueError: pass if len(values) < 2: - raise forms.ValidationError(_("At least two items have to be " - "selected.")) - self.cleaned_data['to_merge'] = values + raise forms.ValidationError(_("At least two items have to be " "selected.")) + self.cleaned_data["to_merge"] = values return values def get_items(self): items = [] - model = self.associated_models['to_merge'] - for pk in sorted(self.cleaned_data['to_merge']): + model = self.associated_models["to_merge"] + for pk in sorted(self.cleaned_data["to_merge"]): try: items.append(model.objects.get(pk=pk)) except model.DoesNotExist: @@ -489,26 +546,29 @@ class ManualMerge(object): class MergeIntoForm(forms.Form): main_item = forms.ChoiceField( - label=_("Merge all items into"), choices=[], - widget=forms.RadioSelect()) + label=_("Merge all items into"), choices=[], widget=forms.RadioSelect() + ) def __init__(self, *args, **kwargs): - self.items = kwargs.pop('items') + self.items = kwargs.pop("items") super(MergeIntoForm, self).__init__(*args, **kwargs) - self.fields['main_item'].choices = [] + self.fields["main_item"].choices = [] for pk in self.items: try: item = self.associated_model.objects.get(pk=pk) except self.associated_model.DoesNotExist: continue - self.fields['main_item'].choices.append( - (item.pk, mark_safe("{} {}".format(simple_link_to_window(item), - str(item))))) + self.fields["main_item"].choices.append( + ( + item.pk, + mark_safe("{} {}".format(simple_link_to_window(item), str(item))), + ) + ) def merge(self): model = self.associated_model try: - main_item = model.objects.get(pk=self.cleaned_data['main_item']) + main_item = model.objects.get(pk=self.cleaned_data["main_item"]) except model.DoesNotExist: return for pk in self.items: @@ -525,15 +585,19 @@ class MergeIntoForm(forms.Form): class OrgaMergeFormSelection(ManualMerge, forms.Form): SEARCH_AND_SELECT = True form_label = _("Organization to merge") - associated_models = {'to_merge': models.Organization} - currents = {'to_merge': models.Organization} + associated_models = {"to_merge": models.Organization} + currents = {"to_merge": models.Organization} to_merge = forms.CharField( - label="", required=False, + label="", + required=False, widget=widgets.DataTable( - reverse_lazy('get-organization'), OrganizationSelect, + reverse_lazy("get-organization"), + OrganizationSelect, models.Organization, multiple_select=True, - source_full=reverse_lazy('get-organization-full')),) + source_full=reverse_lazy("get-organization-full"), + ), + ) class OrgaMergeIntoForm(MergeIntoForm): @@ -545,18 +609,24 @@ class BaseOrganizationForm(forms.ModelForm): class Meta: model = models.Organization - fields = ['name', 'organization_type', - 'grammatical_gender', 'address', 'address_complement', - 'town', 'postal_code'] + fields = [ + "name", + "organization_type", + "grammatical_gender", + "address", + "address_complement", + "town", + "postal_code", + ] class PersonSelect(CustomForm, TableSelect): _model = models.Person search_vector = forms.CharField( - label=_("Full text search"), widget=widgets.SearchWidget( - 'ishtar-common', 'person' - )) + label=_("Full text search"), + widget=widgets.SearchWidget("ishtar-common", "person"), + ) name = forms.CharField(label=_("Name"), max_length=200) surname = forms.CharField(label=_("Surname"), max_length=50) email = forms.CharField(label=_("Email"), max_length=75) @@ -564,70 +634,76 @@ class PersonSelect(CustomForm, TableSelect): attached_to = forms.IntegerField( label=_("Organization"), widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-organization'), - associated_model=models.Organization), - validators=[models.valid_id(models.Organization)]) + reverse_lazy("autocomplete-organization"), + associated_model=models.Organization, + ), + validators=[models.valid_id(models.Organization)], + ) def __init__(self, *args, **kwargs): super(PersonSelect, self).__init__(*args, **kwargs) - self.fields['person_types'].choices = models.PersonType.get_types() + self.fields["person_types"].choices = models.PersonType.get_types() class PersonFormSelection(CustomFormSearch): SEARCH_AND_SELECT = True form_label = _("Person search") - associated_models = {'pk': models.Person} - currents = {'pk': models.Person} + associated_models = {"pk": models.Person} + currents = {"pk": models.Person} pk = forms.IntegerField( label="", widget=widgets.DataTable( - reverse_lazy('get-person'), PersonSelect, models.Person, - source_full=reverse_lazy('get-person-full')), - validators=[models.valid_id(models.Person)]) + reverse_lazy("get-person"), + PersonSelect, + models.Person, + source_full=reverse_lazy("get-person-full"), + ), + validators=[models.valid_id(models.Person)], + ) class PersonFormMultiSelection(MultiSearchForm): form_label = _("Person search") - associated_models = {'pks': models.Person} + associated_models = {"pks": models.Person} pk = forms.CharField( label="", required=True, widget=widgets.DataTable( - reverse_lazy('get-person'), PersonSelect, models.Person, + reverse_lazy("get-person"), + PersonSelect, + models.Person, multiple_select=True, - source_full=reverse_lazy('get-person-full')), - validators=[models.valid_ids(models.Person)]) + source_full=reverse_lazy("get-person-full"), + ), + validators=[models.valid_ids(models.Person)], + ) class QAPersonFormMulti(QAForm): form_admin_name = _("Person - Quick action - Modify") form_slug = "person-quickaction-modify" - base_models = ['qa_title'] + base_models = ["qa_title"] associated_models = { - 'qa_title': models.TitleType, - 'qa_attached_to': models.Organization, + "qa_title": models.TitleType, + "qa_attached_to": models.Organization, } MULTI = True - REPLACE_FIELDS = [ - 'qa_title', - 'qa_attached_to' - ] - qa_title = forms.ChoiceField( - label=_("Title"), required=False - ) + REPLACE_FIELDS = ["qa_title", "qa_attached_to"] + qa_title = forms.ChoiceField(label=_("Title"), required=False) qa_attached_to = forms.IntegerField( label=_("Organization"), widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-organization'), - associated_model=models.Organization), + reverse_lazy("autocomplete-organization"), + associated_model=models.Organization, + ), validators=[models.valid_id(models.Organization)], - required=False + required=False, ) TYPES = [ - FieldType('qa_title', models.TitleType), + FieldType("qa_title", models.TitleType), ] def _get_qa_attached_to(self, value): @@ -641,15 +717,19 @@ class QAPersonFormMulti(QAForm): class PersonMergeFormSelection(ManualMerge, forms.Form): SEARCH_AND_SELECT = True form_label = _("Person to merge") - associated_models = {'to_merge': models.Person} - currents = {'to_merge': models.Person} + associated_models = {"to_merge": models.Person} + currents = {"to_merge": models.Person} to_merge = forms.CharField( - label="", required=False, + label="", + required=False, widget=widgets.DataTable( - reverse_lazy('get-person'), - PersonSelect, models.Person, + reverse_lazy("get-person"), + PersonSelect, + models.Person, multiple_select=True, - source_full=reverse_lazy('get-person-full')),) + source_full=reverse_lazy("get-person-full"), + ), + ) class PersonMergeIntoForm(MergeIntoForm): @@ -659,93 +739,106 @@ class PersonMergeIntoForm(MergeIntoForm): class SimplePersonForm(ManageOldType, NewItemForm): extra_form_modals = ["organization"] form_label = _("Identity") - associated_models = {'attached_to': models.Organization, - 'title': models.TitleType, - "precise_town": models.Town} + associated_models = { + "attached_to": models.Organization, + "title": models.TitleType, + "precise_town": models.Town, + } title = forms.ChoiceField(label=_("Title"), choices=[], required=False) - salutation = forms.CharField(label=_("Salutation"), max_length=200, - required=False) - surname = forms.CharField(label=_("Surname"), max_length=50, - validators=[name_validator]) - name = forms.CharField(label=_("Name"), max_length=200, - validators=[name_validator]) - raw_name = forms.CharField(label=_("Raw name"), max_length=300, - required=False) + salutation = forms.CharField(label=_("Salutation"), max_length=200, required=False) + surname = forms.CharField( + label=_("Surname"), max_length=50, validators=[name_validator] + ) + name = forms.CharField(label=_("Name"), max_length=200, validators=[name_validator]) + raw_name = forms.CharField(label=_("Raw name"), max_length=300, required=False) email = forms.EmailField(label=_("Email"), required=False) - phone_desc = forms.CharField(label=_("Phone description"), max_length=300, - required=False) + phone_desc = forms.CharField( + label=_("Phone description"), max_length=300, required=False + ) phone = forms.CharField(label=_("Phone"), max_length=18, required=False) - phone_desc2 = forms.CharField(label=_("Phone description 2"), - max_length=300, required=False) - phone2 = forms.CharField(label=_("Phone 2"), max_length=18, - required=False) - phone_desc3 = forms.CharField(label=_("Phone description 3"), - max_length=300, required=False) - phone3 = forms.CharField(label=_("Phone 3"), max_length=18, - required=False) - mobile_phone = forms.CharField(label=_("Mobile phone"), max_length=18, - required=False) + phone_desc2 = forms.CharField( + label=_("Phone description 2"), max_length=300, required=False + ) + phone2 = forms.CharField(label=_("Phone 2"), max_length=18, required=False) + phone_desc3 = forms.CharField( + label=_("Phone description 3"), max_length=300, required=False + ) + phone3 = forms.CharField(label=_("Phone 3"), max_length=18, required=False) + mobile_phone = forms.CharField( + label=_("Mobile phone"), max_length=18, required=False + ) attached_to = forms.IntegerField( label=_("Current organization"), widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-organization'), - associated_model=models.Organization, new=True), - validators=[models.valid_id(models.Organization)], required=False) - address = forms.CharField(label=_("Address"), widget=forms.Textarea, - required=False) + reverse_lazy("autocomplete-organization"), + associated_model=models.Organization, + new=True, + ), + validators=[models.valid_id(models.Organization)], + required=False, + ) + address = forms.CharField(label=_("Address"), widget=forms.Textarea, required=False) address_complement = forms.CharField( - label=_("Address complement"), widget=forms.Textarea, required=False) - postal_code = forms.CharField(label=_("Postal code"), max_length=10, - required=False) - town = forms.CharField(label=_("Town (freeform)"), max_length=30, - required=False) + label=_("Address complement"), widget=forms.Textarea, required=False + ) + postal_code = forms.CharField(label=_("Postal code"), max_length=10, required=False) + town = forms.CharField(label=_("Town (freeform)"), max_length=30, required=False) precise_town = get_town_field(required=False) - country = forms.CharField(label=_("Country"), max_length=30, - required=False) - alt_address = forms.CharField(label=_("Other address: address"), - widget=forms.Textarea, required=False) + country = forms.CharField(label=_("Country"), max_length=30, required=False) + alt_address = forms.CharField( + label=_("Other address: address"), widget=forms.Textarea, required=False + ) alt_address_complement = forms.CharField( label=_("Other address: address complement"), - widget=forms.Textarea, required=False) - alt_postal_code = forms.CharField(label=_("Other address: postal code"), - max_length=10, required=False) - alt_town = forms.CharField(label=_("Other address: town"), max_length=30, - required=False) - alt_country = forms.CharField(label=_("Other address: country"), - max_length=30, required=False) + widget=forms.Textarea, + required=False, + ) + alt_postal_code = forms.CharField( + label=_("Other address: postal code"), max_length=10, required=False + ) + alt_town = forms.CharField( + label=_("Other address: town"), max_length=30, required=False + ) + alt_country = forms.CharField( + label=_("Other address: country"), max_length=30, required=False + ) def __init__(self, *args, **kwargs): super(SimplePersonForm, self).__init__(*args, **kwargs) - self.fields['raw_name'].widget.attrs['readonly'] = True - self.fields['title'].choices = models.TitleType.get_types( - initial=self.init_data.get('title')) + self.fields["raw_name"].widget.attrs["readonly"] = True + self.fields["title"].choices = models.TitleType.get_types( + initial=self.init_data.get("title") + ) class PersonUserSelect(PersonSelect): - ishtaruser__isnull = forms.NullBooleanField( - label=_("Already has an account")) + ishtaruser__isnull = forms.NullBooleanField(label=_("Already has an account")) class PersonUserFormSelection(PersonFormSelection): SEARCH_AND_SELECT = True form_label = _("Person search") - associated_models = {'pk': models.Person} - currents = {'pk': models.Person} + associated_models = {"pk": models.Person} + currents = {"pk": models.Person} pk = forms.IntegerField( label="", - widget=widgets.DataTable(reverse_lazy('get-person-for-account'), - PersonUserSelect, models.Person, - table_cols="TABLE_COLS_ACCOUNT"), - validators=[models.valid_id(models.Person)]) + widget=widgets.DataTable( + reverse_lazy("get-person-for-account"), + PersonUserSelect, + models.Person, + table_cols="TABLE_COLS_ACCOUNT", + ), + validators=[models.valid_id(models.Person)], + ) class IshtarUserSelect(TableSelect): _model = models.IshtarUser search_vector = forms.CharField( - label=_("Full text search"), widget=widgets.SearchWidget( - 'ishtar-common', 'ishtaruser' - )) + label=_("Full text search"), + widget=widgets.SearchWidget("ishtar-common", "ishtaruser"), + ) username = forms.CharField(label=_("Username"), max_length=200) name = forms.CharField(label=_("Name"), max_length=200) surname = forms.CharField(label=_("Surname"), max_length=50) @@ -754,44 +847,58 @@ class IshtarUserSelect(TableSelect): attached_to = forms.IntegerField( label=_("Organization"), widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-organization'), - associated_model=models.Organization), - validators=[models.valid_id(models.Organization)]) + reverse_lazy("autocomplete-organization"), + associated_model=models.Organization, + ), + validators=[models.valid_id(models.Organization)], + ) def __init__(self, *args, **kwargs): super(IshtarUserSelect, self).__init__(*args, **kwargs) - self.fields['person_types'].choices = models.PersonType.get_types() + self.fields["person_types"].choices = models.PersonType.get_types() class AccountFormSelection(forms.Form): SEARCH_AND_SELECT = True form_label = _("Account search") - associated_models = {'pk': models.IshtarUser} - currents = {'pk': models.IshtarUser} + associated_models = {"pk": models.IshtarUser} + currents = {"pk": models.IshtarUser} pk = forms.IntegerField( label="", - widget=widgets.DataTable(reverse_lazy('get-ishtaruser'), - IshtarUserSelect, models.IshtarUser), - validators=[models.valid_id(models.IshtarUser)]) + widget=widgets.DataTable( + reverse_lazy("get-ishtaruser"), IshtarUserSelect, models.IshtarUser + ), + validators=[models.valid_id(models.IshtarUser)], + ) class BasePersonForm(forms.ModelForm): class Meta: model = models.Person - fields = ['title', 'salutation', 'name', 'surname', 'address', - 'address_complement', 'town', 'postal_code'] + fields = [ + "title", + "salutation", + "name", + "surname", + "address", + "address_complement", + "town", + "postal_code", + ] class BaseOrganizationPersonForm(forms.ModelForm): class Meta: model = models.Person - fields = ['attached_to', 'title', 'salutation', 'name', 'surname'] - widgets = {'attached_to': widgets.JQueryPersonOrganization( - reverse_lazy('autocomplete-organization'), - reverse_lazy('organization_create'), - model=models.Organization, - attrs={'hidden': True}, - new=True), + fields = ["attached_to", "title", "salutation", "name", "surname"] + widgets = { + "attached_to": widgets.JQueryPersonOrganization( + reverse_lazy("autocomplete-organization"), + reverse_lazy("organization_create"), + model=models.Organization, + attrs={"hidden": True}, + new=True, + ), } def __init__(self, *args, **kwargs): @@ -800,8 +907,9 @@ class BaseOrganizationPersonForm(forms.ModelForm): def save(self, *args, **kwargs): person = super(BaseOrganizationPersonForm, self).save(*args, **kwargs) instance = person.attached_to - form = BaseOrganizationForm(self.data, instance=instance, - prefix=BaseOrganizationForm.form_prefix) + form = BaseOrganizationForm( + self.data, instance=instance, prefix=BaseOrganizationForm.form_prefix + ) if form.is_valid(): orga = form.save() if not person.attached_to: @@ -812,20 +920,23 @@ class BaseOrganizationPersonForm(forms.ModelForm): class PersonForm(SimplePersonForm): person_types = forms.MultipleChoiceField( - label=_("Person type"), choices=[], required=False, - widget=forms.CheckboxSelectMultiple) + label=_("Person type"), + choices=[], + required=False, + widget=forms.CheckboxSelectMultiple, + ) def __init__(self, *args, **kwargs): super(PersonForm, self).__init__(*args, **kwargs) - self.fields['person_types'].choices = models.PersonType.get_types( - initial=self.init_data.get('person_types'), - empty_first=False) - self.fields['person_types'].help_text = models.PersonType.get_help() + self.fields["person_types"].choices = models.PersonType.get_types( + initial=self.init_data.get("person_types"), empty_first=False + ) + self.fields["person_types"].help_text = models.PersonType.get_help() self.limit_fields() def save(self, user, item=None): dct = self.cleaned_data - dct['history_modifier'] = user + dct["history_modifier"] = user for key in self.associated_models.keys(): if key in dct: if not dct[key]: @@ -836,7 +947,7 @@ class PersonForm(SimplePersonForm): dct[key] = model.objects.get(pk=dct[key]) except model.DoesNotExist: dct.pop(key) - person_types = dct.pop('person_types') + person_types = dct.pop("person_types") if not item: new_item = models.Person.objects.create(**dct) else: @@ -853,64 +964,77 @@ class PersonForm(SimplePersonForm): class NoOrgaPersonForm(PersonForm): def __init__(self, *args, **kwargs): super(NoOrgaPersonForm, self).__init__(*args, **kwargs) - self.fields.pop('attached_to') + self.fields.pop("attached_to") class PersonTypeForm(ManageOldType, forms.Form): form_label = _("Person type") - base_model = 'person_type' - associated_models = {'person_type': models.PersonType} + base_model = "person_type" + associated_models = {"person_type": models.PersonType} person_type = forms.MultipleChoiceField( - label=_("Person type"), choices=[], required=False, - widget = widgets.Select2Multiple) + label=_("Person type"), + choices=[], + required=False, + widget=widgets.Select2Multiple, + ) def __init__(self, *args, **kwargs): super(PersonTypeForm, self).__init__(*args, **kwargs) - self.fields['person_type'].choices = models.PersonType.get_types( - initial=self.init_data.get('person_type'), - empty_first=False) - self.fields['person_type'].help_text = models.PersonType.get_help() + self.fields["person_type"].choices = models.PersonType.get_types( + initial=self.init_data.get("person_type"), empty_first=False + ) + self.fields["person_type"].help_text = models.PersonType.get_help() class AccountForm(IshtarForm): form_label = _("Account") - associated_models = {'pk': models.Person} - currents = {'pk': models.Person} - pk = forms.IntegerField(label="", widget=forms.HiddenInput, - required=False) + associated_models = {"pk": models.Person} + currents = {"pk": models.Person} + pk = forms.IntegerField(label="", widget=forms.HiddenInput, required=False) username = forms.CharField(label=_("Account"), max_length=30) - email = forms.CharField(label=_("Email"), max_length=75, - validators=[validators.validate_email]) + email = forms.CharField( + label=_("Email"), max_length=75, validators=[validators.validate_email] + ) hidden_password = forms.CharField( - label=_("New password"), max_length=128, widget=forms.PasswordInput, - required=False, validators=[validators.MinLengthValidator(4)]) + label=_("New password"), + max_length=128, + widget=forms.PasswordInput, + required=False, + validators=[validators.MinLengthValidator(4)], + ) hidden_password_confirm = forms.CharField( - label=_("New password (confirmation)"), max_length=128, - widget=forms.PasswordInput, required=False) + label=_("New password (confirmation)"), + max_length=128, + widget=forms.PasswordInput, + required=False, + ) HEADERS = { - 'hidden_password': FormHeader( + "hidden_password": FormHeader( _("New password"), - help_message=_("Keep these fields empty if you do not want to " - "change password. On creation, if you leave these " - "fields empty, the user will not be able to " - "connect.")), + help_message=_( + "Keep these fields empty if you do not want to " + "change password. On creation, if you leave these " + "fields empty, the user will not be able to " + "connect." + ), + ), } def __init__(self, *args, **kwargs): person = None - if 'initial' in kwargs and 'pk' in kwargs['initial']: + if "initial" in kwargs and "pk" in kwargs["initial"]: try: - person = models.Person.objects.get(pk=kwargs['initial']['pk']) + person = models.Person.objects.get(pk=kwargs["initial"]["pk"]) account = models.IshtarUser.objects.get(person=person).user_ptr - if not kwargs['initial'].get('username'): - kwargs['initial']['username'] = account.username - if not kwargs['initial'].get('email'): - kwargs['initial']['email'] = account.email + if not kwargs["initial"].get("username"): + kwargs["initial"]["username"] = account.username + if not kwargs["initial"].get("email"): + kwargs["initial"]["email"] = account.email except ObjectDoesNotExist: pass - if 'person' in kwargs: - person = kwargs.pop('person') + if "person" in kwargs: + person = kwargs.pop("person") super(AccountForm, self).__init__(*args, **kwargs) if person and (person.raw_name or (person.name and person.surname)): profile = models.IshtarSiteProfile.get_current_profile() @@ -920,25 +1044,27 @@ class AccountForm(IshtarForm): values = person.raw_name.lower().split(" ") if profile.account_naming_style == "FN" and len(values) > 1: values = values[1:] + [values[0]] - self.fields['username'].initial = ".".join(values) + self.fields["username"].initial = ".".join(values) def clean(self): cleaned_data = self.cleaned_data password = cleaned_data.get("hidden_password") - if password and \ - password != cleaned_data.get("hidden_password_confirm"): - raise forms.ValidationError(_("Your password and confirmation " - "password do not match.")) + if password and password != cleaned_data.get("hidden_password_confirm"): + raise forms.ValidationError( + _("Your password and confirmation " "password do not match.") + ) if not cleaned_data.get("pk"): - models.is_unique(User, 'username')(cleaned_data.get("username")) + models.is_unique(User, "username")(cleaned_data.get("username")) if not password: - raise forms.ValidationError(_("You must provide a correct " - "password.")) + raise forms.ValidationError( + _("You must provide a correct " "password.") + ) # check username unicity q = models.IshtarUser.objects.filter( - user_ptr__username=cleaned_data.get('username')) - if cleaned_data.get('pk'): - q = q.exclude(person__pk=cleaned_data.get('pk')) + user_ptr__username=cleaned_data.get("username") + ) + if cleaned_data.get("pk"): + q = q.exclude(person__pk=cleaned_data.get("pk")) if q.count(): raise forms.ValidationError(_("This username already exists.")) return cleaned_data @@ -946,24 +1072,22 @@ class AccountForm(IshtarForm): class ProfileForm(ManageOldType): form_label = _("Profiles") - base_model = 'profile' - associated_models = { - 'profile_type': models.ProfileType, - 'area': models.Area - } + base_model = "profile" + associated_models = {"profile_type": models.ProfileType, "area": models.Area} profile_type = forms.ChoiceField(label=_("Type"), choices=[]) area = widgets.Select2MultipleField(label=_("Areas"), required=False) name = forms.CharField(label=_("Name"), required=False) pk = forms.IntegerField(label=" ", widget=forms.HiddenInput, required=False) TYPES = [ - FieldType('profile_type', models.ProfileType), - FieldType('area', models.Area, is_multiple=True), + FieldType("profile_type", models.ProfileType), + FieldType("area", models.Area, is_multiple=True), ] -ProfileFormset = formset_factory(ProfileForm, can_delete=True, - formset=FormSetWithDeleteSwitches) +ProfileFormset = formset_factory( + ProfileForm, can_delete=True, formset=FormSetWithDeleteSwitches +) ProfileFormset.form_label = _("Profiles") ProfileFormset.form_admin_name = _("Profiles") ProfileFormset.form_slug = "profiles" @@ -972,8 +1096,9 @@ ProfileFormset.form_slug = "profiles" class FinalAccountForm(forms.Form): final = True form_label = _("Confirm") - send_password = forms.BooleanField(label=_("Send the new password by " - "email?"), required=False) + send_password = forms.BooleanField( + label=_("Send the new password by " "email?"), required=False + ) def __init__(self, *args, **kwargs): self.is_hidden = True @@ -984,88 +1109,97 @@ class ProfilePersonForm(forms.Form): """ Edit the current profile """ - current_profile = forms.ChoiceField(label=_("Current profile"), - choices=[]) + + current_profile = forms.ChoiceField(label=_("Current profile"), choices=[]) name = forms.CharField(label=_("Name"), required=False) - profile_type = forms.ChoiceField(label=_("Profile type"), required=False, - disabled=True, choices=[]) + profile_type = forms.ChoiceField( + label=_("Profile type"), required=False, disabled=True, choices=[] + ) auto_pin = forms.BooleanField( - label=_("Pin automatically items on creation and modification"), - required=False) - display_pin_menu = forms.BooleanField( - label=_("Show pin menu"), - required=False) + label=_("Pin automatically items on creation and modification"), required=False + ) + display_pin_menu = forms.BooleanField(label=_("Show pin menu"), required=False) duplicate_profile = forms.BooleanField( - label=_("Duplicate this profile"), required=False) + label=_("Duplicate this profile"), required=False + ) delete_profile = forms.BooleanField( - label=_("Delete this profile"), required=False, + label=_("Delete this profile"), + required=False, ) def __init__(self, *args, **kwargs): - self.user = kwargs.pop('user') - choices, initial = [], kwargs.get('initial', {}) + self.user = kwargs.pop("user") + choices, initial = [], kwargs.get("initial", {}) current_profile = None for profile in self.user.ishtaruser.person.profiles.order_by( - 'name', 'profile_type__label').all(): + "name", "profile_type__label" + ).all(): if profile.current: current_profile = profile - initial['current_profile'] = profile.pk + initial["current_profile"] = profile.pk choices.append((profile.pk, str(profile))) if current_profile: - initial['name'] = current_profile.name or \ - current_profile.profile_type - initial['profile_type'] = current_profile.profile_type.pk - initial['auto_pin'] = current_profile.auto_pin - initial['display_pin_menu'] = current_profile.display_pin_menu - kwargs['initial'] = initial + initial["name"] = current_profile.name or current_profile.profile_type + initial["profile_type"] = current_profile.profile_type.pk + initial["auto_pin"] = current_profile.auto_pin + initial["display_pin_menu"] = current_profile.display_pin_menu + kwargs["initial"] = initial super(ProfilePersonForm, self).__init__(*args, **kwargs) - self.fields['current_profile'].choices = choices + self.fields["current_profile"].choices = choices - if not current_profile or \ - not self.user.ishtaruser.person.profiles.filter( - profile_type=current_profile.profile_type).exclude( - pk=current_profile.pk).count(): + if ( + not current_profile + or not self.user.ishtaruser.person.profiles.filter( + profile_type=current_profile.profile_type + ) + .exclude(pk=current_profile.pk) + .count() + ): # cannot delete the current profile if no profile of this type is # available - self.fields.pop('delete_profile') + self.fields.pop("delete_profile") if not current_profile: return - self.fields['profile_type'].choices = [ + self.fields["profile_type"].choices = [ (current_profile.profile_type.pk, current_profile.profile_type.name) ] def clean(self): data = self.cleaned_data q = models.UserProfile.objects.filter( - person__ishtaruser=self.user.ishtaruser, - pk=data['current_profile']) + person__ishtaruser=self.user.ishtaruser, pk=data["current_profile"] + ) if not q.count(): return data profile = q.all()[0] - name = data.get('name', '') - if models.UserProfile.objects.filter( - person__ishtaruser=self.user.ishtaruser, - name=name).exclude(pk=profile.pk).count(): - raise forms.ValidationError( - _("A profile with the same name exists.")) + name = data.get("name", "") + if ( + models.UserProfile.objects.filter( + person__ishtaruser=self.user.ishtaruser, name=name + ) + .exclude(pk=profile.pk) + .count() + ): + raise forms.ValidationError(_("A profile with the same name exists.")) return data def save(self, session): q = models.UserProfile.objects.filter( person__ishtaruser=self.user.ishtaruser, - pk=self.cleaned_data['current_profile']) + pk=self.cleaned_data["current_profile"], + ) if not q.count(): return profile = q.all()[0] # manage deletion - if self.cleaned_data.get('delete_profile', None): + if self.cleaned_data.get("delete_profile", None): q = self.user.ishtaruser.person.profiles.filter( - profile_type=profile.profile_type).exclude( - pk=profile.pk) + profile_type=profile.profile_type + ).exclude(pk=profile.pk) if not q.count(): # cannot delete the current profile if no profile of this type # is available @@ -1076,10 +1210,10 @@ class ProfilePersonForm(forms.Form): profile.delete() return - name = self.cleaned_data['name'] + name = self.cleaned_data["name"] # manage duplication - if self.cleaned_data.get('duplicate_profile', None): + if self.cleaned_data.get("duplicate_profile", None): profile_name = profile.name or profile.profile_type.label if name == profile_name: name += str(_(" (duplicate)")) @@ -1088,24 +1222,23 @@ class ProfilePersonForm(forms.Form): profile.current = True profile.name = name - profile.auto_pin = self.cleaned_data['auto_pin'] - profile.display_pin_menu = self.cleaned_data['display_pin_menu'] + profile.auto_pin = self.cleaned_data["auto_pin"] + profile.display_pin_menu = self.cleaned_data["display_pin_menu"] profile.save() clean_session_cache(session) class TownForm(forms.Form): form_label = _("Towns") - base_model = 'town' - associated_models = {'town': models.Town} + base_model = "town" + associated_models = {"town": models.Town} town = get_town_field(required=False) class TownFormSet(FormSet): def clean(self): """Checks that no towns are duplicated.""" - return self.check_duplicate(('town',), - _("There are identical towns.")) + return self.check_duplicate(("town",), _("There are identical towns.")) TownFormset = formset_factory(TownForm, can_delete=True, formset=TownFormSet) @@ -1115,8 +1248,8 @@ TownFormset.form_slug = "towns" class MergeFormSet(BaseModelFormSet): - from_key = '' - to_key = '' + from_key = "" + to_key = "" def __init__(self, *args, **kwargs): self._cached_list = [] @@ -1150,14 +1283,13 @@ class MergeFormSet(BaseModelFormSet): pk = self.get_restricted_queryset()[i].pk if isinstance(pk, list): pk = pk[0] - kwargs['instance'] = self._existing_object(pk) - if i < self.initial_form_count() and not kwargs.get('instance'): - kwargs['instance'] = self.get_restricted_queryset()[i] + kwargs["instance"] = self._existing_object(pk) + if i < self.initial_form_count() and not kwargs.get("instance"): + kwargs["instance"] = self.get_restricted_queryset()[i] if i >= self.initial_form_count() and self.initial_extra: # Set initial values for extra forms try: - kwargs['initial'] = \ - self.initial_extra[i - self.initial_form_count()] + kwargs["initial"] = self.initial_extra[i - self.initial_form_count()] except IndexError: pass return super(BaseModelFormSet, self)._construct_form(i, **kwargs) @@ -1172,8 +1304,7 @@ class MergeFormSet(BaseModelFormSet): existing, res = [], [] # only get one version of each couple for item in q.all(): - tpl = [getattr(item, self.from_key).pk, - getattr(item, self.to_key).pk] + tpl = [getattr(item, self.from_key).pk, getattr(item, self.to_key).pk] if tpl not in existing: res.append(item) existing.append(list(reversed(tpl))) @@ -1182,15 +1313,17 @@ class MergeFormSet(BaseModelFormSet): class MergeForm(forms.ModelForm): - id = forms.IntegerField( - label="", widget=forms.HiddenInput, required=False) + id = forms.IntegerField(label="", widget=forms.HiddenInput, required=False) a_is_duplicate_b = forms.BooleanField(required=False) b_is_duplicate_a = forms.BooleanField(required=False) not_duplicate = forms.BooleanField(required=False) def clean(self): - checked = [True for k in ['a_is_duplicate_b', 'b_is_duplicate_a', - 'not_duplicate'] if self.cleaned_data.get(k)] + checked = [ + True + for k in ["a_is_duplicate_b", "b_is_duplicate_a", "not_duplicate"] + if self.cleaned_data.get(k) + ] if len(checked) > 1: raise forms.ValidationError(_("Only one choice can be checked.")) return self.cleaned_data @@ -1201,18 +1334,18 @@ class MergeForm(forms.ModelForm): from_item = getattr(self.instance, self.FROM_KEY) except ObjectDoesNotExist: return - if self.cleaned_data.get('a_is_duplicate_b'): + if self.cleaned_data.get("a_is_duplicate_b"): to_item.merge(from_item) - elif self.cleaned_data.get('b_is_duplicate_a'): + elif self.cleaned_data.get("b_is_duplicate_a"): from_item.merge(to_item) - elif self.cleaned_data.get('not_duplicate'): + elif self.cleaned_data.get("not_duplicate"): from_item.merge_exclusion.add(to_item) else: return try: self.instance.__class__.objects.get( - **{self.TO_KEY: from_item, - self.FROM_KEY: to_item}).delete() + **{self.TO_KEY: from_item, self.FROM_KEY: to_item} + ).delete() except ObjectDoesNotExist: pass self.instance.delete() @@ -1223,8 +1356,8 @@ class MergePersonForm(MergeForm): model = models.Person fields = [] - FROM_KEY = 'from_person' - TO_KEY = 'to_person' + FROM_KEY = "from_person" + TO_KEY = "to_person" class MergeOrganizationForm(MergeForm): @@ -1232,18 +1365,27 @@ class MergeOrganizationForm(MergeForm): model = models.Organization fields = [] - FROM_KEY = 'from_organization' - TO_KEY = 'to_organization' + FROM_KEY = "from_organization" + TO_KEY = "to_organization" def get_image_help(): if not settings.IMAGE_MAX_SIZE: return max_size_help() - return str( - _("Heavy images are resized to: %(width)dx%(height)d " - "(ratio is preserved).") % { - 'width': settings.IMAGE_MAX_SIZE[0], - 'height': settings.IMAGE_MAX_SIZE[1]}) + " " + str(max_size_help()) + return ( + str( + _( + "Heavy images are resized to: %(width)dx%(height)d " + "(ratio is preserved)." + ) + % { + "width": settings.IMAGE_MAX_SIZE[0], + "height": settings.IMAGE_MAX_SIZE[1], + } + ) + + " " + + str(max_size_help()) + ) ####################### @@ -1256,13 +1398,13 @@ class AddGenericForm(ManageOldType, NewItemForm): label = forms.CharField(label=_("Label"), max_length=200) def clean_label(self): - value = self.cleaned_data.get('label', None).strip() + value = self.cleaned_data.get("label", None).strip() if self.model.objects.filter(label=value).count(): raise forms.ValidationError(_("This value already exist")) return value def save(self, user): - label = self.cleaned_data['label'] + label = self.cleaned_data["label"] base_slug = slugify(label) slug = base_slug idx = 0 @@ -1286,168 +1428,232 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType): form_admin_name = _("Document - General") form_slug = "document-general" file_upload = True - extra_form_modals = ["author", "person", "organization", "documenttag", - "container"] - associated_models = {'source_type': models.SourceType, - 'support_type': models.SupportType, - 'publisher': models.Organization, - 'format_type': models.Format} + extra_form_modals = ["author", "person", "organization", "documenttag", "container"] + associated_models = { + "source_type": models.SourceType, + "support_type": models.SupportType, + "publisher": models.Organization, + "format_type": models.Format, + } pk = forms.IntegerField(label="", required=False, widget=forms.HiddenInput) - title = forms.CharField(label=_("Title"), required=False, - validators=[validators.MaxLengthValidator(200)]) + title = forms.CharField( + label=_("Title"), + required=False, + validators=[validators.MaxLengthValidator(200)], + ) source_type = widgets.ModelChoiceField( - model=models.SourceType, label=_("Type"), choices=[], - required=False) + model=models.SourceType, label=_("Type"), choices=[], required=False + ) support_type = widgets.ModelChoiceField( - model=models.SupportType, label=_("Medium"), choices=[], - required=False) + model=models.SupportType, label=_("Medium"), choices=[], required=False + ) format_type = widgets.ModelChoiceField( - model=models.Format, label=_("Format"), choices=[], - required=False) + model=models.Format, label=_("Format"), choices=[], required=False + ) scale = forms.CharField(label=_("Scale"), max_length=30, required=False) container_id = forms.IntegerField( label=_("Current container"), widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-container'), - associated_model=Container, new=True), - validators=[models.valid_id(Container)], required=False) + reverse_lazy("autocomplete-container"), associated_model=Container, new=True + ), + validators=[models.valid_id(Container)], + required=False, + ) container_ref_id = forms.IntegerField( label=_("Reference container"), widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-container'), - associated_model=Container, new=True), - validators=[models.valid_id(Container)], required=False) + reverse_lazy("autocomplete-container"), associated_model=Container, new=True + ), + validators=[models.valid_id(Container)], + required=False, + ) authors = widgets.Select2MultipleField( - label=_("Authors"), required=False, model=models.Author, - remote="autocomplete-author") + label=_("Authors"), + required=False, + model=models.Author, + remote="autocomplete-author", + ) publisher = forms.IntegerField( label=_("Publisher"), widget=widgets.JQueryAutoComplete( reverse_lazy( - 'autocomplete-organization', - args=[models.organization_type_pks_lazy( - settings.ISHTAR_SLUGS["document-publisher"])]), + "autocomplete-organization", + args=[ + models.organization_type_pks_lazy( + settings.ISHTAR_SLUGS["document-publisher"] + ) + ], + ), limit={ - 'organization_type': [models.organization_type_pks_lazy( - settings.ISHTAR_SLUGS["document-publisher"])]}, + "organization_type": [ + models.organization_type_pks_lazy( + settings.ISHTAR_SLUGS["document-publisher"] + ) + ] + }, tips=models.get_publisher_label, - associated_model=models.Organization), - validators=[models.valid_id(models.Organization)], required=False) + associated_model=models.Organization, + ), + validators=[models.valid_id(models.Organization)], + required=False, + ) publishing_year = forms.IntegerField( label=_("Year of publication"), validators=[MinValueValidator(1000), max_value_current_year], - required=False) + required=False, + ) licenses = widgets.Select2MultipleField( - label=_("Licenses"), required=False, model=models.LicenseType) + label=_("Licenses"), required=False, model=models.LicenseType + ) tags = widgets.Select2MultipleField( - label=_("Tags"), required=False, model=models.DocumentTag, - remote="autocomplete-documenttag") + label=_("Tags"), + required=False, + model=models.DocumentTag, + remote="autocomplete-documenttag", + ) language = widgets.ModelChoiceField( - model=models.Language, label=_("Language"), choices=[], - required=False) + model=models.Language, label=_("Language"), choices=[], required=False + ) issn = forms.CharField( label=_("ISSN"), widget=widgets.ISSNWidget, - validators=[validators.MaxLengthValidator(9)], required=False) + validators=[validators.MaxLengthValidator(9)], + required=False, + ) isbn = forms.CharField( label=_("ISBN"), widget=widgets.ISBNWidget, - validators=[validators.MaxLengthValidator(17)], required=False) + validators=[validators.MaxLengthValidator(17)], + required=False, + ) source = widgets.ModelJQueryAutocompleteField( - label=_("Source"), - model=models.Document, required=False) + label=_("Source"), model=models.Document, required=False + ) source_free_input = forms.CharField( label=_("Source - free input"), - validators=[validators.MaxLengthValidator(500)], required=False) + validators=[validators.MaxLengthValidator(500)], + required=False, + ) source_page_range = forms.CharField( label=_("Source - page range"), - validators=[validators.MaxLengthValidator(500)], required=False, - help_text=_("Unique page: \"242\", page range: \"242-245\", multiple " - "pages: \"242;245;249\", multiples pages and multiple " - "pages ranges: \"242-245;249;262-265\".") + validators=[validators.MaxLengthValidator(500)], + required=False, + help_text=_( + 'Unique page: "242", page range: "242-245", multiple ' + 'pages: "242;245;249", multiples pages and multiple ' + 'pages ranges: "242-245;249;262-265".' + ), ) associated_url = forms.URLField( - max_length=1000, required=False, - label=_("Numerical ressource (web address)")) + max_length=1000, required=False, label=_("Numerical ressource (web address)") + ) image = forms.ImageField( - label=_("Image"), help_text=mark_safe(get_image_help()), - max_length=255, required=False, widget=widgets.ImageFileInput(), - validators=[file_size_validator] + label=_("Image"), + help_text=mark_safe(get_image_help()), + max_length=255, + required=False, + widget=widgets.ImageFileInput(), + validators=[file_size_validator], ) associated_file = forms.FileField( - label=pgettext("Not directory", "File"), max_length=255, - required=False, help_text=max_size_help(), - validators=[file_size_validator] + label=pgettext("Not directory", "File"), + max_length=255, + required=False, + help_text=max_size_help(), + validators=[file_size_validator], ) reference = forms.CharField( label=_("Reference"), - validators=[validators.MaxLengthValidator(100)], required=False) + validators=[validators.MaxLengthValidator(100)], + required=False, + ) internal_reference = forms.CharField( label=_("Internal reference"), - validators=[validators.MaxLengthValidator(100)], required=False) - receipt_date = forms.DateField(label=_("Receipt date"), required=False, - widget=DatePicker) - creation_date = forms.DateField(label=_("Creation date"), required=False, - widget=DatePicker) + validators=[validators.MaxLengthValidator(100)], + required=False, + ) + receipt_date = forms.DateField( + label=_("Receipt date"), required=False, widget=DatePicker + ) + creation_date = forms.DateField( + label=_("Creation date"), required=False, widget=DatePicker + ) receipt_date_in_documentation = forms.DateField( - label=_("Receipt date in documentation"), required=False, - widget=DatePicker) - comment = forms.CharField(label=_("Comment"), widget=forms.Textarea, - required=False) - description = forms.CharField(label=_("Description"), - widget=forms.Textarea, required=False) + label=_("Receipt date in documentation"), required=False, widget=DatePicker + ) + comment = forms.CharField(label=_("Comment"), widget=forms.Textarea, required=False) + description = forms.CharField( + label=_("Description"), widget=forms.Textarea, required=False + ) additional_information = forms.CharField( - label=_("Additional information"), widget=forms.Textarea, - required=False) - duplicate = forms.NullBooleanField(label=_("Has a duplicate"), - required=False) + label=_("Additional information"), widget=forms.Textarea, required=False + ) + duplicate = forms.NullBooleanField(label=_("Has a duplicate"), required=False) TYPES = [ - FieldType('source_type', models.SourceType), - FieldType('support_type', models.SupportType), - FieldType('format_type', models.Format), - FieldType('language', models.Language), - FieldType('licences', models.LicenseType, is_multiple=True), - FieldType('tags', models.DocumentTag, is_multiple=True), + FieldType("source_type", models.SourceType), + FieldType("support_type", models.SupportType), + FieldType("format_type", models.Format), + FieldType("language", models.Language), + FieldType("licences", models.LicenseType, is_multiple=True), + FieldType("tags", models.DocumentTag, is_multiple=True), ] class Meta: model = models.Document fields = [ - 'title', 'source_type', 'reference', 'internal_reference', - 'format_type', 'support_type', 'scale', - 'image', 'associated_file', 'associated_url', 'tags', - 'authors', 'receipt_date', - 'receipt_date_in_documentation', 'creation_date', - 'publisher', 'publishing_year', - 'language', 'isbn', 'issn', 'licenses', - 'source', 'source_free_input', 'source_page_range', - 'container_id', "container_ref_id", - 'comment', 'description', 'additional_information', 'duplicate' + "title", + "source_type", + "reference", + "internal_reference", + "format_type", + "support_type", + "scale", + "image", + "associated_file", + "associated_url", + "tags", + "authors", + "receipt_date", + "receipt_date_in_documentation", + "creation_date", + "publisher", + "publishing_year", + "language", + "isbn", + "issn", + "licenses", + "source", + "source_free_input", + "source_page_range", + "container_id", + "container_ref_id", + "comment", + "description", + "additional_information", + "duplicate", ] HEADERS = { - 'finds': FormHeader(_("Related items")), - 'title': FormHeader(_("Identification")), - 'format_type': FormHeader(_("Format")), - 'image': FormHeader(_("Content")), - 'authors': FormHeader(_("Authors")), - 'receipt_date': FormHeader(_("Dates")), - 'publisher': FormHeader(_("Publishing"), collapse=True), - 'source': FormHeader(_("Source"), collapse=True), - 'container_id': FormHeader(_("Warehouse"), collapse=True), - 'comment': FormHeader(_("Advanced"), collapse=True), + "finds": FormHeader(_("Related items")), + "title": FormHeader(_("Identification")), + "format_type": FormHeader(_("Format")), + "image": FormHeader(_("Content")), + "authors": FormHeader(_("Authors")), + "receipt_date": FormHeader(_("Dates")), + "publisher": FormHeader(_("Publishing"), collapse=True), + "source": FormHeader(_("Source"), collapse=True), + "container_id": FormHeader(_("Warehouse"), collapse=True), + "comment": FormHeader(_("Advanced"), collapse=True), } OPTIONS_PERMISSIONS = [ # field name, permission, options ("tags", ("ishtar_common.add_documenttag",), {"new": True}), ("authors", ("ishtar_common.add_author",), {"new": True}), ("publisher", ("ishtar_common.add_organization",), {"new": True}), - ("container", ("archaeological_warehouse.add_container",), - {"new": True}), - ("container_ref", ("archaeological_warehouse.add_container",), - {"new": True}), + ("container", ("archaeological_warehouse.add_container",), {"new": True}), + ("container_ref", ("archaeological_warehouse.add_container",), {"new": True}), ] def __init__(self, *args, **kwargs): @@ -1457,33 +1663,36 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType): self.user = None if kwargs.get("user", None): self.user = kwargs.pop("user") - self.is_instancied = bool(kwargs.get('instance', False)) + self.is_instancied = bool(kwargs.get("instance", False)) super(DocumentForm, self).__init__(*args, **kwargs) fields = OrderedDict() for related_key in models.Document.RELATED_MODELS_ALT: model = models.Document._meta.get_field(related_key).related_model fields[related_key] = widgets.Select2MultipleField( - model=model, remote=True, label=model._meta.verbose_name_plural, - required=False, style="width: 100%" + model=model, + remote=True, + label=model._meta.verbose_name_plural, + required=False, + style="width: 100%", ) if related_key in main_items_fields: for field_key, label in main_items_fields[related_key]: disabled = False - if kwargs.get('initial', None) and kwargs['initial'].get( - field_key, False): + if kwargs.get("initial", None) and kwargs["initial"].get( + field_key, False + ): disabled = True fields[field_key] = forms.BooleanField( - label=label, required=False, disabled=disabled) + label=label, required=False, disabled=disabled + ) for k in self.fields: fields[k] = self.fields[k] self.fields = fields def clean_source_page_range(self): - value = self.cleaned_data.get( - 'source_page_range', None).replace(" ", "") + value = self.cleaned_data.get("source_page_range", None).replace(" ", "") if value and not re.match(r"^(\d+[-;]*\d)+$", value): - raise forms.ValidationError( - _("Incorrect page range.")) + raise forms.ValidationError(_("Incorrect page range.")) return value def get_headers(self): @@ -1511,80 +1720,93 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType): """ conditional_fields = {} excluded_fields = {} - key = 'source_type' + key = "source_type" for doc_type in models.SourceType.objects.filter( - available=True, formats__pk__isnull=False).all(): + available=True, formats__pk__isnull=False + ).all(): if key not in conditional_fields: conditional_fields[key] = {} sub_key = doc_type.pk if sub_key in conditional_fields[key]: continue - lst = [str(f.pk) for f in models.Format.objects.filter( - available=True, document_types__pk=doc_type.pk)] - if 'format_type' not in excluded_fields: - excluded_fields['format_type'] = [] + lst = [ + str(f.pk) + for f in models.Format.objects.filter( + available=True, document_types__pk=doc_type.pk + ) + ] + if "format_type" not in excluded_fields: + excluded_fields["format_type"] = [] for k in lst: - if k not in excluded_fields['format_type']: - excluded_fields['format_type'].append(k) - conditional_fields[key][sub_key] = [('format_type', ",".join(lst))] + if k not in excluded_fields["format_type"]: + excluded_fields["format_type"].append(k) + conditional_fields[key][sub_key] = [("format_type", ",".join(lst))] for doc_type in models.SourceType.objects.filter( - available=True, supports__pk__isnull=False).all(): + available=True, supports__pk__isnull=False + ).all(): if key not in conditional_fields: conditional_fields[key] = {} - lst = [str(f.pk) for f in models.SupportType.objects.filter( - available=True, document_types__pk=doc_type.pk)] - if 'support_type' not in excluded_fields: - excluded_fields['support_type'] = [] + lst = [ + str(f.pk) + for f in models.SupportType.objects.filter( + available=True, document_types__pk=doc_type.pk + ) + ] + if "support_type" not in excluded_fields: + excluded_fields["support_type"] = [] for k in lst: - if k not in excluded_fields['support_type']: - excluded_fields['support_type'].append(k) + if k not in excluded_fields["support_type"]: + excluded_fields["support_type"].append(k) sub_key = doc_type.pk if sub_key not in conditional_fields[key]: conditional_fields[key][sub_key] = [] - conditional_fields[key][sub_key].append( - ('support_type', ",".join(lst))) + conditional_fields[key][sub_key].append(("support_type", ",".join(lst))) for k in excluded_fields: excluded_fields[k] = ",".join(excluded_fields[k]) all_values = { "format_type": [list(tp) for tp in models.Format.get_types()], - "support_type": [list(tp) for tp in models.SupportType.get_types()] + "support_type": [list(tp) for tp in models.SupportType.get_types()], } return conditional_fields, excluded_fields, all_values def clean(self): cleaned_data = self.cleaned_data - if not cleaned_data.get('title', None) and \ - not cleaned_data.get('image', None) and \ - not cleaned_data.get('associated_file', None) and \ - not cleaned_data.get('associated_url', None): + if ( + not cleaned_data.get("title", None) + and not cleaned_data.get("image", None) + and not cleaned_data.get("associated_file", None) + and not cleaned_data.get("associated_url", None) + ): raise forms.ValidationError( - _("You should at least fill one of this field: title, url, " - "image or file. If you have provided an image check that " - "it is not corrupted.")) + _( + "You should at least fill one of this field: title, url, " + "image or file. If you have provided an image check that " + "it is not corrupted." + ) + ) for rel in models.Document.RELATED_MODELS: if cleaned_data.get(rel, None): return cleaned_data - raise forms.ValidationError(_("A document has to be attached at least " - "to one item")) + raise forms.ValidationError( + _("A document has to be attached at least " "to one item") + ) def clean_publisher(self): if not self.cleaned_data.get("publisher", None): return try: - return models.Organization.objects.get( - pk=self.cleaned_data["publisher"]) + return models.Organization.objects.get(pk=self.cleaned_data["publisher"]) except models.Organization.DoesNotExist: return def save(self, commit=True): - if not self.cleaned_data.get('authors', None): - self.cleaned_data['authors'] = [] + if not self.cleaned_data.get("authors", None): + self.cleaned_data["authors"] = [] item = super(DocumentForm, self).save(commit=commit) for related_key in models.Document.RELATED_MODELS: related = getattr(item, related_key) initial = dict([(rel.pk, rel) for rel in related.all()]) - new = [int(pk) - for pk in sorted(self.cleaned_data.get(related_key, []))] + new = [int(pk) for pk in sorted(self.cleaned_data.get(related_key, []))] for pk, value in initial.items(): if pk in new: continue @@ -1594,8 +1816,7 @@ class DocumentForm(forms.ModelForm, CustomForm, ManageOldType): if new_pk not in initial.keys(): related.add(related_item) key = "{}_{}_main_image".format(related_key, related_item.pk) - if self.cleaned_data.get(key, []) and \ - related_item.main_image != item: + if self.cleaned_data.get(key, []) and related_item.main_image != item: related_item.skip_history_when_saving = True related_item.main_image = item related_item.save() @@ -1614,15 +1835,18 @@ class DocumentSelect(HistorySelect): form_slug = "document-001-search" search_vector = forms.CharField( - label=_("Full text search"), widget=widgets.SearchWidget( - 'ishtar-common', 'document' - )) + label=_("Full text search"), + widget=widgets.SearchWidget("ishtar-common", "document"), + ) authors = forms.IntegerField( widget=widgets.JQueryAutoComplete( - "/" + settings.URL_PATH + 'autocomplete-author', - associated_model=models.Author), - validators=[models.valid_id(models.Author)], label=_("Author"), - required=False) + "/" + settings.URL_PATH + "autocomplete-author", + associated_model=models.Author, + ), + validators=[models.valid_id(models.Author)], + label=_("Author"), + required=False, + ) title = forms.CharField(label=_("Title")) source_type = forms.ChoiceField(label=_("Type"), choices=[]) @@ -1638,15 +1862,25 @@ class DocumentSelect(HistorySelect): label=_("Publisher"), widget=widgets.JQueryAutoComplete( reverse_lazy( - 'autocomplete-organization', - args=[models.organization_type_pks_lazy( - settings.ISHTAR_SLUGS["document-publisher"])]), + "autocomplete-organization", + args=[ + models.organization_type_pks_lazy( + settings.ISHTAR_SLUGS["document-publisher"] + ) + ], + ), limit={ - 'organization_type': [models.organization_type_pks_lazy( - settings.ISHTAR_SLUGS["document-publisher"])]}, + "organization_type": [ + models.organization_type_pks_lazy( + settings.ISHTAR_SLUGS["document-publisher"] + ) + ] + }, tips=models.get_publisher_label, - associated_model=models.Organization), - validators=[models.valid_id(models.Organization)]) + associated_model=models.Organization, + ), + validators=[models.valid_id(models.Organization)], + ) publishing_year = forms.IntegerField(label=_("Year of publication")) language = forms.ChoiceField(label=_("Language"), choices=[]) isbn = forms.CharField(label=_("ISBN")) @@ -1654,150 +1888,170 @@ class DocumentSelect(HistorySelect): licenses = forms.ChoiceField(label=_("License"), choices=[]) comment = forms.CharField(label=_("Comment")) - additional_information = forms.CharField( - label=_("Additional informations")) + additional_information = forms.CharField(label=_("Additional informations")) duplicate = forms.NullBooleanField(label=_("Has a duplicate")) associated_file__isnull = forms.NullBooleanField(label=_("Has a file?")) image__isnull = forms.NullBooleanField(label=_("Has an image?")) source = forms.IntegerField( label=_("Source"), widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-document'), - associated_model=models.Document), - validators=[models.valid_id(models.Document)]) - source_free_input = forms.CharField( - label=_("Source - free input")) + reverse_lazy("autocomplete-document"), associated_model=models.Document + ), + validators=[models.valid_id(models.Document)], + ) + source_free_input = forms.CharField(label=_("Source - free input")) warehouse_container = forms.IntegerField( - label=_("Warehouse - Container"), required=False, + label=_("Warehouse - Container"), + required=False, widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-container'), - associated_model=Container), - validators=[models.valid_id(Container)]) + reverse_lazy("autocomplete-container"), associated_model=Container + ), + validators=[models.valid_id(Container)], + ) warehouse_container_ref = forms.IntegerField( - label=_("Warehouse - Reference container"), required=False, + label=_("Warehouse - Reference container"), + required=False, widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-container'), - associated_model=Container), - validators=[models.valid_id(Container)]) + reverse_lazy("autocomplete-container"), associated_model=Container + ), + validators=[models.valid_id(Container)], + ) operation = forms.IntegerField( - label=_("Operation"), required=False, + label=_("Operation"), + required=False, widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-operation'), - associated_model=Operation), - validators=[models.valid_id(Operation)]) + reverse_lazy("autocomplete-operation"), associated_model=Operation + ), + validators=[models.valid_id(Operation)], + ) context_record = forms.IntegerField( - label=_("Context record"), required=False, + label=_("Context record"), + required=False, widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-contextrecord'), - associated_model=ContextRecord), - validators=[models.valid_id(ContextRecord)]) + reverse_lazy("autocomplete-contextrecord"), associated_model=ContextRecord + ), + validators=[models.valid_id(ContextRecord)], + ) find_basket = forms.IntegerField( label=_("Basket - Finds"), widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-findbasket'), - associated_model=FindBasket), - validators=[models.valid_id(FindBasket)], required=False) + reverse_lazy("autocomplete-findbasket"), associated_model=FindBasket + ), + validators=[models.valid_id(FindBasket)], + required=False, + ) find = forms.IntegerField( - label=_("Find"), required=False, + label=_("Find"), + required=False, widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-find'), - associated_model=Find), - validators=[models.valid_id(Find)]) - find__denomination = forms.CharField(label=_("Find - denomination"), - required=False) + reverse_lazy("autocomplete-find"), associated_model=Find + ), + validators=[models.valid_id(Find)], + ) + find__denomination = forms.CharField(label=_("Find - denomination"), required=False) containers = forms.IntegerField( - label=_("Container"), required=False, + label=_("Container"), + required=False, widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-container'), - associated_model=Container), - validators=[models.valid_id(Container)]) + reverse_lazy("autocomplete-container"), associated_model=Container + ), + validators=[models.valid_id(Container)], + ) receipt_date__before = forms.DateField( - label=_("Receipt date before"), widget=DatePicker) + label=_("Receipt date before"), widget=DatePicker + ) receipt_date__after = forms.DateField( - label=_("Receipt date after"), widget=DatePicker) + label=_("Receipt date after"), widget=DatePicker + ) creation_date__before = forms.DateField( - label=_("Creation date before"), widget=DatePicker) + label=_("Creation date before"), widget=DatePicker + ) creation_date__after = forms.DateField( - label=_("Creation date after"), widget=DatePicker) + label=_("Creation date after"), widget=DatePicker + ) receipt_date_in_documentation__before = forms.DateField( - label=_("Receipt date before"), widget=DatePicker) + label=_("Receipt date before"), widget=DatePicker + ) receipt_date_in_documentation__after = forms.DateField( - label=_("Receipt date after"), widget=DatePicker) + label=_("Receipt date after"), widget=DatePicker + ) TYPES = [ - FieldType('source_type', models.SourceType), - FieldType('format', models.Format), - FieldType('support', models.SupportType), - FieldType('tag', models.DocumentTag), - FieldType('language', models.Language), - FieldType('licenses', models.LicenseType), + FieldType("source_type", models.SourceType), + FieldType("format", models.Format), + FieldType("support", models.SupportType), + FieldType("tag", models.DocumentTag), + FieldType("language", models.Language), + FieldType("licenses", models.LicenseType), ] PROFILE_FILTER = { - 'context_record': ['context_record'], - 'find': ['find'], - 'warehouse': ['container'] + "context_record": ["context_record"], + "find": ["find"], + "warehouse": ["container"], } class DocumentFormSelection(LockForm, CustomFormSearch): SEARCH_AND_SELECT = True form_label = _("Document search") - associated_models = {'pk': models.Document} - currents = {'pk': models.Document} + associated_models = {"pk": models.Document} + currents = {"pk": models.Document} pk = forms.IntegerField( - label="", required=False, + label="", + required=False, widget=widgets.DataTable( - reverse_lazy('get-document'), DocumentSelect, - models.Document, - gallery=True + reverse_lazy("get-document"), DocumentSelect, models.Document, gallery=True ), - validators=[models.valid_id(models.Document)]) + validators=[models.valid_id(models.Document)], + ) class DocumentFormMultiSelection(LockForm, MultiSearchForm): form_label = _("Document search") - associated_models = {'pks': models.Document} - pk_key = 'pks' + associated_models = {"pks": models.Document} + pk_key = "pks" pk = forms.CharField( - label="", required=False, + label="", + required=False, widget=widgets.DataTable( - reverse_lazy('get-document'), DocumentSelect, + reverse_lazy("get-document"), + DocumentSelect, models.Document, multiple_select=True, - gallery=True + gallery=True, ), - validators=[models.valid_ids(models.Document)]) + validators=[models.valid_ids(models.Document)], + ) class QADocumentFormMulti(QAForm): form_admin_name = _("Document - Quick action - Modify") form_slug = "document-quickaction-modify" - base_models = ['qa_source_type'] + base_models = ["qa_source_type"] associated_models = { - 'qa_source_type': models.SourceType, - 'qa_authors': models.Author, + "qa_source_type": models.SourceType, + "qa_authors": models.Author, } MULTI = True REPLACE_FIELDS = [ - 'qa_source_type', - 'qa_creation_date', + "qa_source_type", + "qa_creation_date", ] - qa_source_type = forms.ChoiceField( - label=_("Source type"), required=False - ) + qa_source_type = forms.ChoiceField(label=_("Source type"), required=False) qa_authors = widgets.ModelJQueryAutocompleteField( - model=models.Author, label=_("Author"), new=True, - required=False) + model=models.Author, label=_("Author"), new=True, required=False + ) qa_creation_date = forms.DateField( - label=_("Creation date"), widget=DatePicker, required=False) + label=_("Creation date"), widget=DatePicker, required=False + ) TYPES = [ - FieldType('qa_source_type', models.SourceType), + FieldType("qa_source_type", models.SourceType), ] def _get_qa_authors(self, value): @@ -1809,28 +2063,25 @@ class QADocumentFormMulti(QAForm): class QADocumentDuplicateForm(IshtarForm): - qa_title = forms.CharField(label=_("Reference"), max_length=500, - required=False) - qa_source_type = forms.ChoiceField(label=_("Type"), choices=[], - required=False) + qa_title = forms.CharField(label=_("Reference"), max_length=500, required=False) + qa_source_type = forms.ChoiceField(label=_("Type"), choices=[], required=False) TYPES = [ - FieldType('qa_source_type', models.SourceType), + FieldType("qa_source_type", models.SourceType), ] def __init__(self, *args, **kwargs): self.user = None - if 'user' in kwargs: - self.user = kwargs.pop('user') - if hasattr(self.user, 'ishtaruser'): + if "user" in kwargs: + self.user = kwargs.pop("user") + if hasattr(self.user, "ishtaruser"): self.user = self.user.ishtaruser - self.document = kwargs.pop('items')[0] + self.document = kwargs.pop("items")[0] super(QADocumentDuplicateForm, self).__init__(*args, **kwargs) - self.fields['qa_title'].initial = self.document.title + str( - _(" - duplicate")) + self.fields["qa_title"].initial = self.document.title + str(_(" - duplicate")) if self.document.source_type: - self.fields['qa_source_type'].initial = self.document.source_type.pk + self.fields["qa_source_type"].initial = self.document.source_type.pk for related_key in models.Document.RELATED_MODELS_ALT: related = getattr(self.document, related_key) @@ -1839,8 +2090,12 @@ class QADocumentDuplicateForm(IshtarForm): model = models.Document._meta.get_field(related_key).related_model initial = [item.pk for item in related.all()] self.fields["qa_" + related_key] = widgets.Select2MultipleField( - model=model, remote=True, label=model._meta.verbose_name_plural, - required=False, long_widget=True, initial=initial + model=model, + remote=True, + label=model._meta.verbose_name_plural, + required=False, + long_widget=True, + initial=initial, ) def save(self): @@ -1850,7 +2105,8 @@ class QADocumentDuplicateForm(IshtarForm): if self.cleaned_data.get("qa_source_type", None): try: data["source_type"] = models.SourceType.objects.get( - pk=int(self.cleaned_data["qa_source_type"]), available=True) + pk=int(self.cleaned_data["qa_source_type"]), available=True + ) except models.SourceType.DoesNotExist: return new = self.document.duplicate_item(self.user, data=data) @@ -1872,37 +2128,39 @@ class QADocumentPackagingForm(IshtarForm): container = forms.IntegerField( label=_("Container"), widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-container'), - associated_model=Container, new=True), - validators=[models.valid_id(Container)]) + reverse_lazy("autocomplete-container"), associated_model=Container, new=True + ), + validators=[models.valid_id(Container)], + ) container_to_change = forms.ChoiceField( - label=_("Change "), required=True, + label=_("Change "), + required=True, choices=( - ('current-and-reference', _("current and reference containers")), - ('reference', _("the reference container")), - ('current', _("the current container")), - ) + ("current-and-reference", _("current and reference containers")), + ("reference", _("the reference container")), + ("current", _("the current container")), + ), ) def __init__(self, *args, **kwargs): self.confirm = False self.user = None - if 'user' in kwargs: - self.user = kwargs.pop('user') - if hasattr(self.user, 'ishtaruser'): + if "user" in kwargs: + self.user = kwargs.pop("user") + if hasattr(self.user, "ishtaruser"): self.user = self.user.ishtaruser - self.items = kwargs.pop('items') + self.items = kwargs.pop("items") super(QADocumentPackagingForm, self).__init__(*args, **kwargs) def save(self, items, user): - container = Container.objects.get(pk=self.cleaned_data['container']) - container_to_change = self.cleaned_data.get('container_to_change', '') + container = Container.objects.get(pk=self.cleaned_data["container"]) + container_to_change = self.cleaned_data.get("container_to_change", "") container_attrs = [] - if container_to_change in ('reference', 'current-and-reference'): - container_attrs.append('container_ref') - if container_to_change in ('current', 'current-and-reference'): - container_attrs.append('container') + if container_to_change in ("reference", "current-and-reference"): + container_attrs.append("container_ref") + if container_to_change in ("current", "current-and-reference"): + container_attrs.append("container") for document in items: changed = False for container_attr in container_attrs: @@ -1917,11 +2175,11 @@ class QADocumentPackagingForm(IshtarForm): class QALockForm(forms.Form): action = forms.ChoiceField( - label=_("Action"), choices=(('lock', _("Lock")), - ('unlock', _("Unlock")))) + label=_("Action"), choices=(("lock", _("Lock")), ("unlock", _("Unlock"))) + ) def __init__(self, *args, **kwargs): - self.items = kwargs.pop('items') + self.items = kwargs.pop("items") super(QALockForm, self).__init__(*args, **kwargs) def save(self, items, user): @@ -1937,6 +2195,7 @@ class SourceDeletionForm(FinalForm): confirm_msg = " " confirm_end_msg = _("Would you like to delete this documentation?") + ###################### # Authors management # ###################### @@ -1944,19 +2203,23 @@ class SourceDeletionForm(FinalForm): class AuthorForm(ManageOldType, NewItemForm): form_label = _("Author") - associated_models = {'person': models.Person, - 'author_type': models.AuthorType} + associated_models = {"person": models.Person, "author_type": models.AuthorType} person = forms.IntegerField( widget=widgets.JQueryAutoComplete( - "/" + settings.URL_PATH + 'autocomplete-person', - associated_model=models.Person, new=True), - validators=[models.valid_id(models.Person)], label=_("Person")) + "/" + settings.URL_PATH + "autocomplete-person", + associated_model=models.Person, + new=True, + ), + validators=[models.valid_id(models.Person)], + label=_("Person"), + ) author_type = forms.ChoiceField(label=_("Author type"), choices=[]) def __init__(self, *args, **kwargs): super(AuthorForm, self).__init__(*args, **kwargs) - self.fields['author_type'].choices = models.AuthorType.get_types( - initial=self.init_data.get('author_type')) + self.fields["author_type"].choices = models.AuthorType.get_types( + initial=self.init_data.get("author_type") + ) self.limit_fields() def clean(self): @@ -1964,15 +2227,15 @@ class AuthorForm(ManageOldType, NewItemForm): author_type_id = self.cleaned_data.get("author_type", None) if not person_id or not author_type_id: return self.cleaned_data - if models.Author.objects.filter(author_type_id=author_type_id, - person_id=person_id).count(): + if models.Author.objects.filter( + author_type_id=author_type_id, person_id=person_id + ).count(): raise forms.ValidationError(_("This author already exist.")) def save(self, user): dct = self.cleaned_data - dct['author_type'] = models.AuthorType.objects.get( - pk=dct['author_type']) - dct['person'] = models.Person.objects.get(pk=dct['person']) + dct["author_type"] = models.AuthorType.objects.get(pk=dct["author_type"]) + dct["person"] = models.Person.objects.get(pk=dct["person"]) new_item = models.Author(**dct) new_item.save() return new_item @@ -1980,89 +2243,103 @@ class AuthorForm(ManageOldType, NewItemForm): class AuthorFormSelection(forms.Form): form_label = _("Author selection") - base_model = 'author' - associated_models = {'author': models.Author} + base_model = "author" + associated_models = {"author": models.Author} author = forms.IntegerField( required=False, widget=widgets.JQueryAutoComplete( - "/" + settings.URL_PATH + 'autocomplete-author', - associated_model=models.Author, new=True), - validators=[models.valid_id(models.Author)], label=_("Author")) + "/" + settings.URL_PATH + "autocomplete-author", + associated_model=models.Author, + new=True, + ), + validators=[models.valid_id(models.Author)], + label=_("Author"), + ) class AuthorFormSet(FormSet): def clean(self): """Checks that no author are duplicated.""" - return self.check_duplicate(('author',), - _("There are identical authors.")) + return self.check_duplicate(("author",), _("There are identical authors.")) -AuthorFormset = formset_factory(AuthorFormSelection, can_delete=True, - formset=AuthorFormSet) +AuthorFormset = formset_factory( + AuthorFormSelection, can_delete=True, formset=AuthorFormSet +) AuthorFormset.form_label = _("Authors") AuthorFormset.form_admin_name = _("Authors") AuthorFormset.form_slug = "authors" class SearchQueryForm(forms.Form): - query = forms.CharField(max_length=None, label=_("Query"), initial='*', - widget=forms.HiddenInput) - search_query = forms.ChoiceField(label="", required=False, - choices=[]) + query = forms.CharField( + max_length=None, label=_("Query"), initial="*", widget=forms.HiddenInput + ) + search_query = forms.ChoiceField(label="", required=False, choices=[]) label = forms.CharField(label="", max_length=None, required=False) is_alert = forms.BooleanField(label=_("Is an alert"), required=False) create_or_update = forms.ChoiceField( - choices=(('create', _("Create")), - ('update', _("Update"))), initial='create') + choices=(("create", _("Create")), ("update", _("Update"))), initial="create" + ) def __init__(self, profile, content_type, *args, **kwargs): self.profile = profile self.content_type = content_type super(SearchQueryForm, self).__init__(*args, **kwargs) - self.fields['search_query'].choices = [ - (c.pk, c.label) for c in models.SearchQuery.objects.filter( - content_type=content_type, profile=profile).all()] - if not self.fields['search_query'].choices: - self.fields.pop('search_query') + self.fields["search_query"].choices = [ + (c.pk, c.label) + for c in models.SearchQuery.objects.filter( + content_type=content_type, profile=profile + ).all() + ] + if not self.fields["search_query"].choices: + self.fields.pop("search_query") def clean(self): data = self.cleaned_data - if data['create_or_update'] == 'create' and not data['label']: - raise forms.ValidationError(_("A label is required for a new " - "search query.")) - elif data['create_or_update'] == 'update': - if not data['search_query']: - raise forms.ValidationError(_("Select the search query to " - "update")) + if data["create_or_update"] == "create" and not data["label"]: + raise forms.ValidationError( + _("A label is required for a new " "search query.") + ) + elif data["create_or_update"] == "update": + if not data["search_query"]: + raise forms.ValidationError(_("Select the search query to " "update")) q = models.SearchQuery.objects.filter( - profile=self.profile, content_type=self.content_type, - pk=data['search_query']) + profile=self.profile, + content_type=self.content_type, + pk=data["search_query"], + ) if not q.count(): raise forms.ValidationError(_("Query does not exist.")) return data def save(self): data = self.cleaned_data - if data['create_or_update'] == 'create': + if data["create_or_update"] == "create": sq = models.SearchQuery.objects.create( - label=data['label'], query=data['query'], profile=self.profile, - content_type=self.content_type, is_alert=data['is_alert']) + label=data["label"], + query=data["query"], + profile=self.profile, + content_type=self.content_type, + is_alert=data["is_alert"], + ) else: try: sq = models.SearchQuery.objects.get( - profile=self.profile, content_type=self.content_type, - pk=data['search_query']) + profile=self.profile, + content_type=self.content_type, + pk=data["search_query"], + ) except models.SearchQuery.DoesNotExist: raise forms.ValidationError(_("Query does not exist.")) - sq.query = data['query'] + sq.query = data["query"] sq.save() return sq class QRSearchForm(forms.Form): - query = forms.CharField(max_length=None, label=_("Query"), initial='*') - current_url = forms.CharField(max_length=None, label="", - widget=forms.HiddenInput()) + query = forms.CharField(max_length=None, label=_("Query"), initial="*") + current_url = forms.CharField(max_length=None, label="", widget=forms.HiddenInput()) def save(self): data = self.cleaned_data @@ -2073,13 +2350,11 @@ class QRSearchForm(forms.Form): url += "?stored_search=" + quote(data["query"]) tiny_url = models.TinyUrl.objects.create(link=url) - short_url = base_url + reverse('tiny-redirect', - args=[tiny_url.get_short_id()]) + short_url = base_url + reverse("tiny-redirect", args=[tiny_url.get_short_id()]) qr = pyqrcode.create(short_url, version=settings.ISHTAR_QRCODE_VERSION) tmpdir = tempfile.mkdtemp("-qrcode") - date = datetime.datetime.today().isoformat().replace( - ":", "-").replace(".", "") - base_filename = '{}-qrcode.png'.format(date) + date = datetime.datetime.today().isoformat().replace(":", "-").replace(".", "") + base_filename = "{}-qrcode.png".format(date) filename = os.path.join(tmpdir, base_filename) qr.png(filename, scale=settings.ISHTAR_QRCODE_SCALE) diff --git a/ishtar_common/ishtar_menu.py b/ishtar_common/ishtar_menu.py index 1e64a80f2..0574d0852 100644 --- a/ishtar_common/ishtar_menu.py +++ b/ishtar_common/ishtar_menu.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2010-2016 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> @@ -26,125 +26,209 @@ from . import models # be careful: each access_controls must be relevant with check_rights in urls MENU_SECTIONS = [ - (1, SectionItem('home', _("Home"), childs=[])), - (5, SectionItem('admin', _("Administration"), - childs=[ + (1, SectionItem("home", _("Home"), childs=[])), + ( + 5, SectionItem( - 'account', _("Account"), - childs=[MenuItem('account_management', _("Addition/modification"), - model=models.IshtarUser, - access_controls=['administrator', ]), - MenuItem('account_deletion', _("Deletion"), - model=models.IshtarUser, - access_controls=['administrator', ]), ]), - MenuItem('admin-globalvar', _("Global variables"), - model=models.GlobalVar, - access_controls=['administrator', ]), - ]) + "admin", + _("Administration"), + childs=[ + SectionItem( + "account", + _("Account"), + childs=[ + MenuItem( + "account_management", + _("Addition/modification"), + model=models.IshtarUser, + access_controls=[ + "administrator", + ], + ), + MenuItem( + "account_deletion", + _("Deletion"), + model=models.IshtarUser, + access_controls=[ + "administrator", + ], + ), + ], + ), + MenuItem( + "admin-globalvar", + _("Global variables"), + model=models.GlobalVar, + access_controls=[ + "administrator", + ], + ), + ], + ), ), - (10, SectionItem('administration', _("Directory"), - childs=[ + ( + 10, SectionItem( - 'person', _("Person"), + "administration", + _("Directory"), + childs=[ + SectionItem( + "person", + _("Person"), + childs=[ + MenuItem( + "person_search", + _("Search"), + model=models.Person, + access_controls=["add_person"], + ), + MenuItem( + "person_creation", + _("Creation"), + model=models.Person, + access_controls=["add_person"], + ), + MenuItem( + "person_modification", + _("Modification"), + model=models.Person, + access_controls=["change_person", "change_own_person"], + ), + MenuItem( + "person-merge", + _("Automatic merge"), + model=models.Person, + access_controls=["administrator"], + ), + MenuItem( + "person-manual-merge", + _("Manual merge"), + model=models.Person, + access_controls=["administrator"], + ), + MenuItem( + "person_deletion", + _("Deletion"), + model=models.Person, + access_controls=["change_person", "change_own_person"], + ), + ], + ), + SectionItem( + "organization", + _("Organization"), + childs=[ + MenuItem( + "organization_search", + _("Search"), + model=models.Organization, + access_controls=[ + "add_organization", + "add_own_organization", + ], + ), + MenuItem( + "organization_creation", + _("Creation"), + model=models.Organization, + access_controls=[ + "add_organization", + "add_own_organization", + ], + ), + MenuItem( + "organization_modification", + _("Modification"), + model=models.Organization, + access_controls=[ + "change_organization", + "change_own_organization", + ], + ), + MenuItem( + "organization-merge", + _("Automatic merge"), + model=models.Organization, + access_controls=["administrator"], + ), + MenuItem( + "orga-manual-merge", + _("Manual merge"), + model=models.Organization, + access_controls=["administrator"], + ), + MenuItem( + "organization_deletion", + _("Deletion"), + model=models.Organization, + access_controls=[ + "change_organization", + "change_own_organization", + ], + ), + ], + ), + ], + ), + ), + ( + 15, + SectionItem( + "imports", + _("Imports"), childs=[ MenuItem( - 'person_search', _("Search"), - model=models.Person, - access_controls=['add_person']), - MenuItem( - 'person_creation', _("Creation"), - model=models.Person, - access_controls=['add_person']), - MenuItem( - 'person_modification', _("Modification"), - model=models.Person, - access_controls=['change_person', 'change_own_person']), - MenuItem( - 'person-merge', _("Automatic merge"), - model=models.Person, - access_controls=['administrator']), + "import-new", + _("New import"), + model=models.Import, + access_controls=["change_import"], + ), MenuItem( - 'person-manual-merge', _("Manual merge"), - model=models.Person, - access_controls=['administrator']), + "import-list", + _("Current imports"), + model=models.Import, + access_controls=["change_import"], + ), MenuItem( - 'person_deletion', _("Deletion"), - model=models.Person, - access_controls=['change_person', 'change_own_person']), - ]), + "import-list-old", + _("Old imports"), + model=models.Import, + access_controls=["change_import"], + ), + ], + ), + ), + ( + 250, SectionItem( - 'organization', _("Organization"), + "document", + _("Documentation / Images"), childs=[ MenuItem( - 'organization_search', _("Search"), - model=models.Organization, - access_controls=['add_organization', - 'add_own_organization']), - MenuItem( - 'organization_creation', _("Creation"), - model=models.Organization, - access_controls=['add_organization', - 'add_own_organization']), - MenuItem( - 'organization_modification', _("Modification"), - model=models.Organization, - access_controls=['change_organization', - 'change_own_organization']), + "document/search", + _("Search"), + model=models.Document, + access_controls=["view_document", "view_own_document"], + ), MenuItem( - 'organization-merge', _("Automatic merge"), - model=models.Organization, - access_controls=['administrator']), + "document/create", + _("Creation"), + model=models.Document, + access_controls=["add_document", "add_own_document"], + ), MenuItem( - 'orga-manual-merge', _("Manual merge"), - model=models.Organization, - access_controls=['administrator']), + "document/edit", + _("Modification"), + model=models.Document, + access_controls=["change_document", "change_own_document"], + ), MenuItem( - 'organization_deletion', _("Deletion"), - model=models.Organization, - access_controls=['change_organization', - 'change_own_organization']), - ]), - ]) + "document/delete", + _("Deletion"), + model=models.Document, + access_controls=["change_document", "change_own_document"], + ), + ], + ), ), - (15, SectionItem( - 'imports', _("Imports"), - childs=[ - MenuItem( - 'import-new', _("New import"), - model=models.Import, - access_controls=['change_import']), - MenuItem( - 'import-list', _("Current imports"), - model=models.Import, - access_controls=['change_import']), - MenuItem( - 'import-list-old', _("Old imports"), - model=models.Import, - access_controls=['change_import']), - ])), - (250, SectionItem( - 'document', _("Documentation / Images"), - childs=[ - MenuItem('document/search', - _("Search"), - model=models.Document, - access_controls=['view_document', - 'view_own_document']), - MenuItem('document/create', - _("Creation"), - model=models.Document, - access_controls=['add_document', - 'add_own_document']), - MenuItem('document/edit', - _("Modification"), - model=models.Document, - access_controls=['change_document', - 'change_own_document']), - MenuItem('document/delete', - _("Deletion"), - model=models.Document, - access_controls=['change_document', - 'change_own_document']), - ]) - ) ] diff --git a/ishtar_common/menu_base.py b/ishtar_common/menu_base.py index d88c5c7c9..e8470787a 100644 --- a/ishtar_common/menu_base.py +++ b/ishtar_common/menu_base.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2012-2013 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> @@ -21,8 +21,7 @@ from ishtar_common.models import get_current_profile class SectionItem: - def __init__(self, idx, label, childs=None, profile_restriction=None, - css=''): + def __init__(self, idx, label, childs=None, profile_restriction=None, css=""): self.idx = idx self._label = label self.childs = childs or [] @@ -67,15 +66,24 @@ class SectionItem: if user: self.available = self.can_be_available(user, session=session) for child in self.childs: - selected = child.set_items(user, items, current_action, - session=session) or selected + selected = ( + child.set_items(user, items, current_action, session=session) + or selected + ) items[child.idx] = child return selected class MenuItem: - def __init__(self, idx, label, model=None, access_controls=None, - profile_restriction=None, css=''): + def __init__( + self, + idx, + label, + model=None, + access_controls=None, + profile_restriction=None, + css="", + ): self.idx = idx self.label = label self.model = model @@ -98,12 +106,11 @@ class MenuItem: return False if not self.access_controls: return True - if not hasattr(user, 'ishtaruser'): + if not hasattr(user, "ishtaruser"): return False for access_control in self.access_controls: # check by profile - if user.ishtaruser.person.has_right(access_control, - session=session): + if user.ishtaruser.person.has_right(access_control, session=session): return True return False @@ -112,11 +119,12 @@ class MenuItem: return False if not self.access_controls: return True - if not hasattr(user, 'ishtaruser'): + if not hasattr(user, "ishtaruser"): return False for access_control in self.access_controls: if user.ishtaruser.person.has_right( - access_control, obj=obj, session=session): + access_control, obj=obj, session=session + ): return True return False diff --git a/ishtar_common/menus.py b/ishtar_common/menus.py index 770990a5e..466ed18c4 100644 --- a/ishtar_common/menus.py +++ b/ishtar_common/menus.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2010-2017 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> @@ -33,14 +33,13 @@ from django.contrib.auth.models import User _extra_menus = [] # collect menu from INSTALLED_APPS for app in settings.INSTALLED_APPS: - mod = __import__(app, fromlist=['ishtar_menu']) - if hasattr(mod, 'ishtar_menu'): - menu = getattr(mod, 'ishtar_menu') + mod = __import__(app, fromlist=["ishtar_menu"]) + if hasattr(mod, "ishtar_menu"): + menu = getattr(mod, "ishtar_menu") _extra_menus += menu.MENU_SECTIONS # sort -__section_items = [mnu for order, mnu in sorted(_extra_menus, - key=lambda x:x[0])] +__section_items = [mnu for order, mnu in sorted(_extra_menus, key=lambda x: x[0])] # regroup menus _section_items, __keys = [], [] for section_item in __section_items: @@ -81,7 +80,8 @@ class Menu: Force cache deletion and reinitialization of menu for all """ lst_cache_key = "{}-{}".format( - settings.PROJECT_SLUG, 'menu_updated_list', + settings.PROJECT_SLUG, + "menu_updated_list", ) lst_ids = cache.get(lst_cache_key) if not lst_ids: @@ -94,7 +94,8 @@ class Menu: Stock updated information in cache """ lst_cache_key = "{}-{}".format( - settings.PROJECT_SLUG, 'menu_updated_list', + settings.PROJECT_SLUG, + "menu_updated_list", ) lst_ids = cache.get(lst_cache_key) if not lst_ids: @@ -120,13 +121,14 @@ class Menu: self.user = User.objects.get(pk=user_id) except User.DoesNotExist: return - cache_key = "{}-{}-{}".format( - settings.PROJECT_SLUG, 'menu_updated', - user_id - ) + cache_key = "{}-{}-{}".format(settings.PROJECT_SLUG, "menu_updated", user_id) menu_updated = cache.get(cache_key) - if not force and menu_updated and self.initialized \ - and self.initialized == menu_updated: + if ( + not force + and menu_updated + and self.initialized + and self.initialized == menu_updated + ): return self.set_menu_updated_key(cache_key, user_id) self.items = {} @@ -143,19 +145,18 @@ class Menu: sub_childs.pop(len(main_menu.childs) - s_idx - 1) continue self.items_by_idx[child.idx] = child - if hasattr(child, 'childs'): + if hasattr(child, "childs"): sub_sub_childs = child.childs[:] for ss_idx, subchild in enumerate(reversed(child.childs)): - if not subchild.can_be_available( - self.user, self.session): + if not subchild.can_be_available(self.user, self.session): sub_sub_childs.pop(len(child.childs) - ss_idx - 1) continue self.items_by_idx[subchild.idx] = subchild child.childs = sub_sub_childs main_menu.childs = sub_childs selected = main_menu.set_items( - self.user, self.items, - self.current_action, session=self.session) + self.user, self.items, self.current_action, session=self.session + ) if selected: self.selected_idx = idx self.childs = childs @@ -166,7 +167,7 @@ class Menu: # current_sections, current_subsections, etc. are list of: # (label, url, has_children) - self.current_section = '' + self.current_section = "" self.current_sections = [] self.current_subsection = "" @@ -188,8 +189,8 @@ class Menu: for menu_item in section.childs: if not menu_item.available: continue - if not hasattr(menu_item, 'childs') or not menu_item.childs: - item_url = reverse('action', args=[menu_item.idx]) + if not hasattr(menu_item, "childs") or not menu_item.childs: + item_url = reverse("action", args=[menu_item.idx]) if not section_url: section_url = item_url subsections.append([menu_item.label, item_url, False]) @@ -205,7 +206,7 @@ class Menu: for menu_subitem in menu_item.childs: if not menu_subitem.available: continue - item_url = reverse('action', args=[menu_subitem.idx]) + item_url = reverse("action", args=[menu_subitem.idx]) if not section_url: section_url = item_url if not subsection_url: @@ -226,6 +227,6 @@ class Menu: self.current_subsections = subsections if not section_url: section_url = "/" - self.current_sections.append([section.label, section_url, - bool(subsections)]) - + self.current_sections.append( + [section.label, section_url, bool(subsections)] + ) diff --git a/ishtar_common/model_merging.py b/ishtar_common/model_merging.py index 6b839a143..0e1d34c58 100644 --- a/ishtar_common/model_merging.py +++ b/ishtar_common/model_merging.py @@ -6,22 +6,26 @@ from django.db.models import Model from django.contrib.contenttypes.fields import GenericForeignKey from django.core.exceptions import ObjectDoesNotExist -from ishtar_common.utils import get_all_related_many_to_many_objects, \ - get_all_related_objects +from ishtar_common.utils import ( + get_all_related_many_to_many_objects, + get_all_related_objects, +) def get_models(): _apps = apps.app_configs.items() models = [] for app_name, app_config in _apps: - models += [apps.get_model(app_name, m) - for m in apps.get_app_config(app_name).models] + models += [ + apps.get_model(app_name, m) for m in apps.get_app_config(app_name).models + ] return models @transaction.atomic -def merge_model_objects(primary_object, alias_objects=None, keep_old=False, - exclude_fields=None): +def merge_model_objects( + primary_object, alias_objects=None, keep_old=False, exclude_fields=None +): """ Use this function to merge model objects (i.e. Users, Organizations, etc.) and migrate all of the related fields from the alias objects to the @@ -38,7 +42,7 @@ def merge_model_objects(primary_object, alias_objects=None, keep_old=False, if not exclude_fields: exclude_fields = [] - MERGE_FIELDS = ('merge_candidate', 'merge_exclusion') + MERGE_FIELDS = ("merge_candidate", "merge_exclusion") MERGE_STRING_FIELDS = [] if getattr(primary_object, "MERGE_STRING_FIELDS", None): @@ -52,11 +56,11 @@ def merge_model_objects(primary_object, alias_objects=None, keep_old=False, primary_class = primary_object.__class__ if not issubclass(primary_class, Model): - raise TypeError('Only django.db.models.Model subclasses can be merged') + raise TypeError("Only django.db.models.Model subclasses can be merged") for alias_object in alias_objects: if not isinstance(alias_object, primary_class): - raise TypeError('Only models of same class can be merged') + raise TypeError("Only models of same class can be merged") # Get a list of all GenericForeignKeys in all models # TODO: this is a bit of a hack, since the generics framework should @@ -64,8 +68,9 @@ def merge_model_objects(primary_object, alias_objects=None, keep_old=False, # method to the ForeignKey field for accessing the generic related fields. generic_fields = [] for model in get_models(): - for field_name, field in filter(lambda x: isinstance( - x[1], GenericForeignKey), model.__dict__.items()): + for field_name, field in filter( + lambda x: isinstance(x[1], GenericForeignKey), model.__dict__.items() + ): generic_fields.append(field) blank_local_fields = set() @@ -74,7 +79,7 @@ def merge_model_objects(primary_object, alias_objects=None, keep_old=False, # string fields with only spaces are empty fields if isinstance(value, str): value = value.strip() - if value in [None, '']: + if value in [None, ""]: blank_local_fields.add(field.attname) # Loop through all alias objects and migrate their data to the primary @@ -89,14 +94,16 @@ def merge_model_objects(primary_object, alias_objects=None, keep_old=False, obj_varname = related_object.field.name if obj_varname in exclude_fields: continue - if getattr(related_object.field, "related_model", None) and \ - not related_object.related_model._meta.managed: + if ( + getattr(related_object.field, "related_model", None) + and not related_object.related_model._meta.managed + ): continue try: related_objects = getattr(alias_object, alias_varname) except ObjectDoesNotExist: continue - if not hasattr(related_objects, 'all'): + if not hasattr(related_objects, "all"): # one to one field setattr(related_objects, obj_varname, primary_object) related_objects.save() @@ -107,8 +114,7 @@ def merge_model_objects(primary_object, alias_objects=None, keep_old=False, # Migrate all many to many references from alias object to primary # object. - related_many_objects = \ - get_all_related_many_to_many_objects(alias_object) + related_many_objects = get_all_related_many_to_many_objects(alias_object) related_many_object_names = set() for related_many_object in related_many_objects: alias_varname = related_many_object.get_accessor_name() @@ -118,13 +124,11 @@ def merge_model_objects(primary_object, alias_objects=None, keep_old=False, if alias_varname is not None: # standard case - q_related_many_objects = getattr( - alias_object, alias_varname).all() + q_related_many_objects = getattr(alias_object, alias_varname).all() related_many_object_names.add(alias_varname) else: # special case, symmetrical relation, no reverse accessor - q_related_many_objects = getattr( - alias_object, obj_varname).all() + q_related_many_objects = getattr(alias_object, obj_varname).all() related_many_object_names.add(obj_varname) for obj in q_related_many_objects.all(): getattr(obj, obj_varname).remove(alias_object) @@ -134,8 +138,10 @@ def merge_model_objects(primary_object, alias_objects=None, keep_old=False, # object. for many_to_many_object in alias_object._meta.many_to_many: alias_varname = many_to_many_object.get_attname() - if alias_varname in related_many_object_names or \ - alias_varname in MERGE_FIELDS: + if ( + alias_varname in related_many_object_names + or alias_varname in MERGE_FIELDS + ): continue many_to_many_objects = getattr(alias_object, alias_varname).all() @@ -150,21 +156,21 @@ def merge_model_objects(primary_object, alias_objects=None, keep_old=False, for field in generic_fields: filter_kwargs = {} filter_kwargs[field.fk_field] = alias_object._get_pk_val() - filter_kwargs[field.ct_field] = field.get_content_type( - alias_object) - for generic_related_object in field.model.objects.filter( - **filter_kwargs): + filter_kwargs[field.ct_field] = field.get_content_type(alias_object) + for generic_related_object in field.model.objects.filter(**filter_kwargs): if field.name in exclude_fields: continue setattr(generic_related_object, field.name, primary_object) generic_related_object.save() for field_name in MERGE_STRING_FIELDS: - if getattr(primary_object, field_name) and \ - getattr(alias_object, field_name): + if getattr(primary_object, field_name) and getattr( + alias_object, field_name + ): val = "{} ; {}".format( getattr(primary_object, field_name), - getattr(alias_object, field_name)) + getattr(alias_object, field_name), + ) if field_name in exclude_fields: continue setattr(primary_object, field_name, val) @@ -174,7 +180,7 @@ def merge_model_objects(primary_object, alias_objects=None, keep_old=False, filled_up = set() for field_name in blank_local_fields: val = getattr(alias_object, field_name) - if val not in [None, '']: + if val not in [None, ""]: setattr(primary_object, field_name, val) filled_up.add(field_name) blank_local_fields -= filled_up diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 56c65c1f3..88aa993c6 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -54,8 +54,11 @@ from django.contrib.postgres.fields import JSONField from django.contrib.postgres.indexes import GinIndex from django.contrib.sites.models import Site from django.core.cache import cache -from django.core.exceptions import ObjectDoesNotExist, ValidationError, \ - MultipleObjectsReturned +from django.core.exceptions import ( + ObjectDoesNotExist, + ValidationError, + MultipleObjectsReturned, +) from django.core.files.base import ContentFile from django.core.files.uploadedfile import SimpleUploadedFile from django.core.urlresolvers import reverse @@ -65,55 +68,136 @@ from django.db.utils import DatabaseError from django.template import Context, Template from django.template.defaultfilters import slugify from django.utils.functional import lazy -from ishtar_common.utils import ugettext_lazy as _, ugettext, \ - pgettext_lazy, get_generated_id, get_current_profile, duplicate_item, \ - get_image_path +from ishtar_common.utils import ( + ugettext_lazy as _, + ugettext, + pgettext_lazy, + get_generated_id, + get_current_profile, + duplicate_item, + get_image_path, +) from ishtar_common.utils_secretary import IshtarSecretaryRenderer -from ishtar_common.alternative_configs import ALTERNATE_CONFIGS, \ - ALTERNATE_CONFIGS_CHOICES +from ishtar_common.alternative_configs import ( + ALTERNATE_CONFIGS, + ALTERNATE_CONFIGS_CHOICES, +) from ishtar_common.data_importer import pre_importer_action -from ishtar_common.model_managers import SlugModelManager, ExternalIdManager, \ - UUIDModelManager +from ishtar_common.model_managers import ( + SlugModelManager, + ExternalIdManager, + UUIDModelManager, +) from ishtar_common.model_merging import merge_model_objects -from ishtar_common.models_imports import ImporterModel, ImporterType, \ - ImporterDefault, ImporterDefaultValues, ImporterColumn, \ - ImporterDuplicateField, Regexp, ImportTarget, TargetKey, FormaterType, \ - Import, TargetKeyGroup, ValueFormater -from ishtar_common.utils import get_cache, create_slug, \ - get_all_field_names, cached_label_changed, \ - generate_relation_graph, max_size_help - -from ishtar_common.models_common import GeneralType, HierarchicalType, \ - BaseHistorizedItem, LightHistorizedItem, FullSearch, \ - SearchAltName, OwnPerms, Cached, \ - Address, post_save_cache, TemplateItem, SpatialReferenceSystem, \ - DashboardFormItem, document_attached_changed, SearchAltName, \ - DynamicRequest, GeoItem, CompleteIdentifierItem, SearchVectorConfig, \ - DocumentItem, QuickAction, MainItem, Merge, ShortMenuItem, Town, \ - ImageContainerModel, StatisticItem, CachedGen, CascasdeUpdate, \ - Department, State +from ishtar_common.models_imports import ( + ImporterModel, + ImporterType, + ImporterDefault, + ImporterDefaultValues, + ImporterColumn, + ImporterDuplicateField, + Regexp, + ImportTarget, + TargetKey, + FormaterType, + Import, + TargetKeyGroup, + ValueFormater, +) +from ishtar_common.utils import ( + get_cache, + create_slug, + get_all_field_names, + cached_label_changed, + generate_relation_graph, + max_size_help, +) + +from ishtar_common.models_common import ( + GeneralType, + HierarchicalType, + BaseHistorizedItem, + LightHistorizedItem, + FullSearch, + SearchAltName, + OwnPerms, + Cached, + Address, + post_save_cache, + TemplateItem, + SpatialReferenceSystem, + DashboardFormItem, + document_attached_changed, + SearchAltName, + DynamicRequest, + GeoItem, + CompleteIdentifierItem, + SearchVectorConfig, + DocumentItem, + QuickAction, + MainItem, + Merge, + ShortMenuItem, + Town, + ImageContainerModel, + StatisticItem, + CachedGen, + CascasdeUpdate, + Department, + State, +) __all__ = [ - 'ImporterModel', 'ImporterType', 'ImporterDefault', 'ImporterDefaultValues', - 'ImporterColumn', 'ImporterDuplicateField', 'Regexp', 'ImportTarget', - 'TargetKey', 'FormaterType', 'Import', 'TargetKeyGroup', 'ValueFormater', - 'Organization', 'Person', 'valid_id', 'Town', 'SpatialReferenceSystem', - 'OrganizationType', 'Document', 'GeneralType', 'get_generated_id', - 'LightHistorizedItem', 'OwnPerms', 'Address', 'post_save_cache', - 'DashboardFormItem', 'ShortMenuItem', 'document_attached_changed', - 'SearchAltName', 'DynamicRequest', 'GeoItem', - 'SearchVectorConfig', 'DocumentItem', 'CachedGen', 'StatisticItem', - 'CascasdeUpdate', 'Department', 'State', 'CompleteIdentifierItem' + "ImporterModel", + "ImporterType", + "ImporterDefault", + "ImporterDefaultValues", + "ImporterColumn", + "ImporterDuplicateField", + "Regexp", + "ImportTarget", + "TargetKey", + "FormaterType", + "Import", + "TargetKeyGroup", + "ValueFormater", + "Organization", + "Person", + "valid_id", + "Town", + "SpatialReferenceSystem", + "OrganizationType", + "Document", + "GeneralType", + "get_generated_id", + "LightHistorizedItem", + "OwnPerms", + "Address", + "post_save_cache", + "DashboardFormItem", + "ShortMenuItem", + "document_attached_changed", + "SearchAltName", + "DynamicRequest", + "GeoItem", + "SearchVectorConfig", + "DocumentItem", + "CachedGen", + "StatisticItem", + "CascasdeUpdate", + "Department", + "State", + "CompleteIdentifierItem", ] logger = logging.getLogger(__name__) def post_save_user(sender, **kwargs): - user = kwargs['instance'] + user = kwargs["instance"] if kwargs["created"]: try: @@ -131,85 +215,106 @@ class ValueGetter(object): COL_LABELS = {} GET_VALUES_EXTRA = [] GET_VALUES_EXCLUDE_FIELDS = [ - 'search_vector', 'id', 'multi_polygon', 'point_2d', 'point', - 'history_m2m'] + "search_vector", + "id", + "multi_polygon", + "point_2d", + "point", + "history_m2m", + ] GET_VALUES_ = [ - 'preservation_to_considers', 'alterations', 'alteration_causes'] + "preservation_to_considers", + "alterations", + "alteration_causes", + ] GET_VALUES_EXTRA_TYPES = [ - 'preservation_to_considers', 'alterations', 'alteration_causes'] + "preservation_to_considers", + "alterations", + "alteration_causes", + ] def _get_values_documents(self, prefix="", filtr=None): values = {} - if not hasattr(self, 'documents'): + if not hasattr(self, "documents"): return values if not filtr or prefix + "documents" in filtr: values[prefix + "documents"] = [ - doc.get_values(no_values=True) - for doc in self.documents.all() + doc.get_values(no_values=True) for doc in self.documents.all() ] if filtr and prefix + "main_image" not in filtr: return values - if hasattr(self, "main_image") and self.main_image and hasattr( - self.main_image, "get_values"): + if ( + hasattr(self, "main_image") + and self.main_image + and hasattr(self.main_image, "get_values") + ): values[prefix + "main_image"] = self.main_image.get_values( - no_values=True) + no_values=True + ) return values def _get_values_update_sub_filter(self, filtr, prefix): if not filtr: return - return [k[len(prefix):] for k in filtr if k.startswith(prefix)] + return [k[len(prefix) :] for k in filtr if k.startswith(prefix)] - def get_values(self, prefix='', no_values=False, filtr=None, **kwargs): + def get_values(self, prefix="", no_values=False, filtr=None, **kwargs): if not prefix: prefix = self._prefix exclude = kwargs.get("exclude", []) values = {} - if hasattr(self, "qrcode") and ( - not filtr or prefix + 'qrcode_path' in filtr) and \ - prefix + 'qrcode_path' not in exclude: - values[prefix + 'qrcode_path'] = self.qrcode_path + if ( + hasattr(self, "qrcode") + and (not filtr or prefix + "qrcode_path" in filtr) + and prefix + "qrcode_path" not in exclude + ): + values[prefix + "qrcode_path"] = self.qrcode_path for field_name in get_all_field_names(self): try: value = getattr(self, field_name) except (AttributeError, MultipleObjectsReturned): continue - if field_name in self.GET_VALUES_EXCLUDE_FIELDS or \ - prefix + field_name in exclude: + if ( + field_name in self.GET_VALUES_EXCLUDE_FIELDS + or prefix + field_name in exclude + ): continue if filtr and not any( - field_name for f in filtr - if f.startswith(prefix + field_name)): + field_name for f in filtr if f.startswith(prefix + field_name) + ): continue - if hasattr(value, 'get_values'): - new_prefix = prefix + field_name + '_' + if hasattr(value, "get_values"): + new_prefix = prefix + field_name + "_" values.update( - value.get_values(new_prefix, filtr=filtr, **kwargs)) + value.get_values(new_prefix, filtr=filtr, **kwargs) + ) if hasattr(self, "get_values_for_" + field_name): values[prefix + field_name] = getattr( - self, "get_values_for_" + field_name)() + self, "get_values_for_" + field_name + )() else: values[prefix + field_name] = value values.update(self._get_values_documents(prefix=prefix, filtr=filtr)) for extra_field in self.GET_VALUES_EXTRA: - values[prefix + extra_field] = getattr(self, extra_field) or '' + values[prefix + extra_field] = getattr(self, extra_field) or "" for key, val in values.items(): if val is None: - val = '' + val = "" elif (key in self.GET_VALUES_EXTRA_TYPES or "type" in key) and ( - val.__class__.__name__.split('.')[0] == 'ManyRelatedManager'): + val.__class__.__name__.split(".")[0] == "ManyRelatedManager" + ): val = " ; ".join(str(v) for v in val.all()) elif not isinstance(val, (tuple, list, dict)): val = str(val) - if val.endswith('.None'): - val = '' + if val.endswith(".None"): + val = "" values[key] = val if (prefix and prefix != self._prefix) or no_values: # do not provide KEYS and VALUES for sub-items return values value_list = [] for key, value_ in values.items(): - if key in ('KEYS', 'VALUES'): + if key in ("KEYS", "VALUES"): continue value_list.append((key, str(value_))) for global_var in GlobalVar.objects.all(): @@ -217,10 +322,12 @@ class ValueGetter(object): return values @classmethod - def get_empty_values(cls, prefix=''): + def get_empty_values(cls, prefix=""): if not prefix: prefix = cls._prefix - return {prefix + field_name: '' for field_name in get_all_field_names(cls)} + return { + prefix + field_name: "" for field_name in get_all_field_names(cls) + } class HistoryModel(models.Model): @@ -231,14 +338,14 @@ class HistoryModel(models.Model): if not self.history_m2m or key not in self.history_m2m: return models = self.__class__.__module__ - if not models.endswith('.models'): + if not models.endswith(".models"): models += ".models" models = import_module(models) - model = getattr( - models, self.__class__.__name__[len('Historical'):]) + model = getattr(models, self.__class__.__name__[len("Historical") :]) related_model = getattr(model, key).rel.model - return related_model.history_decompress(self.history_m2m[key], - create=create) + return related_model.history_decompress( + self.history_m2m[key], create=create + ) def valid_id(cls): @@ -262,8 +369,7 @@ def valid_ids(cls): try: cls.objects.get(pk=v) except ObjectDoesNotExist: - raise ValidationError( - _("A selected item is not a valid item.")) + raise ValidationError(_("A selected item is not a valid item.")) return func @@ -317,11 +423,11 @@ class ItemKey(models.Model): key = models.TextField(_("Key")) content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() - content_object = GenericForeignKey('content_type', 'object_id') + content_object = GenericForeignKey("content_type", "object_id") importer = models.ForeignKey( - Import, null=True, blank=True, - help_text=_("Specific key to an import")) - user = models.ForeignKey('IshtarUser', blank=True, null=True) + Import, null=True, blank=True, help_text=_("Specific key to an import") + ) + user = models.ForeignKey("IshtarUser", blank=True, null=True) group = models.ForeignKey(TargetKeyGroup, blank=True, null=True) def __str__(self): @@ -329,14 +435,23 @@ class ItemKey(models.Model): class ImageModel(models.Model, ImageContainerModel): - image = models.ImageField(upload_to=get_image_path, blank=True, null=True, - max_length=255, help_text=max_size_help()) + image = models.ImageField( + upload_to=get_image_path, + blank=True, + null=True, + max_length=255, + help_text=max_size_help(), + ) thumbnail = models.ImageField( - upload_to=get_image_path, blank=True, null=True, max_length=255, - help_text=max_size_help()) + upload_to=get_image_path, + blank=True, + null=True, + max_length=255, + help_text=max_size_help(), + ) IMAGE_MAX_SIZE = settings.IMAGE_MAX_SIZE THUMB_MAX_SIZE = settings.THUMB_MAX_SIZE - IMAGE_PREFIX = '' + IMAGE_PREFIX = "" class Meta: abstract = True @@ -344,7 +459,7 @@ class ImageModel(models.Model, ImageContainerModel): def has_changed(self, field): if not self.pk: return True - manager = getattr(self.__class__, 'objects') + manager = getattr(self.__class__, "objects") old = getattr(manager.get(pk=self.pk), field) return getattr(self, field) != old @@ -352,17 +467,17 @@ class ImageModel(models.Model, ImageContainerModel): """Returns the image resized to fit inside a box of the given size""" image.thumbnail(size, Image.ANTIALIAS) temp = BytesIO() - image.save(temp, 'jpeg') + image.save(temp, "jpeg") temp.seek(0) - return SimpleUploadedFile('temp', temp.read()) + return SimpleUploadedFile("temp", temp.read()) def save(self, *args, **kwargs): - if 'force_copy' in kwargs: - kwargs.pop('force_copy') + if "force_copy" in kwargs: + kwargs.pop("force_copy") super(ImageModel, self).save(*args, **kwargs) return # manage images - if not self.has_changed('image'): + if not self.has_changed("image"): return super(ImageModel, self).save(*args, **kwargs) if not self.image: self.thumbnail = None @@ -377,14 +492,16 @@ class ImageModel(models.Model, ImageContainerModel): try: image = Image.open(self.image.file) # convert to RGB - if image.mode not in ('L', 'RGB'): - image = image.convert('RGB') + if image.mode not in ("L", "RGB"): + image = image.convert("RGB") # resize if necessary if self.IMAGE_MAX_SIZE: - self.image.save(filename, - self.create_thumb(image, self.IMAGE_MAX_SIZE), - save=False) + self.image.save( + filename, + self.create_thumb(image, self.IMAGE_MAX_SIZE), + save=False, + ) if old_path != self.image.path: try: @@ -398,7 +515,8 @@ class ImageModel(models.Model, ImageContainerModel): self.thumbnail.save( thumb_filename, self.create_thumb(image, self.THUMB_MAX_SIZE), - save=False) + save=False, + ) except (IOError, ValueError): self.thumbnail = None self.image = None @@ -408,10 +526,8 @@ class ImageModel(models.Model, ImageContainerModel): return super(ImageModel, self).save(*args, **kwargs) def _get_thumb_name(self, filename): - splited = filename.split('.') - return "{}-thumb.{}".format( - ".".join(splited[:-1]), splited[-1] - ) + splited = filename.split(".") + return "{}-thumb.{}".format(".".join(splited[:-1]), splited[-1]) class BulkUpdatedItem(object): @@ -428,7 +544,7 @@ class BulkUpdatedItem(object): """ if not transaction_id: transaction_id = str(time.time()) - args = ['cached_label_bulk_update', transaction_id] + extra_args + args = ["cached_label_bulk_update", transaction_id] + extra_args key, val = get_cache(cls, args) if val: return transaction_id, True @@ -440,60 +556,98 @@ class RelationItem(models.Model): """ Items with relation between them """ + MAIN_UP_MODEL_QUERY = "" relation_image = models.FileField( - _("Generated relation image (SVG)"), null=True, blank=True, - upload_to=get_image_path, help_text=max_size_help() + _("Generated relation image (SVG)"), + null=True, + blank=True, + upload_to=get_image_path, + help_text=max_size_help(), ) relation_bitmap_image = models.FileField( - _("Generated relation image (PNG)"), null=True, blank=True, - upload_to=get_image_path, help_text=max_size_help() + _("Generated relation image (PNG)"), + null=True, + blank=True, + upload_to=get_image_path, + help_text=max_size_help(), ) relation_dot = models.FileField( - _("Generated relation image (DOT)"), null=True, blank=True, - upload_to=get_image_path, help_text=max_size_help() + _("Generated relation image (DOT)"), + null=True, + blank=True, + upload_to=get_image_path, + help_text=max_size_help(), ) relation_image_above = models.FileField( - _("Generated above relation image (SVG)"), null=True, blank=True, - upload_to=get_image_path, help_text=max_size_help() + _("Generated above relation image (SVG)"), + null=True, + blank=True, + upload_to=get_image_path, + help_text=max_size_help(), ) relation_dot_above = models.FileField( - _("Generated above relation image (DOT)"), null=True, blank=True, - upload_to=get_image_path, help_text=max_size_help() + _("Generated above relation image (DOT)"), + null=True, + blank=True, + upload_to=get_image_path, + help_text=max_size_help(), ) relation_bitmap_image_above = models.FileField( - _("Generated above relation image (PNG)"), null=True, blank=True, - upload_to=get_image_path, help_text=max_size_help() + _("Generated above relation image (PNG)"), + null=True, + blank=True, + upload_to=get_image_path, + help_text=max_size_help(), ) relation_image_below = models.FileField( - _("Generated below relation image (SVG)"), null=True, blank=True, - upload_to=get_image_path, help_text=max_size_help() + _("Generated below relation image (SVG)"), + null=True, + blank=True, + upload_to=get_image_path, + help_text=max_size_help(), ) relation_dot_below = models.FileField( - _("Generated below relation image (DOT)"), null=True, blank=True, - upload_to=get_image_path, help_text=max_size_help() + _("Generated below relation image (DOT)"), + null=True, + blank=True, + upload_to=get_image_path, + help_text=max_size_help(), ) relation_bitmap_image_below = models.FileField( - _("Generated below relation image (PNG)"), null=True, blank=True, - upload_to=get_image_path, help_text=max_size_help() + _("Generated below relation image (PNG)"), + null=True, + blank=True, + upload_to=get_image_path, + help_text=max_size_help(), ) class Meta: abstract = True def generate_relation_image( - self, highlight_current=True, render_above=True, - render_below=True, full=False): - generate_relation_graph(self, highlight_current=highlight_current, - render_above=render_above, - render_below=render_below, full=full) + self, + highlight_current=True, + render_above=True, + render_below=True, + full=False, + ): + generate_relation_graph( + self, + highlight_current=highlight_current, + render_above=render_above, + render_below=render_below, + full=full, + ) class JsonDataSectionManager(models.Manager): def get_by_natural_key(self, name, app_label, model): - return self.get(name=name, - content_type__app_label=app_label, - content_type__model=model) + return self.get( + name=name, + content_type__app_label=app_label, + content_type__model=model, + ) class JsonDataSection(models.Model): @@ -505,7 +659,7 @@ class JsonDataSection(models.Model): class Meta: verbose_name = _("Json data - Menu") verbose_name_plural = _("Json data - Menus") - ordering = ['order', 'name'] + ordering = ["order", "name"] unique_together = ("name", "content_type") def natural_key(self): @@ -516,48 +670,59 @@ class JsonDataSection(models.Model): JSON_VALUE_TYPES = ( - ('T', _("Text")), - ('LT', _("Long text")), - ('I', _("Integer")), - ('B', _("Boolean")), - ('F', _("Float")), - ('D', _("Date")), - ('C', _("Choices")), + ("T", _("Text")), + ("LT", _("Long text")), + ("I", _("Integer")), + ("B", _("Boolean")), + ("F", _("Float")), + ("D", _("Date")), + ("C", _("Choices")), ) class JsonDataFieldManager(models.Manager): def get_by_natural_key(self, key, app_label, model): - return self.get(key=key, content_type__app_label=app_label, - content_type__model=model) + return self.get( + key=key, + content_type__app_label=app_label, + content_type__model=model, + ) class JsonDataField(models.Model): name = models.CharField(_("Name"), max_length=200) content_type = models.ForeignKey(ContentType) key = models.CharField( - _("Key"), max_length=200, - help_text=_("Value of the key in the JSON schema. For hierarchical " - "key use \"__\" to explain it. For instance for the key " - "'my_subkey' with data such as {'my_key': {'my_subkey': " - "'value'}}, its value will be reached with " - "my_key__my_subkey.")) + _("Key"), + max_length=200, + help_text=_( + "Value of the key in the JSON schema. For hierarchical " + 'key use "__" to explain it. For instance for the key ' + "'my_subkey' with data such as {'my_key': {'my_subkey': " + "'value'}}, its value will be reached with " + "my_key__my_subkey." + ), + ) display = models.BooleanField(_("Display"), default=True) - value_type = models.CharField(_("Type"), default="T", max_length=10, - choices=JSON_VALUE_TYPES) + value_type = models.CharField( + _("Type"), default="T", max_length=10, choices=JSON_VALUE_TYPES + ) order = models.IntegerField(_("Order"), default=10) - search_index = models.BooleanField(_("Use in search indexes"), - default=False) - section = models.ForeignKey(JsonDataSection, blank=True, null=True, - on_delete=models.SET_NULL) + search_index = models.BooleanField( + _("Use in search indexes"), default=False + ) + section = models.ForeignKey( + JsonDataSection, blank=True, null=True, on_delete=models.SET_NULL + ) custom_forms = models.ManyToManyField( - "CustomForm", blank=True, through="CustomFormJsonField") + "CustomForm", blank=True, through="CustomFormJsonField" + ) objects = JsonDataFieldManager() class Meta: verbose_name = _("Json data - Field") verbose_name_plural = _("Json data - Fields") - ordering = ['order', 'name'] + ordering = ["order", "name"] unique_together = ("content_type", "key") def natural_key(self): @@ -571,29 +736,35 @@ class JsonDataField(models.Model): return if self.section.content_type != self.content_type: raise ValidationError( - _("Content types of the field and of the menu do not match")) + _("Content types of the field and of the menu do not match") + ) LOGICAL_TYPES = ( - ('above', _("Above")), - ('below', _("Below")), - ('equal', _("Equal")), - ('include', _("Include")), - ('included', _("Is included")), + ("above", _("Above")), + ("below", _("Below")), + ("equal", _("Equal")), + ("include", _("Include")), + ("included", _("Is included")), ) class GeneralRelationType(GeneralType): order = models.IntegerField(_("Order"), default=1) symmetrical = models.BooleanField(_("Symmetrical")) - tiny_label = models.CharField(_("Tiny label"), max_length=50, - blank=True, null=True) + tiny_label = models.CharField( + _("Tiny label"), max_length=50, blank=True, null=True + ) inverse_relation = models.ForeignKey( - 'self', verbose_name=_("Inverse relation"), blank=True, - null=True) + "self", verbose_name=_("Inverse relation"), blank=True, null=True + ) logical_relation = models.CharField( - verbose_name=_("Logical relation"), max_length=10, - choices=LOGICAL_TYPES, blank=True, null=True) + verbose_name=_("Logical relation"), + max_length=10, + choices=LOGICAL_TYPES, + blank=True, + null=True, + ) class Meta: abstract = True @@ -602,7 +773,8 @@ class GeneralRelationType(GeneralType): # cannot have symmetrical and an inverse_relation if self.symmetrical and self.inverse_relation: raise ValidationError( - _("Cannot have symmetrical and an inverse_relation")) + _("Cannot have symmetrical and an inverse_relation") + ) def get_tiny_label(self): return self.tiny_label or self.label or "" @@ -611,9 +783,10 @@ class GeneralRelationType(GeneralType): obj = super(GeneralRelationType, self).save(*args, **kwargs) # after saving check that the inverse_relation of the inverse_relation # point to the saved object - if self.inverse_relation \ - and (not self.inverse_relation.inverse_relation - or self.inverse_relation.inverse_relation != self): + if self.inverse_relation and ( + not self.inverse_relation.inverse_relation + or self.inverse_relation.inverse_relation != self + ): self.inverse_relation.inverse_relation = self self.inverse_relation.symmetrical = False self.inverse_relation.save() @@ -623,7 +796,7 @@ class GeneralRelationType(GeneralType): class GeneralRecordRelations(object): @classmethod def general_types(cls): - return ['relation_type'] + return ["relation_type"] def save(self, *args, **kwargs): super(GeneralRecordRelations, self).save(*args, **kwargs) @@ -638,8 +811,11 @@ class GeneralRecordRelations(object): if not sym_rel_type: return - dct = {'right_record': self.left_record, - 'left_record': self.right_record, 'relation_type': sym_rel_type} + dct = { + "right_record": self.left_record, + "left_record": self.right_record, + "relation_type": sym_rel_type, + } self.__class__.objects.get_or_create(**dct) return self @@ -653,9 +829,11 @@ def post_delete_record_relation(sender, instance, **kwargs): # no symetric/inverse is defined if not sym_rel_type: return - dct = {'right_record_id': instance.left_record_id, - 'left_record_id': instance.right_record_id, - 'relation_type': sym_rel_type} + dct = { + "right_record_id": instance.left_record_id, + "left_record_id": instance.right_record_id, + "relation_type": sym_rel_type, + } q = instance.__class__.objects.filter(**dct) if q.count(): q.delete() @@ -664,342 +842,474 @@ def post_delete_record_relation(sender, instance, **kwargs): class SearchQuery(models.Model): label = models.TextField(_("Label"), blank=True, default="") query = models.TextField(_("Query"), blank=True, default="") - content_type = models.ForeignKey(ContentType, - verbose_name=_("Content type")) + content_type = models.ForeignKey( + ContentType, verbose_name=_("Content type") + ) profile = models.ForeignKey("UserProfile", verbose_name=_("Profile")) is_alert = models.BooleanField(_("Is an alert"), default=False) class Meta: verbose_name = _("Search query") verbose_name_plural = _("Search queries") - ordering = ['label'] + ordering = ["label"] def __str__(self): return str(self.label) class Language(GeneralType): - iso_code = models.CharField(_("ISO code"), null=True, blank=True, - max_length=2) + iso_code = models.CharField( + _("ISO code"), null=True, blank=True, max_length=2 + ) class Meta: verbose_name = _("Language") verbose_name_plural = _("Languages") -CURRENCY = (("€", _("Euro")), - ("$", _("US dollar"))) -FIND_INDEX_SOURCE = (("O", _("Operations")), - ("CR", _("Context records"))) -SITE_LABELS = [('site', _("Site")), ('entity', _("Archaeological entity"))] +CURRENCY = (("€", _("Euro")), ("$", _("US dollar"))) +FIND_INDEX_SOURCE = (("O", _("Operations")), ("CR", _("Context records"))) +SITE_LABELS = [("site", _("Site")), ("entity", _("Archaeological entity"))] TRANSLATED_SITE_LABELS = { - 'site': { - 'search': _("Site search"), - 'new': _("New site"), - 'modification': _("Site modification"), - 'deletion': _("Site deletion"), + "site": { + "search": _("Site search"), + "new": _("New site"), + "modification": _("Site modification"), + "deletion": _("Site deletion"), "attached-to-operation": _("Site (attached to the operation)"), - "name-attached-to-operation": - _("Site name (attached to the operation)"), + "name-attached-to-operation": _( + "Site name (attached to the operation)" + ), "attached-to-cr": _("Site (attached to the context record)"), "name-attached-to-cr": _("Site name (attached to the context record)"), }, - 'entity': { - 'search': _("Archaeological entity search"), - 'new': _("New archaeological entity"), - 'modification': _("Archaeological entity modification"), - 'deletion': _("Archaeological entity deletion"), - "attached-to-operation": _("Archaeological entity (attached to the " - "operation)"), - "name-attached-to-operation": _("Archaeological entity name (attached " - "to the operation)"), - "attached-to-cr": _("Archaeological entity (attached to the context " - "record)"), - "name-attached-to-cr": - _("Archaeological entity name (attached to the context record)"), + "entity": { + "search": _("Archaeological entity search"), + "new": _("New archaeological entity"), + "modification": _("Archaeological entity modification"), + "deletion": _("Archaeological entity deletion"), + "attached-to-operation": _( + "Archaeological entity (attached to the " "operation)" + ), + "name-attached-to-operation": _( + "Archaeological entity name (attached " "to the operation)" + ), + "attached-to-cr": _( + "Archaeological entity (attached to the context " "record)" + ), + "name-attached-to-cr": _( + "Archaeological entity name (attached to the context record)" + ), }, } ACCOUNT_NAMING_STYLE = ( - ('NF', _("name.firstname")), - ('FN', _("firstname.name")), + ("NF", _("name.firstname")), + ("FN", _("firstname.name")), ) class IshtarSiteProfile(models.Model, Cached): - slug_field = 'slug' + slug_field = "slug" label = models.TextField(_("Name")) slug = models.SlugField(_("Slug"), unique=True) active = models.BooleanField(_("Current active"), default=False) experimental_feature = models.BooleanField( - _("Activate experimental feature"), default=False) + _("Activate experimental feature"), default=False + ) description = models.TextField(_("Description"), blank=True, default="") warning_name = models.TextField(_("Warning name"), blank=True, default="") - warning_message = models.TextField(_("Warning message"), blank=True, - default="") + warning_message = models.TextField( + _("Warning message"), blank=True, default="" + ) delete_image_zip_on_archive = models.BooleanField( _("Import - Delete image/document zip on archive"), default=False ) clean_redundant_document_association = models.BooleanField( - _("Document - Remove redundant association"), default=False, - help_text=_("For instance, remove operation association of a " - "document also associated to a find of this operation. " - "Only manage association of operations, context records " - "and finds.") + _("Document - Remove redundant association"), + default=False, + help_text=_( + "For instance, remove operation association of a " + "document also associated to a find of this operation. " + "Only manage association of operations, context records " + "and finds." + ), ) calculate_weight_on_full = models.BooleanField( _("Container - calculate weight only when all find has a weight"), - default=False) + default=False, + ) config = models.CharField( - _("Alternate configuration"), max_length=200, + _("Alternate configuration"), + max_length=200, choices=ALTERNATE_CONFIGS_CHOICES, - help_text=_("Choose an alternate configuration for label, " - "index management"), - null=True, blank=True + help_text=_( + "Choose an alternate configuration for label, " "index management" + ), + null=True, + blank=True, ) files = models.BooleanField(_("Files module"), default=False) archaeological_site = models.BooleanField( - _("Archaeological site module"), default=False) + _("Archaeological site module"), default=False + ) archaeological_site_label = models.CharField( - _("Archaeological site type"), max_length=200, + _("Archaeological site type"), + max_length=200, choices=SITE_LABELS, - default='site' + default="site", + ) + context_record = models.BooleanField( + _("Context records module"), default=False + ) + find = models.BooleanField( + _("Finds module"), + default=False, + help_text=_("Need context records module"), ) - context_record = models.BooleanField(_("Context records module"), - default=False) - find = models.BooleanField(_("Finds module"), default=False, - help_text=_("Need context records module")) find_index = models.CharField( - _("Find index is based on"), default='O', max_length=2, + _("Find index is based on"), + default="O", + max_length=2, choices=FIND_INDEX_SOURCE, - help_text=_("To prevent irrelevant indexes, change this parameter " - "only if there is no find in the database")) + help_text=_( + "To prevent irrelevant indexes, change this parameter " + "only if there is no find in the database" + ), + ) warehouse = models.BooleanField( - _("Warehouses module"), default=False, - help_text=_("Need finds module")) - preservation = models.BooleanField(_("Preservation module"), - default=False) + _("Warehouses module"), default=False, help_text=_("Need finds module") + ) + preservation = models.BooleanField(_("Preservation module"), default=False) mapping = models.BooleanField(_("Mapping module"), default=False) point_precision = models.IntegerField( - _("Point precision (search and sheets)"), null=True, blank=True, + _("Point precision (search and sheets)"), + null=True, + blank=True, help_text=_( "Number of digit to round from the decimal point for coordinates " "in WGS84 (latitude, longitude). Empty value means no round." - ) + ), ) locate_warehouses = models.BooleanField( - _("Locate warehouse and containers"), default=False, + _("Locate warehouse and containers"), + default=False, help_text=_( "Mapping module must be activated. With many containers and " "background task not activated, activating this option may " - "consume many resources.") + "consume many resources." + ), ) use_town_for_geo = models.BooleanField( - _("Use town to locate when coordinates are missing"), default=True) - relation_graph = models.BooleanField(_("Generate relation graph"), - default=False) + _("Use town to locate when coordinates are missing"), default=True + ) + relation_graph = models.BooleanField( + _("Generate relation graph"), default=False + ) underwater = models.BooleanField(_("Underwater module"), default=False) parcel_mandatory = models.BooleanField( - _("Parcel are mandatory for context records"), default=True) + _("Parcel are mandatory for context records"), default=True + ) homepage = models.TextField( - _("Home page"), blank=True, default="", - help_text=_("Homepage of Ishtar - if not defined a default homepage " - "will appear. Use the markdown syntax. {random_image} " - "can be used to display a random image.")) + _("Home page"), + blank=True, + default="", + help_text=_( + "Homepage of Ishtar - if not defined a default homepage " + "will appear. Use the markdown syntax. {random_image} " + "can be used to display a random image." + ), + ) operation_prefix = models.CharField( - _("Main operation code prefix"), default='OA', null=True, blank=True, - max_length=20 + _("Main operation code prefix"), + default="OA", + null=True, + blank=True, + max_length=20, ) default_operation_prefix = models.CharField( - _("Default operation code prefix"), default='OP', null=True, - blank=True, max_length=20 + _("Default operation code prefix"), + default="OP", + null=True, + blank=True, + max_length=20, ) operation_region_code = models.CharField( - _("Operation region code"), null=True, blank=True, - max_length=5 + _("Operation region code"), null=True, blank=True, max_length=5 ) operation_complete_identifier = models.TextField( _("Operation complete identifier"), - default="", blank=True, - help_text=_("Formula to manage operation complete identifier.")) + default="", + blank=True, + help_text=_("Formula to manage operation complete identifier."), + ) operation_custom_index = models.TextField( _("Operation custom index key"), - default="", blank=True, - help_text=_("Keys to be used to manage operation custom index. " - "Separate keys with a semicolon.")) + default="", + blank=True, + help_text=_( + "Keys to be used to manage operation custom index. " + "Separate keys with a semicolon." + ), + ) site_complete_identifier = models.TextField( _("Archaeological site complete identifier"), - default="", blank=True, - help_text=_("Formula to manage archaeological site complete" - " identifier.")) + default="", + blank=True, + help_text=_( + "Formula to manage archaeological site complete" " identifier." + ), + ) site_custom_index = models.TextField( _("Archaeological site custom index key"), - default="", blank=True, - help_text=_("Keys to be used to manage archaeological site custom " - "index. Separate keys with a semicolon.")) + default="", + blank=True, + help_text=_( + "Keys to be used to manage archaeological site custom " + "index. Separate keys with a semicolon." + ), + ) file_external_id = models.TextField( _("File external id"), default="{year}-{numeric_reference}", - help_text=_("Formula to manage file external ID. " - "Change this with care. With incorrect formula, the " - "application might be unusable and import of external " - "data can be destructive.")) + help_text=_( + "Formula to manage file external ID. " + "Change this with care. With incorrect formula, the " + "application might be unusable and import of external " + "data can be destructive." + ), + ) file_complete_identifier = models.TextField( _("Archaeological file complete identifier"), - default="", blank=True, - help_text=_("Formula to manage archaeological file complete " - "identifier.")) + default="", + blank=True, + help_text=_( + "Formula to manage archaeological file complete " "identifier." + ), + ) file_custom_index = models.TextField( _("Archaeological file custom index key"), - default="", blank=True, - help_text=_("Keys to be used to manage archaeological file custom " - "index. Separate keys with a semicolon.")) + default="", + blank=True, + help_text=_( + "Keys to be used to manage archaeological file custom " + "index. Separate keys with a semicolon." + ), + ) parcel_external_id = models.TextField( _("Parcel external id"), default="{associated_file__external_id}{operation__code_patriarche}-" - "{town__numero_insee}-{section}{parcel_number}", - help_text=_("Formula to manage parcel external ID. " - "Change this with care. With incorrect formula, the " - "application might be unusable and import of external " - "data can be destructive.")) + "{town__numero_insee}-{section}{parcel_number}", + help_text=_( + "Formula to manage parcel external ID. " + "Change this with care. With incorrect formula, the " + "application might be unusable and import of external " + "data can be destructive." + ), + ) context_record_external_id = models.TextField( _("Context record external id"), default="{parcel__external_id}-{label}", - help_text=_("Formula to manage context record external ID. " - "Change this with care. With incorrect formula, the " - "application might be unusable and import of external " - "data can be destructive.")) + help_text=_( + "Formula to manage context record external ID. " + "Change this with care. With incorrect formula, the " + "application might be unusable and import of external " + "data can be destructive." + ), + ) contextrecord_complete_identifier = models.TextField( _("Context record complete identifier"), - default="", blank=True, - help_text=_("Formula to manage context record complete identifier.")) + default="", + blank=True, + help_text=_("Formula to manage context record complete identifier."), + ) contextrecord_custom_index = models.TextField( _("Context record custom index key"), - default="", blank=True, - help_text=_("Keys to be used to manage context record custom index. " - "Separate keys with a semicolon.")) + default="", + blank=True, + help_text=_( + "Keys to be used to manage context record custom index. " + "Separate keys with a semicolon." + ), + ) base_find_external_id = models.TextField( _("Base find external id"), default="{context_record__external_id}-{label}", - help_text=_("Formula to manage base find external ID. " - "Change this with care. With incorrect formula, the " - "application might be unusable and import of external " - "data can be destructive.")) + help_text=_( + "Formula to manage base find external ID. " + "Change this with care. With incorrect formula, the " + "application might be unusable and import of external " + "data can be destructive." + ), + ) basefind_complete_identifier = models.TextField( _("Base find complete identifier"), - default="", blank=True, - help_text=_("Formula to manage base find complete identifier.")) + default="", + blank=True, + help_text=_("Formula to manage base find complete identifier."), + ) basefind_custom_index = models.TextField( _("Base find custom index key"), - default="", blank=True, - help_text=_("Keys to be used to manage base find custom index. " - "Separate keys with a semicolon.")) + default="", + blank=True, + help_text=_( + "Keys to be used to manage base find custom index. " + "Separate keys with a semicolon." + ), + ) find_external_id = models.TextField( _("Find external id"), default="{get_first_base_find__context_record__external_id}-{label}", - help_text=_("Formula to manage find external ID. " - "Change this with care. With incorrect formula, the " - "application might be unusable and import of external " - "data can be destructive.")) + help_text=_( + "Formula to manage find external ID. " + "Change this with care. With incorrect formula, the " + "application might be unusable and import of external " + "data can be destructive." + ), + ) find_complete_identifier = models.TextField( _("Find complete identifier"), - default="", blank=True, - help_text=_("Formula to manage find complete identifier.")) + default="", + blank=True, + help_text=_("Formula to manage find complete identifier."), + ) find_custom_index = models.TextField( _("Find custom index key"), - default="", blank=True, - help_text=_("Keys to be used to manage find custom index. " - "Separate keys with a semicolon.")) + default="", + blank=True, + help_text=_( + "Keys to be used to manage find custom index. " + "Separate keys with a semicolon." + ), + ) container_external_id = models.TextField( _("Container external id"), - default="{parent_external_id}-{container_type__txt_idx}-" - "{reference}", - help_text=_("Formula to manage container external ID. " - "Change this with care. With incorrect formula, the " - "application might be unusable and import of external " - "data can be destructive.")) + default="{parent_external_id}-{container_type__txt_idx}-" "{reference}", + help_text=_( + "Formula to manage container external ID. " + "Change this with care. With incorrect formula, the " + "application might be unusable and import of external " + "data can be destructive." + ), + ) container_complete_identifier = models.TextField( _("Container complete identifier"), - default="", blank=True, - help_text=_("Formula to manage container complete identifier.")) + default="", + blank=True, + help_text=_("Formula to manage container complete identifier."), + ) container_custom_index = models.TextField( _("Container custom index key"), - default="", blank=True, - help_text=_("Keys to be used to manage container custom index. " - "Separate keys with a semicolon.")) + default="", + blank=True, + help_text=_( + "Keys to be used to manage container custom index. " + "Separate keys with a semicolon." + ), + ) warehouse_external_id = models.TextField( _("Warehouse external id"), default="{name|slug}", - help_text=_("Formula to manage warehouse external ID. " - "Change this with care. With incorrect formula, the " - "application might be unusable and import of external " - "data can be destructive.")) + help_text=_( + "Formula to manage warehouse external ID. " + "Change this with care. With incorrect formula, the " + "application might be unusable and import of external " + "data can be destructive." + ), + ) warehouse_complete_identifier = models.TextField( _("Warehouse complete identifier"), - default="", blank=True, - help_text=_("Formula to manage warehouse complete identifier.")) + default="", + blank=True, + help_text=_("Formula to manage warehouse complete identifier."), + ) warehouse_custom_index = models.TextField( _("Warehouse custom index key"), - default="", blank=True, - help_text=_("Keys to be used to manage warehouse custom index. " - "Separate keys with a semicolon.")) + default="", + blank=True, + help_text=_( + "Keys to be used to manage warehouse custom index. " + "Separate keys with a semicolon." + ), + ) document_external_id = models.TextField( _("Document external id"), default="{index}", - help_text=_("Formula to manage document external ID. " - "Change this with care. With incorrect formula, the " - "application might be unusable and import of external " - "data can be destructive.")) + help_text=_( + "Formula to manage document external ID. " + "Change this with care. With incorrect formula, the " + "application might be unusable and import of external " + "data can be destructive." + ), + ) document_complete_identifier = models.TextField( _("Document complete identifier"), - default="", blank=True, - help_text=_("Formula to manage document complete identifier.")) + default="", + blank=True, + help_text=_("Formula to manage document complete identifier."), + ) document_custom_index = models.TextField( _("Document custom index key"), - default="", blank=True, - help_text=_("Keys to be used to manage document custom index. " - "Separate keys with a semicolon.")) + default="", + blank=True, + help_text=_( + "Keys to be used to manage document custom index. " + "Separate keys with a semicolon." + ), + ) person_raw_name = models.TextField( _("Raw name for person"), default="{name|upper} {surname}", - help_text=_("Formula to manage person raw_name. " - "Change this with care. With incorrect formula, the " - "application might be unusable and import of external " - "data can be destructive.")) - find_use_index = models.BooleanField(_("Use auto index for finds"), - default=True) - currency = models.CharField(_("Currency"), default="€", - choices=CURRENCY, max_length=5) + help_text=_( + "Formula to manage person raw_name. " + "Change this with care. With incorrect formula, the " + "application might be unusable and import of external " + "data can be destructive." + ), + ) + find_use_index = models.BooleanField( + _("Use auto index for finds"), default=True + ) + currency = models.CharField( + _("Currency"), default="€", choices=CURRENCY, max_length=5 + ) account_naming_style = models.CharField( - _("Naming style for accounts"), max_length=2, default="NF", - choices=ACCOUNT_NAMING_STYLE + _("Naming style for accounts"), + max_length=2, + default="NF", + choices=ACCOUNT_NAMING_STYLE, ) default_center = models.PointField( - _("Maps - default center"), - default='SRID=4326;POINT(2.4397 46.5528)') - default_zoom = models.IntegerField( - _("Maps - default zoom"), default=6) + _("Maps - default center"), default="SRID=4326;POINT(2.4397 46.5528)" + ) + default_zoom = models.IntegerField(_("Maps - default zoom"), default=6) display_srs = models.ForeignKey( SpatialReferenceSystem, verbose_name=_("Spatial Reference System for display"), - blank=True, null=True, - help_text=_("Spatial Reference System used for display when no SRS is " - "defined") + blank=True, + null=True, + help_text=_( + "Spatial Reference System used for display when no SRS is " + "defined" + ), ) default_language = models.ForeignKey( Language, verbose_name=_("Default language for documentation"), - blank=True, null=True, - help_text=_("If set, by default the selected language will be set for " - "localized documents.") + blank=True, + null=True, + help_text=_( + "If set, by default the selected language will be set for " + "localized documents." + ), ) objects = SlugModelManager() class Meta: verbose_name = _("Ishtar site profile") verbose_name_plural = _("Ishtar site profiles") - ordering = ['label'] + ordering = ["label"] def __str__(self): return str(self.label) @@ -1008,18 +1318,22 @@ class IshtarSiteProfile(models.Model, Cached): return (self.slug,) def has_overload(self, key): - return self.config and self.config in ALTERNATE_CONFIGS and \ - hasattr(ALTERNATE_CONFIGS[self.config], key) + return ( + self.config + and self.config in ALTERNATE_CONFIGS + and hasattr(ALTERNATE_CONFIGS[self.config], key) + ) @classmethod def get_current_profile(cls, force=False): - cache_key, value = get_cache(cls, ['is-current-profile']) + cache_key, value = get_cache(cls, ["is-current-profile"]) if value and not force: return value q = cls.objects.filter(active=True) if not q.count(): obj = cls.objects.create( - label="Default profile", slug='default', active=True) + label="Default profile", slug="default", active=True + ) else: obj = q.all()[0] cache.set(cache_key, obj, settings.CACHE_TIMEOUT) @@ -1032,14 +1346,12 @@ class IshtarSiteProfile(models.Model, Cached): def get_site_label(self, key=None): if not key: return str(dict(SITE_LABELS)[self.archaeological_site_label]) - return str( - TRANSLATED_SITE_LABELS[self.archaeological_site_label][key] - ) + return str(TRANSLATED_SITE_LABELS[self.archaeological_site_label][key]) def save(self, *args, **kwargs): raw = False - if 'raw' in kwargs: - raw = kwargs.pop('raw') + if "raw" in kwargs: + raw = kwargs.pop("raw") super(IshtarSiteProfile, self).save(*args, **kwargs) obj = self if raw: @@ -1074,6 +1386,7 @@ profile_mapping = lazy(_profile_mapping) def cached_site_changed(sender, **kwargs): get_current_profile(force=True) from ishtar_common.menus import Menu + MAIN_MENU = Menu(None) MAIN_MENU.init() MAIN_MENU.reinit_menu_for_all_user() @@ -1093,27 +1406,35 @@ class CustomForm(models.Model): form = models.CharField(_("Form"), max_length=250) available = models.BooleanField(_("Available"), default=True) enabled = models.BooleanField( - _("Enable this form"), default=True, - help_text=_("Disable with caution: disabling a form with mandatory " - "fields may lead to database errors.")) + _("Enable this form"), + default=True, + help_text=_( + "Disable with caution: disabling a form with mandatory " + "fields may lead to database errors." + ), + ) apply_to_all = models.BooleanField( - _("Apply to all"), default=False, - help_text=_("Apply this form to all users. If set to True, selecting " - "user and user type is useless.")) - users = models.ManyToManyField('IshtarUser', blank=True) + _("Apply to all"), + default=False, + help_text=_( + "Apply this form to all users. If set to True, selecting " + "user and user type is useless." + ), + ) + users = models.ManyToManyField("IshtarUser", blank=True) user_types = models.ManyToManyField( - 'PersonType', blank=True, - help_text=_("Deprecated - use profile types")) + "PersonType", blank=True, help_text=_("Deprecated - use profile types") + ) profile_types = models.ManyToManyField("ProfileType", blank=True) objects = CustomFormManager() - SERIALIZATION_EXCLUDE = ("users", ) + SERIALIZATION_EXCLUDE = ("users",) class Meta: verbose_name = _("Custom form") verbose_name_plural = _("Custom forms") - ordering = ['name', 'form'] - unique_together = (('name', 'form'),) + ordering = ["name", "form"] + unique_together = (("name", "form"),) def natural_key(self): return (self.name, self.form) @@ -1135,12 +1456,14 @@ class CustomForm(models.Model): @classmethod def register(cls): - if hasattr(cls, '_register') and hasattr(cls, '_register_fields'): + if hasattr(cls, "_register") and hasattr(cls, "_register_fields"): return cls._register, cls._register_fields - cache_key, value = get_cache(cls.__class__, ['dct-forms'], - app_label='ishtar_common') + cache_key, value = get_cache( + cls.__class__, ["dct-forms"], app_label="ishtar_common" + ) cache_key_fields, value_fields = get_cache( - cls.__class__, ['dct-fields'], app_label='ishtar_common') + cls.__class__, ["dct-fields"], app_label="ishtar_common" + ) if value and value_fields: cls._register = value cls._register_fields = value_fields @@ -1155,15 +1478,17 @@ class CustomForm(models.Model): if app_name == "archaeological_files_pdl": app_name = "archaeological_files" for form in dir(app_form): - if 'Form' not in form and 'Select' not in form: + if "Form" not in form and "Select" not in form: # not very clean... but do not treat inappropriate items continue form = getattr(app_form, form) - if not inspect.isclass(form) \ - or not issubclass(form, CustomFormForm) \ - or not getattr(form, 'form_slug', None): + if ( + not inspect.isclass(form) + or not issubclass(form, CustomFormForm) + or not getattr(form, "form_slug", None) + ): continue - model_name = form.form_slug.split('-')[0].replace('_', '') + model_name = form.form_slug.split("-")[0].replace("_", "") if app_name not in cls._register_fields: cls._register_fields[app_name] = [] if model_name not in cls._register_fields[app_name]: @@ -1182,36 +1507,43 @@ class CustomForm(models.Model): if self.form not in self._register: return [] current_form = register[self.form] - app_name = current_form.__module__.split('.')[0] + app_name = current_form.__module__.split(".")[0] if app_name == "archaeological_files_pdl": app_name = "archaeological_files" if app_name not in register_fields: return [] res = [] for model_name in register_fields[app_name]: - q = ContentType.objects.filter(app_label=app_name, - model=model_name) + q = ContentType.objects.filter(app_label=app_name, model=model_name) if not q.count(): continue ct = q.all()[0] for json_field in JsonDataField.objects.filter( - content_type=ct).all(): - res.append((json_field.pk, "{} ({})".format( - json_field.name, - dict(JSON_VALUE_TYPES)[json_field.value_type]))) + content_type=ct + ).all(): + res.append( + ( + json_field.pk, + "{} ({})".format( + json_field.name, + dict(JSON_VALUE_TYPES)[json_field.value_type], + ), + ) + ) return res class ExcludedFieldManager(models.Manager): - def get_by_natural_key(self, custom_form_name, custom_form_form, - field): - return self.get(custom_form__name=custom_form_name, - custom_form__form=custom_form_form, - field=field) + def get_by_natural_key(self, custom_form_name, custom_form_form, field): + return self.get( + custom_form__name=custom_form_name, + custom_form__form=custom_form_form, + field=field, + ) class ExcludedField(models.Model): - custom_form = models.ForeignKey(CustomForm, related_name='excluded_fields') + custom_form = models.ForeignKey(CustomForm, related_name="excluded_fields") field = models.CharField(_("Field"), max_length=250) objects = ExcludedFieldManager() @@ -1221,29 +1553,33 @@ class ExcludedField(models.Model): unique_together = ("custom_form", "field") def natural_key(self): - return (self.custom_form.name , self.custom_form.form, - self.field) + return (self.custom_form.name, self.custom_form.form, self.field) class CustomFormJsonFieldManager(models.Manager): - def get_by_natural_key(self, custom_form_name, custom_form_form, - json_field_key, json_field_app_label, - json_field_model): + def get_by_natural_key( + self, + custom_form_name, + custom_form_form, + json_field_key, + json_field_app_label, + json_field_model, + ): return self.get( custom_form__name=custom_form_name, custom_form__form=custom_form_form, json_field__key=json_field_key, json_field__content_type__app_label=json_field_app_label, - json_field__content_type__model=json_field_model + json_field__content_type__model=json_field_model, ) class CustomFormJsonField(models.Model): - custom_form = models.ForeignKey(CustomForm, related_name='json_fields') - json_field = models.ForeignKey(JsonDataField, - related_name='custom_form_details') - label = models.CharField(_("Label"), max_length=200, blank=True, - default='') + custom_form = models.ForeignKey(CustomForm, related_name="json_fields") + json_field = models.ForeignKey( + JsonDataField, related_name="custom_form_details" + ) + label = models.CharField(_("Label"), max_length=200, blank=True, default="") order = models.IntegerField(verbose_name=_("Order"), default=1) help_text = models.TextField(_("Help"), blank=True, default="") objects = CustomFormJsonFieldManager() @@ -1255,23 +1591,26 @@ class CustomFormJsonField(models.Model): def natural_key(self): return ( - self.custom_form.name, self.custom_form.form, - self.json_field.key, self.json_field.content_type.app_label, - self.json_field.content_type.model + self.custom_form.name, + self.custom_form.form, + self.json_field.key, + self.json_field.content_type.app_label, + self.json_field.content_type.model, ) class GlobalVar(models.Model, Cached): slug = models.SlugField(_("Variable name"), unique=True) description = models.TextField( - _("Description of the variable"), blank=True, default="") + _("Description of the variable"), blank=True, default="" + ) value = models.TextField(_("Value"), blank=True, default="") objects = SlugModelManager() class Meta: verbose_name = _("Global variable") verbose_name_plural = _("Global variables") - ordering = ['slug'] + ordering = ["slug"] def natural_key(self): return (self.slug,) @@ -1281,9 +1620,9 @@ class GlobalVar(models.Model, Cached): def cached_globalvar_changed(sender, **kwargs): - if not kwargs['instance']: + if not kwargs["instance"]: return - var = kwargs['instance'] + var = kwargs["instance"] cache_key, value = get_cache(GlobalVar, var.slug) cache.set(cache_key, var.value, settings.CACHE_TIMEOUT) @@ -1293,10 +1632,12 @@ post_save.connect(cached_globalvar_changed, sender=GlobalVar) class UserDashboard: def __init__(self): - types = IshtarUser.objects.values('person__person_types', - 'person__person_types__label') - self.types = types.annotate(number=Count('pk')) \ - .order_by('person__person_types') + types = IshtarUser.objects.values( + "person__person_types", "person__person_types__label" + ) + self.types = types.annotate(number=Count("pk")).order_by( + "person__person_types" + ) class StatsCache(models.Model): @@ -1312,8 +1653,9 @@ class StatsCache(models.Model): class Dashboard(object): - def __init__(self, model, slice='year', date_source=None, show_detail=None, - fltr=None): + def __init__( + self, model, slice="year", date_source=None, show_detail=None, fltr=None + ): if not fltr: fltr = {} # don't provide date_source if it is not relevant @@ -1323,32 +1665,34 @@ class Dashboard(object): history_model = self.model.history.model # last edited - created self.recents, self.lasts = [], [] - for last_lst, modif_type in ((self.lasts, '+'), (self.recents, '~')): - last_ids = history_model.objects.values('id') \ - .annotate(hd=Max('history_date')) + for last_lst, modif_type in ((self.lasts, "+"), (self.recents, "~")): + last_ids = history_model.objects.values("id").annotate( + hd=Max("history_date") + ) last_ids = last_ids.filter(history_type=modif_type) from archaeological_finds.models import Find + if self.model == Find: - last_ids = last_ids.filter( - downstream_treatment_id__isnull=True) - if modif_type == '+': + last_ids = last_ids.filter(downstream_treatment_id__isnull=True) + if modif_type == "+": last_ids = last_ids.filter( - upstream_treatment_id__isnull=True) - last_ids = last_ids.order_by('-hd').distinct().all()[:5] + upstream_treatment_id__isnull=True + ) + last_ids = last_ids.order_by("-hd").distinct().all()[:5] for idx in last_ids: try: - obj = self.model.objects.get(pk=idx['id']) + obj = self.model.objects.get(pk=idx["id"]) except self.model.DoesNotExist: # deleted object are always referenced in history continue - obj.history_date = idx['hd'] + obj.history_date = idx["hd"] last_lst.append(obj) # years - base_kwargs = {'fltr': fltr.copy()} + base_kwargs = {"fltr": fltr.copy()} if date_source: - base_kwargs['date_source'] = date_source + base_kwargs["date_source"] = date_source periods_kwargs = copy.deepcopy(base_kwargs) - periods_kwargs['slice'] = slice + periods_kwargs["slice"] = slice self.periods = model.get_periods(**periods_kwargs) self.periods = list(set(self.periods)) self.periods.sort() @@ -1357,36 +1701,45 @@ class Dashboard(object): kwargs_num = copy.deepcopy(base_kwargs) self.serie_labels = [_("Total")] # numbers - if slice == 'year': - self.values = [('year', "", - list(reversed(self.periods)))] - self.numbers = [model.get_by_year(year, **kwargs_num).count() - for year in self.periods] - self.values += [('number', _("Number"), - list(reversed(self.numbers)))] - if slice == 'month': + if slice == "year": + self.values = [("year", "", list(reversed(self.periods)))] + self.numbers = [ + model.get_by_year(year, **kwargs_num).count() + for year in self.periods + ] + self.values += [ + ("number", _("Number"), list(reversed(self.numbers))) + ] + if slice == "month": periods = list(reversed(self.periods)) - self.periods = ["%d-%s-01" % (p[0], ('0' + str(p[1])) - if len(str(p[1])) == 1 else p[1]) for p in periods] - self.values = [('month', "", self.periods)] + self.periods = [ + "%d-%s-01" + % (p[0], ("0" + str(p[1])) if len(str(p[1])) == 1 else p[1]) + for p in periods + ] + self.values = [("month", "", self.periods)] if show_detail: for dpt, lbl in settings.ISHTAR_DPTS: self.serie_labels.append(str(dpt)) - idx = 'number_' + str(dpt) - kwargs_num['fltr']["towns__numero_insee__startswith"] = \ - str(dpt) - numbers = [model.get_by_month(*p.split('-')[:2], - **kwargs_num).count() - for p in self.periods] + idx = "number_" + str(dpt) + kwargs_num["fltr"]["towns__numero_insee__startswith"] = str( + dpt + ) + numbers = [ + model.get_by_month( + *p.split("-")[:2], **kwargs_num + ).count() + for p in self.periods + ] self.values += [(idx, dpt, list(numbers))] # put "Total" at the end self.serie_labels.append(self.serie_labels.pop(0)) kwargs_num = base_kwargs.copy() - self.numbers = [model.get_by_month(*p.split('-')[:2], - **kwargs_num).count() - for p in self.periods] - self.values += [('number', _("Total"), - list(self.numbers))] + self.numbers = [ + model.get_by_month(*p.split("-")[:2], **kwargs_num).count() + for p in self.periods + ] + self.values += [("number", _("Total"), list(self.numbers))] # calculate self.average = self.get_average() self.variance = self.get_variance() @@ -1394,23 +1747,28 @@ class Dashboard(object): self.median = self.get_median() self.mode = self.get_mode() # by operation - if not hasattr(model, 'get_by_operation'): + if not hasattr(model, "get_by_operation"): return operations = model.get_operations() - operation_numbers = [model.get_by_operation(op).count() - for op in operations] + operation_numbers = [ + model.get_by_operation(op).count() for op in operations + ] # calculate self.operation_average = self.get_average(operation_numbers) self.operation_variance = self.get_variance(operation_numbers) self.operation_standard_deviation = self.get_standard_deviation( - operation_numbers) + operation_numbers + ) self.operation_median = self.get_median(operation_numbers) - operation_mode_pk = self.get_mode(dict(zip(operations, - operation_numbers))) + operation_mode_pk = self.get_mode( + dict(zip(operations, operation_numbers)) + ) if operation_mode_pk: from archaeological_operations.models import Operation + self.operation_mode = str( - Operation.objects.get(pk=operation_mode_pk)) + Operation.objects.get(pk=operation_mode_pk) + ) def get_average(self, vals=None): if not vals: @@ -1436,8 +1794,7 @@ class Dashboard(object): if (len_vals % 2) == 1: return vals[int(len_vals / 2)] else: - return (vals[int(len_vals / 2) - 1] + - vals[int(len_vals / 2)]) / 2.0 + return (vals[int(len_vals / 2) - 1] + vals[int(len_vals / 2)]) / 2.0 def get_mode(self, vals=None): if not vals: @@ -1453,34 +1810,47 @@ class DocumentTemplate(models.Model): slug = models.SlugField(_("Slug"), max_length=100, unique=True) associated_model = models.ForeignKey(ImporterModel) template = models.FileField( - _("Template"), upload_to="templates/%Y/", blank=True, null=True, - help_text=max_size_help()) + _("Template"), + upload_to="templates/%Y/", + blank=True, + null=True, + help_text=max_size_help(), + ) label_template = models.FileField( - _("Base template for labels"), upload_to="templates/%Y/", - blank=True, null=True, help_text=max_size_help()) + _("Base template for labels"), + upload_to="templates/%Y/", + blank=True, + null=True, + help_text=max_size_help(), + ) label_targets = models.TextField( _("Labels: targets for labels in the LibreOffice file"), - blank=True, null=True, - help_text=_("Each target is separated by a semi-colon. The first " - "target is the name of the object including the data in " - "base template. Following targets will be filled with the " - "content of the first target. For instance: " - "\"Cadre1;Cadre2;Cadre3;Cadre4;Cadre5;Cadre6\" for a " - "sheet with 6 labels.") + blank=True, + null=True, + help_text=_( + "Each target is separated by a semi-colon. The first " + "target is the name of the object including the data in " + "base template. Following targets will be filled with the " + "content of the first target. For instance: " + '"Cadre1;Cadre2;Cadre3;Cadre4;Cadre5;Cadre6" for a ' + "sheet with 6 labels." + ), ) available = models.BooleanField(_("Available"), default=True) for_labels = models.BooleanField(_("Used for labels"), default=False) label_per_page = models.IntegerField( - _("Number of label per page"), blank=True, null=True, - help_text=_("Only relevant for label template") + _("Number of label per page"), + blank=True, + null=True, + help_text=_("Only relevant for label template"), ) objects = SlugModelManager() - SERIALIZATION_FILES = ("template", ) + SERIALIZATION_FILES = ("template",) class Meta: verbose_name = _("Document template") verbose_name_plural = _("Document templates") - ordering = ['associated_model', 'name'] + ordering = ["associated_model", "name"] def __str__(self): return self.name @@ -1490,8 +1860,12 @@ class DocumentTemplate(models.Model): def clean(self): if self.for_labels and not self.label_per_page: - raise ValidationError(_("For label template, you must provide " - "number of label per page.")) + raise ValidationError( + _( + "For label template, you must provide " + "number of label per page." + ) + ) def generate_label_template(self): if not self.label_template.name or not self.label_targets: @@ -1500,10 +1874,11 @@ class DocumentTemplate(models.Model): base_target = targets[0] try: with zipfile.ZipFile(self.label_template.path) as zip: - with zip.open('content.xml') as content: - soup = BeautifulSoup(content.read(), 'xml') + with zip.open("content.xml") as content: + soup = BeautifulSoup(content.read(), "xml") base_content = soup.find( - "draw:frame", attrs={"draw:name": base_target}) + "draw:frame", attrs={"draw:name": base_target} + ) if not base_content: return base_content = base_content.contents @@ -1520,12 +1895,13 @@ class DocumentTemplate(models.Model): fixed_text = text.replace("items.0", replace_str) text.replace_with(fixed_text) for image in content.find_all( - attrs={"draw:name": re.compile("items.0")}): - image["draw:name"] = image["draw:name"].replace("items.0", - replace_str) + attrs={"draw:name": re.compile("items.0")} + ): + image["draw:name"] = image["draw:name"].replace( + "items.0", replace_str + ) new_content.append(content) - next_target = soup.find( - "draw:frame", attrs={"draw:name": target}) + next_target = soup.find("draw:frame", attrs={"draw:name": target}) if next_target: next_target.contents = new_content @@ -1536,15 +1912,15 @@ class DocumentTemplate(models.Model): sp[-2] += "-label" new_filename = ".".join(sp) new_file = os.path.join(tmp, new_filename) - with zipfile.ZipFile(new_file, 'w') as zip_out: - with zipfile.ZipFile(self.label_template.path, 'r') as zip_in: + with zipfile.ZipFile(new_file, "w") as zip_out: + with zipfile.ZipFile(self.label_template.path, "r") as zip_in: zip_out.comment = zip_in.comment for item in zip_in.infolist(): if item.filename != "content.xml": - zip_out.writestr(item, - zip_in.read(item.filename)) - with zipfile.ZipFile(new_file, mode='a', - compression=zipfile.ZIP_DEFLATED) as zf: + zip_out.writestr(item, zip_in.read(item.filename)) + with zipfile.ZipFile( + new_file, mode="a", compression=zipfile.ZIP_DEFLATED + ) as zf: zf.writestr("content.xml", str(soup)) media_dir = "templates/{}/".format(datetime.date.today().year) @@ -1569,23 +1945,26 @@ class DocumentTemplate(models.Model): if not self.slug: self.slug = create_slug(DocumentTemplate, self.name) super(DocumentTemplate, self).save(*args, **kwargs) - if self.label_template.name and self.label_targets and not \ - self.template: + if ( + self.label_template.name + and self.label_targets + and not self.template + ): self.generate_label_template() @classmethod def get_tuples(cls, dct=None, empty_first=True): if not dct: dct = {} - dct['available'] = True + dct["available"] = True if empty_first: - yield '', '----------' + yield "", "----------" items = cls.objects.filter(**dct) for item in items.distinct().order_by(*cls._meta.ordering).all(): yield item.pk, _(str(item)) def get_baselink_for_labels(self): - return reverse('generate-labels', args=[self.slug]) + return reverse("generate-labels", args=[self.slug]) def _exclude_filter(self, value): """ @@ -1604,36 +1983,48 @@ class DocumentTemplate(models.Model): if not regexp_list: return None z = zipfile.ZipFile(template) - content = z.open('content.xml') + content = z.open("content.xml") full_content = content.read().decode("utf-8") filtr = [] for regexp in regexp_list: - iter = re.finditer( - regexp, - full_content) + iter = re.finditer(regexp, full_content) for s in iter: key = s.groups()[0] if key not in filtr: filtr.append(key) new_filter = [] - OPERATORS = ["==", "not", "in", ">", "<", "!=", ">", "<", ">=", - "<=", "or", ">=", "<="] + OPERATORS = [ + "==", + "not", + "in", + ">", + "<", + "!=", + ">", + "<", + ">=", + "<=", + "or", + ">=", + "<=", + ] for fil in filtr: if not fil: continue - new_filter += [f for f in fil.split(" ") - if f and f not in OPERATORS] + new_filter += [ + f for f in fil.split(" ") if f and f not in OPERATORS + ] filtr = new_filter new_filter = [] for fil in filtr: - keys = fil.strip().split("|")[0].split('.') + keys = fil.strip().split("|")[0].split(".") new_filter += [k for k in keys if not self._exclude_filter(k)] prefix = "" for k in keys: if self._exclude_filter(k): continue if prefix: - prefix += '_' + prefix += "_" if prefix + k in new_filter: continue new_filter.append(prefix + k) @@ -1643,28 +2034,36 @@ class DocumentTemplate(models.Model): ITEM_RE = r"([A-Za-z0-9_.]*)(?:[\[\]0-9-:])*(?:\|[^}]+)*" BASE_RE = [ # {{ key1.key2 }} - r'{{ *' + ITEM_RE + ' *}}', + r"{{ *" + ITEM_RE + " *}}", # {% for item in key1.key2 %} - r'{% *for +[A-Za-z0-9_]+ +in +' + ITEM_RE + r' *%}', + r"{% *for +[A-Za-z0-9_]+ +in +" + ITEM_RE + r" *%}", # {% if ** %} - r'{% (?:el)*if ([^}]*)%}', - ] + r"{% (?:el)*if ([^}]*)%}", + ] def publish(self, c_object): tempdir = tempfile.mkdtemp("-ishtardocs") - output_name = tempdir + os.path.sep + \ - slugify(self.name.replace(' ', '_').lower()) + '-' + \ - datetime.date.today().strftime('%Y-%m-%d') + \ - "." + self.template.name.split('.')[-1] + output_name = ( + tempdir + + os.path.sep + + slugify(self.name.replace(" ", "_").lower()) + + "-" + + datetime.date.today().strftime("%Y-%m-%d") + + "." + + self.template.name.split(".")[-1] + ) filtr = self.get_filter(self.template, self.BASE_RE) # values = c_object.get_values(filtr=[]) if "VALUES" in filtr: filtr = [] values = c_object.get_values(filtr=filtr) - if not filtr or 'VALUES' in filtr: - values['VALUES'] = json.dumps( - values, indent=4, sort_keys=True, - skipkeys=True, ensure_ascii=False, + if not filtr or "VALUES" in filtr: + values["VALUES"] = json.dumps( + values, + indent=4, + sort_keys=True, + skipkeys=True, + ensure_ascii=False, separators=("", " : "), ).replace(" " * 4, "\t") engine = IshtarSecretaryRenderer() @@ -1676,28 +2075,32 @@ class DocumentTemplate(models.Model): raise TemplateSyntaxError(str(e), 0) except Exception as e: raise TemplateSyntaxError(str(e), 0) - with open(output_name, 'wb') as output: + with open(output_name, "wb") as output: output.write(result) return output_name LABEL_ITEM_RE = r"items\.\d\.([A-Za-z0-9_.]*)(?:[\[\]0-9-:])*(?:\|[^}]+)*" LABEL_RE = [ # {{items.4.key}} - r'{{ *' + LABEL_ITEM_RE + r' *}}', + r"{{ *" + LABEL_ITEM_RE + r" *}}", # {% if ** %} - r'{% (?:el)*if ([^}]*)%}', + r"{% (?:el)*if ([^}]*)%}", # {% for item in items.42.another_keys %} - r'{% *for +[A-Za-z0-9_]+ +in +' + LABEL_ITEM_RE + r' *%}', + r"{% *for +[A-Za-z0-9_]+ +in +" + LABEL_ITEM_RE + r" *%}", ] def publish_labels(self, objects): if not objects: return tempdir = tempfile.mkdtemp("-ishtarlabels") - main_output_name = tempdir + os.path.sep + \ - slugify(self.name.replace(' ', '_').lower()) + '-' + \ - datetime.datetime.now().strftime('%Y-%m-%d-%H%M%S') - suffix = "." + self.template.name.split('.')[-1] + main_output_name = ( + tempdir + + os.path.sep + + slugify(self.name.replace(" ", "_").lower()) + + "-" + + datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S") + ) + suffix = "." + self.template.name.split(".")[-1] len_objects = len(objects) names = [] @@ -1719,7 +2122,7 @@ class DocumentTemplate(models.Model): raise TemplateSyntaxError(str(e), e.lineno) output_name = main_output_name + "-" + str(idx) + suffix names.append(output_name) - with open(output_name, 'wb') as output: + with open(output_name, "wb") as output: output.write(result) output_name = main_output_name + suffix o = OOoPy(infile=names[0], outfile=output_name) @@ -1731,28 +2134,34 @@ class DocumentTemplate(models.Model): OOTransforms.renumber_all(o.mimetype), OOTransforms.set_meta(o.mimetype), OOTransforms.Fix_OOo_Tag(), - OOTransforms.Manifest_Append() + OOTransforms.Manifest_Append(), ) - t.transform (o) + t.transform(o) o.close() return output_name class Area(HierarchicalType): - towns = models.ManyToManyField(Town, verbose_name=_("Towns"), blank=True, - related_name='areas') - reference = models.CharField(_("Reference"), max_length=200, blank=True, - null=True) + towns = models.ManyToManyField( + Town, verbose_name=_("Towns"), blank=True, related_name="areas" + ) + reference = models.CharField( + _("Reference"), max_length=200, blank=True, null=True + ) parent = models.ForeignKey( - 'self', blank=True, null=True, verbose_name=_("Parent"), + "self", + blank=True, + null=True, + verbose_name=_("Parent"), help_text=_("Only four level of parent are managed."), - related_name='children', on_delete=models.SET_NULL + related_name="children", + on_delete=models.SET_NULL, ) class Meta: verbose_name = _("Area") verbose_name_plural = _("Areas") - ordering = ('label',) + ordering = ("label",) def __str__(self): if not self.reference: @@ -1785,7 +2194,7 @@ def documentation_get_gender_values(): class BaseGenderedType(ValueGetter): - def get_values(self, prefix='', **kwargs): + def get_values(self, prefix="", **kwargs): dct = super(BaseGenderedType, self).get_values(prefix=prefix, **kwargs) assert hasattr(self, "grammatical_gender") dct[prefix + "grammatical_gender"] = self.grammatical_gender @@ -1794,8 +2203,13 @@ class BaseGenderedType(ValueGetter): class GenderedType(BaseGenderedType, GeneralType): grammatical_gender = models.CharField( - _("Grammatical gender"), max_length=1, choices=GENDER, - blank=True, default="", help_text=documentation_get_gender_values) + _("Grammatical gender"), + max_length=1, + choices=GENDER, + blank=True, + default="", + help_text=documentation_get_gender_values, + ) class Meta: abstract = True @@ -1815,7 +2229,7 @@ class OrganizationType(GenderedType): class Meta: verbose_name = _("Organization type") verbose_name_plural = _("Organization types") - ordering = ('label',) + ordering = ("label",) def get_orga_planning_service_label(): @@ -1841,52 +2255,62 @@ organization_type_pk_lazy = lazy(OrganizationType.get_or_create_pk, str) organization_type_pks_lazy = lazy(OrganizationType.get_or_create_pks, str) -class Organization(Address, Merge, OwnPerms, BaseGenderedType, ValueGetter, - MainItem): - TABLE_COLS = ('name', 'organization_type', 'town') +class Organization( + Address, Merge, OwnPerms, BaseGenderedType, ValueGetter, MainItem +): + TABLE_COLS = ("name", "organization_type", "town") SLUG = "organization" - SHOW_URL = 'show-organization' - DELETE_URL = 'delete-organization' + SHOW_URL = "show-organization" + DELETE_URL = "delete-organization" # search parameters EXTRA_REQUEST_KEYS = {} - BASE_SEARCH_VECTORS = [SearchVectorConfig('name'), - SearchVectorConfig('town')] + BASE_SEARCH_VECTORS = [ + SearchVectorConfig("name"), + SearchVectorConfig("town"), + ] # alternative names of fields for searches ALT_NAMES = { - 'name': SearchAltName( - pgettext_lazy("key for text search", "name"), - 'name__iexact' + "name": SearchAltName( + pgettext_lazy("key for text search", "name"), "name__iexact" ), - 'organization_type': SearchAltName( + "organization_type": SearchAltName( pgettext_lazy("key for text search", "type"), - 'organization_type__label__iexact' + "organization_type__label__iexact", ), } QA_EDIT = QuickAction( - url="organization-qa-bulk-update", icon_class="fa fa-pencil", - text=_("Bulk update"), target="many", - rights=['change_organization']) - QUICK_ACTIONS = [ - QA_EDIT - ] + url="organization-qa-bulk-update", + icon_class="fa fa-pencil", + text=_("Bulk update"), + target="many", + rights=["change_organization"], + ) + QUICK_ACTIONS = [QA_EDIT] objects = UUIDModelManager() # fields uuid = models.UUIDField(default=uuid.uuid4) name = models.CharField(_("Name"), max_length=500) - organization_type = models.ForeignKey(OrganizationType, - verbose_name=_("Type")) + organization_type = models.ForeignKey( + OrganizationType, verbose_name=_("Type") + ) url = models.URLField(verbose_name=_("Web address"), blank=True, null=True) grammatical_gender = models.CharField( - _("Grammatical gender"), max_length=1, choices=GENDER, - blank=True, default="", help_text=documentation_get_gender_values) - cached_label = models.TextField(_("Cached name"), blank=True, default="", - db_index=True) + _("Grammatical gender"), + max_length=1, + choices=GENDER, + blank=True, + default="", + help_text=documentation_get_gender_values, + ) + cached_label = models.TextField( + _("Cached name"), blank=True, default="", db_index=True + ) - DOWN_MODEL_UPDATE = ['members'] + DOWN_MODEL_UPDATE = ["members"] class Meta: verbose_name = _("Organization") @@ -1899,14 +2323,13 @@ class Organization(Address, Merge, OwnPerms, BaseGenderedType, ValueGetter, ("delete_own_organization", "Can delete own Organization"), ) indexes = [ - GinIndex(fields=['data']), + GinIndex(fields=["data"]), ] def simple_lbl(self): if self.name: return self.name - return "{} - {}".format(self.organization_type, - self.town or "") + return "{} - {}".format(self.organization_type, self.town or "") def natural_key(self): return (self.uuid,) @@ -1918,33 +2341,36 @@ class Organization(Address, Merge, OwnPerms, BaseGenderedType, ValueGetter, if self.name: return self.name attrs = ["organization_type", "address", "town"] - items = [str(getattr(self, attr)) - for attr in attrs if getattr(self, attr)] + items = [ + str(getattr(self, attr)) for attr in attrs if getattr(self, attr) + ] if not items: items = [str(_("unknown organization"))] return " - ".join(items) def generate_merge_key(self): - self.merge_key = slugify(self.name or '') + self.merge_key = slugify(self.name or "") if not self.merge_key: self.merge_key = self.EMPTY_MERGE_KEY if self.town: - self.merge_key += "-" + slugify(self.town or '') + self.merge_key += "-" + slugify(self.town or "") if self.address: - self.merge_key += "-" + slugify(self.address or '') + self.merge_key += "-" + slugify(self.address or "") @property def associated_filename(self): - values = [str(getattr(self, attr)) - for attr in ('organization_type', 'name') - if getattr(self, attr)] + values = [ + str(getattr(self, attr)) + for attr in ("organization_type", "name") + if getattr(self, attr) + ] return slugify("-".join(values)) @classmethod @pre_importer_action def import_get_publisher_type(cls, context, value): if context["name"]: - q = OrganizationType.objects.filter(txt_idx='publisher') + q = OrganizationType.objects.filter(txt_idx="publisher") if not q.count(): return context["organization_type"] = q.all()[0] @@ -1957,7 +2383,7 @@ class PersonType(GeneralType): class Meta: verbose_name = _("Person type") verbose_name_plural = _("Person types") - ordering = ('label',) + ordering = ("label",) post_save.connect(post_save_cache, sender=PersonType) @@ -2030,7 +2456,7 @@ class TitleType(GenderedType): class Meta: verbose_name = _("Title type") verbose_name_plural = _("Title types") - ordering = ('label',) + ordering = ("label",) @classmethod def get_documentation_string(cls): @@ -2041,7 +2467,7 @@ class TitleType(GenderedType): doc += ", **long_title** {}".format(_("Long title")) return doc - def get_values(self, prefix='', **kwargs): + def get_values(self, prefix="", **kwargs): dct = super(TitleType, self).get_values(prefix=prefix, **kwargs) dct[prefix + "long_title"] = self.long_title return dct @@ -2053,109 +2479,139 @@ post_delete.connect(post_save_cache, sender=TitleType) class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): SLUG = "person" - _prefix = 'person_' + _prefix = "person_" TYPE = ( - ('Mr', _('Mr')), - ('Ms', _('Miss')), - ('Mr and Miss', _('Mr and Mrs')), - ('Md', _('Mrs')), - ('Dr', _('Doctor')), - ) - TABLE_COLS = ('name', 'surname', 'raw_name', 'email', 'person_types_list', - 'attached_to', 'town') - TABLE_COLS_ACCOUNT = ('name', 'surname', 'raw_name', 'email', - 'profiles_list', 'attached_to', 'town') - SHOW_URL = 'show-person' - MODIFY_URL = 'person_modify' - DELETE_URL = 'person_delete' + ("Mr", _("Mr")), + ("Ms", _("Miss")), + ("Mr and Miss", _("Mr and Mrs")), + ("Md", _("Mrs")), + ("Dr", _("Doctor")), + ) + TABLE_COLS = ( + "name", + "surname", + "raw_name", + "email", + "person_types_list", + "attached_to", + "town", + ) + TABLE_COLS_ACCOUNT = ( + "name", + "surname", + "raw_name", + "email", + "profiles_list", + "attached_to", + "town", + ) + SHOW_URL = "show-person" + MODIFY_URL = "person_modify" + DELETE_URL = "person_delete" BASE_SEARCH_VECTORS = [ - SearchVectorConfig('name'), - SearchVectorConfig('surname'), - SearchVectorConfig('raw_name'), - SearchVectorConfig('town'), - SearchVectorConfig('attached_to__name'), - SearchVectorConfig('email')] + SearchVectorConfig("name"), + SearchVectorConfig("surname"), + SearchVectorConfig("raw_name"), + SearchVectorConfig("town"), + SearchVectorConfig("attached_to__name"), + SearchVectorConfig("email"), + ] # search parameters - REVERSED_BOOL_FIELDS = ['ishtaruser__isnull'] + REVERSED_BOOL_FIELDS = ["ishtaruser__isnull"] EXTRA_REQUEST_KEYS = { - 'ishtaruser__isnull': 'ishtaruser__isnull', - 'attached_to': 'attached_to', - } - COL_LABELS = { - 'attached_to': _("Organization") + "ishtaruser__isnull": "ishtaruser__isnull", + "attached_to": "attached_to", } + COL_LABELS = {"attached_to": _("Organization")} # alternative names of fields for searches ALT_NAMES = { - 'name': SearchAltName( - pgettext_lazy("key for text search", "name"), - 'name__iexact' + "name": SearchAltName( + pgettext_lazy("key for text search", "name"), "name__iexact" ), - 'surname': SearchAltName( - pgettext_lazy("key for text search", "surname"), - 'surname__iexact' + "surname": SearchAltName( + pgettext_lazy("key for text search", "surname"), "surname__iexact" ), - 'email': SearchAltName( - pgettext_lazy("key for text search", "email"), - 'email__iexact' + "email": SearchAltName( + pgettext_lazy("key for text search", "email"), "email__iexact" ), - 'person_types': SearchAltName( + "person_types": SearchAltName( pgettext_lazy("key for text search", "type"), - 'person_types__label__iexact' + "person_types__label__iexact", ), - 'attached_to': SearchAltName( + "attached_to": SearchAltName( pgettext_lazy("key for text search", "organization"), - 'attached_to__cached_label__iexact' + "attached_to__cached_label__iexact", ), - 'ishtaruser__isnull': SearchAltName( + "ishtaruser__isnull": SearchAltName( pgettext_lazy("key for text search", "has-account"), - 'ishtaruser__isnull' + "ishtaruser__isnull", ), } QA_EDIT = QuickAction( - url="person-qa-bulk-update", icon_class="fa fa-pencil", - text=_("Bulk update"), target="many", - rights=['change_person']) - QUICK_ACTIONS = [ - QA_EDIT - ] + url="person-qa-bulk-update", + icon_class="fa fa-pencil", + text=_("Bulk update"), + target="many", + rights=["change_person"], + ) + QUICK_ACTIONS = [QA_EDIT] objects = UUIDModelManager() # fields uuid = models.UUIDField(default=uuid.uuid4) - old_title = models.CharField(_("Title"), max_length=100, choices=TYPE, - blank=True, null=True) - title = models.ForeignKey(TitleType, verbose_name=_("Title"), - on_delete=models.SET_NULL, - blank=True, null=True) - salutation = models.CharField(_("Salutation"), max_length=200, - blank=True, null=True) + old_title = models.CharField( + _("Title"), max_length=100, choices=TYPE, blank=True, null=True + ) + title = models.ForeignKey( + TitleType, + verbose_name=_("Title"), + on_delete=models.SET_NULL, + blank=True, + null=True, + ) + salutation = models.CharField( + _("Salutation"), max_length=200, blank=True, null=True + ) surname = models.CharField( - _("Surname"), max_length=50, blank=True, null=True, - help_text=_("Attention, historical and unfortunate residue in the " - "code of an initial translation error.")) - name = models.CharField(_("Name"), max_length=200, blank=True, - null=True) - raw_name = models.CharField(_("Raw name"), max_length=300, blank=True, - null=True) - contact_type = models.CharField(_("Contact type"), max_length=300, - blank=True, null=True) + _("Surname"), + max_length=50, + blank=True, + null=True, + help_text=_( + "Attention, historical and unfortunate residue in the " + "code of an initial translation error." + ), + ) + name = models.CharField(_("Name"), max_length=200, blank=True, null=True) + raw_name = models.CharField( + _("Raw name"), max_length=300, blank=True, null=True + ) + contact_type = models.CharField( + _("Contact type"), max_length=300, blank=True, null=True + ) comment = models.TextField(_("Comment"), blank=True, default="") person_types = models.ManyToManyField(PersonType, verbose_name=_("Types")) attached_to = models.ForeignKey( - 'Organization', related_name='members', on_delete=models.SET_NULL, - verbose_name=_("Is attached to"), blank=True, null=True) - cached_label = models.TextField(_("Cached name"), blank=True, default="", - db_index=True) + "Organization", + related_name="members", + on_delete=models.SET_NULL, + verbose_name=_("Is attached to"), + blank=True, + null=True, + ) + cached_label = models.TextField( + _("Cached name"), blank=True, default="", db_index=True + ) DOWN_MODEL_UPDATE = ["author"] class Meta: verbose_name = _("Person") verbose_name_plural = _("Persons") indexes = [ - GinIndex(fields=['data']), + GinIndex(fields=["data"]), ] permissions = ( ("view_person", "Can view all Persons"), @@ -2176,7 +2632,9 @@ class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): def full_title(self): return " ".join( str(getattr(self, attr)) - for attr in ['title', 'salutation'] if getattr(self, attr)) + for attr in ["title", "salutation"] + if getattr(self, attr) + ) @property def current_profile(self): @@ -2202,13 +2660,18 @@ class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): return [] items.append( """<span class="organization">{}</span>""".format( - self.attached_to.name)) + self.attached_to.name + ) + ) items += orga_address return items def simple_lbl(self): - values = [str(getattr(self, attr)) for attr in ('surname', 'name') - if getattr(self, attr)] + values = [ + str(getattr(self, attr)) + for attr in ("surname", "name") + if getattr(self, attr) + ] if not values and self.raw_name: values = [self.raw_name] return " ".join(values) @@ -2217,7 +2680,7 @@ class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): return self.cached_label or "" def _generate_cached_label(self): - lbl = get_generated_id('person_raw_name', self) + lbl = get_generated_id("person_raw_name", self) if not lbl: return "-" if self.attached_to: @@ -2227,8 +2690,11 @@ class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): def fancy_str(self): values = ["<strong>"] - values += [str(getattr(self, attr)) for attr in ('surname', 'name') - if getattr(self, attr)] + values += [ + str(getattr(self, attr)) + for attr in ("surname", "name") + if getattr(self, attr) + ] if not values and self.raw_name: values += [self.raw_name] values += ["</strong>"] @@ -2236,16 +2702,18 @@ class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): if self.attached_to: attached_to = str(self.attached_to) if values: - values.append('-') + values.append("-") values.append(attached_to) return " ".join(values) - def get_values(self, prefix='', no_values=False, filtr=None, **kwargs): + def get_values(self, prefix="", no_values=False, filtr=None, **kwargs): values = super(Person, self).get_values( - prefix=prefix, no_values=no_values, filtr=filtr, **kwargs) + prefix=prefix, no_values=no_values, filtr=filtr, **kwargs + ) if not self.attached_to: values.update( - Person.get_empty_values(prefix=prefix + 'attached_to_')) + Person.get_empty_values(prefix=prefix + "attached_to_") + ) return values person_types_list_lbl = _("Types") @@ -2262,9 +2730,9 @@ class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): def generate_merge_key(self): if self.name and self.name.strip(): - self.merge_key = slugify(self.name.strip()) + \ - (('-' + slugify(self.surname.strip())) - if self.surname else '') + self.merge_key = slugify(self.name.strip()) + ( + ("-" + slugify(self.surname.strip())) if self.surname else "" + ) elif self.raw_name and self.raw_name.strip(): self.merge_key = slugify(self.raw_name.strip()) elif self.attached_to: @@ -2278,46 +2746,67 @@ class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): return not self.attached_to def has_right(self, right_name, session=None, obj=None): - if '.' in right_name: - right_name = right_name.split('.')[-1] + if "." in right_name: + right_name = right_name.split(".")[-1] res, cache_key = "", "" if session: - cache_key = 'session-{}-{}'.format(session.session_key, right_name) + cache_key = "session-{}-{}".format(session.session_key, right_name) res = cache.get(cache_key) if res in (True, False): return res # list all cache key in order to clean them on profile change - cache_key_list = 'sessionlist-{}'.format(session.session_key) + cache_key_list = "sessionlist-{}".format(session.session_key) key_list = cache.get(cache_key_list, []) key_list.append(cache_key) cache.set(cache_key_list, key_list, settings.CACHE_TIMEOUT) if type(right_name) in (list, tuple): - res = bool(self.profiles.filter( - current=True, - profile_type__txt_idx__in=right_name).count()) or \ - bool(self.profiles.filter( - current=True, - profile_type__groups__permissions__codename__in=right_name - ).count()) or \ - bool(self.ishtaruser.user_ptr.groups.filter( - permissions__codename__in=right_name - ).count()) or \ - bool(self.ishtaruser.user_ptr.user_permissions.filter( - codename__in=right_name).count()) + res = ( + bool( + self.profiles.filter( + current=True, profile_type__txt_idx__in=right_name + ).count() + ) + or bool( + self.profiles.filter( + current=True, + profile_type__groups__permissions__codename__in=right_name, + ).count() + ) + or bool( + self.ishtaruser.user_ptr.groups.filter( + permissions__codename__in=right_name + ).count() + ) + or bool( + self.ishtaruser.user_ptr.user_permissions.filter( + codename__in=right_name + ).count() + ) + ) else: - res = bool( - self.profiles.filter( - current=True, - profile_type__txt_idx=right_name).count()) or \ - bool(self.profiles.filter( - current=True, - profile_type__groups__permissions__codename=right_name - ).count()) or \ - bool(self.ishtaruser.user_ptr.groups.filter( - permissions__codename__in=[right_name] - ).count()) or \ - bool(self.ishtaruser.user_ptr.user_permissions.filter( - codename__in=[right_name]).count()) + res = ( + bool( + self.profiles.filter( + current=True, profile_type__txt_idx=right_name + ).count() + ) + or bool( + self.profiles.filter( + current=True, + profile_type__groups__permissions__codename=right_name, + ).count() + ) + or bool( + self.ishtaruser.user_ptr.groups.filter( + permissions__codename__in=[right_name] + ).count() + ) + or bool( + self.ishtaruser.user_ptr.user_permissions.filter( + codename__in=[right_name] + ).count() + ) + ) if session: cache.set(cache_key, res, settings.CACHE_TIMEOUT) return res @@ -2326,9 +2815,11 @@ class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): values = [] if self.title: values = [self.title.label] - values += [str(getattr(self, attr)) - for attr in ('salutation', 'surname', 'name') - if getattr(self, attr)] + values += [ + str(getattr(self, attr)) + for attr in ("salutation", "surname", "name") + if getattr(self, attr) + ] if not values and self.raw_name: values = [self.raw_name] if self.attached_to: @@ -2337,9 +2828,11 @@ class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): @property def associated_filename(self): - values = [str(getattr(self, attr)) - for attr in ('surname', 'name', 'attached_to') - if getattr(self, attr)] + values = [ + str(getattr(self, attr)) + for attr in ("surname", "name", "attached_to") + if getattr(self, attr) + ] return slugify("-".join(values)) def docs_q(self): @@ -2347,52 +2840,56 @@ class Person(Address, Merge, OwnPerms, ValueGetter, MainItem): def operation_docs_q(self): return Document.objects.filter( - authors__person=self, operations__pk__isnull=False) + authors__person=self, operations__pk__isnull=False + ) def contextrecord_docs_q(self): return Document.objects.filter( - authors__person=self, context_records__pk__isnull=False) + authors__person=self, context_records__pk__isnull=False + ) def find_docs_q(self): return Document.objects.filter( - authors__person=self, finds__pk__isnull=False) + authors__person=self, finds__pk__isnull=False + ) def save(self, *args, **kwargs): super(Person, self).save(*args, **kwargs) - raw_name = get_generated_id('person_raw_name', self) + raw_name = get_generated_id("person_raw_name", self) if raw_name and self.raw_name != raw_name: self.raw_name = raw_name self.save() - if hasattr(self, 'responsible_town_planning_service'): + if hasattr(self, "responsible_town_planning_service"): for fle in self.responsible_town_planning_service.all(): fle.save() # force update of raw_town_planning_service - if hasattr(self, 'general_contractor'): + if hasattr(self, "general_contractor"): for fle in self.general_contractor.all(): fle.save() # force update of raw_general_contractor @classmethod def get_query_owns(cls, ishtaruser): - return \ - Q(operation_scientist_responsability__collaborators__ishtaruser - =ishtaruser) | \ - Q(operation_scientist_responsability__scientist__ishtaruser - =ishtaruser) | \ - Q(operation_collaborator__collaborators__ishtaruser - =ishtaruser) | \ - Q(operation_collaborator__scientist__ishtaruser=ishtaruser) + return ( + Q( + operation_scientist_responsability__collaborators__ishtaruser=ishtaruser + ) + | Q( + operation_scientist_responsability__scientist__ishtaruser=ishtaruser + ) + | Q(operation_collaborator__collaborators__ishtaruser=ishtaruser) + | Q(operation_collaborator__scientist__ishtaruser=ishtaruser) + ) post_save.connect(cached_label_changed, sender=Person) class ProfileType(GeneralType): - groups = models.ManyToManyField(Group, verbose_name=_("Groups"), - blank=True) + groups = models.ManyToManyField(Group, verbose_name=_("Groups"), blank=True) class Meta: verbose_name = _("Profile type") verbose_name_plural = _("Profile types") - ordering = ('label',) + ordering = ("label",) post_save.connect(post_save_cache, sender=ProfileType) @@ -2409,36 +2906,42 @@ class ProfileTypeSummary(ProfileType): class UserProfile(models.Model): name = models.CharField(_("Name"), blank=True, default="", max_length=100) profile_type = models.ForeignKey( - ProfileType, verbose_name=_("Profile type")) - areas = models.ManyToManyField("Area", verbose_name=_("Areas"), - blank=True, related_name='profiles') + ProfileType, verbose_name=_("Profile type") + ) + areas = models.ManyToManyField( + "Area", verbose_name=_("Areas"), blank=True, related_name="profiles" + ) current = models.BooleanField(_("Current profile"), default=False) show_field_number = models.BooleanField( - _("Show field number"), default=False) + _("Show field number"), default=False + ) auto_pin = models.BooleanField(_("Automatically pin"), default=False) - display_pin_menu = models.BooleanField(_("Display pin menu"), - default=False) + display_pin_menu = models.BooleanField(_("Display pin menu"), default=False) person = models.ForeignKey( - Person, verbose_name=_("Person"), related_name='profiles') + Person, verbose_name=_("Person"), related_name="profiles" + ) class Meta: verbose_name = _("User profile") verbose_name_plural = _("User profiles") - unique_together = (('name', 'profile_type', 'person'),) + unique_together = (("name", "profile_type", "person"),) def __str__(self): lbl = self.name or str(self.profile_type) if not self.areas.count(): return lbl - return "{} ({})".format(lbl, ", ".join(str(area) for area in self.areas.all())) + return "{} ({})".format( + lbl, ", ".join(str(area) for area in self.areas.all()) + ) @property def query_towns(self): return Town.objects.filter( - Q(areas__profiles=self) | Q(areas__parent__profiles=self) | - Q(areas__parent__parent__profiles=self) | - Q(areas__parent__parent__parent__profiles=self) | - Q(areas__parent__parent__parent__parent__profiles=self) + Q(areas__profiles=self) + | Q(areas__parent__profiles=self) + | Q(areas__parent__parent__profiles=self) + | Q(areas__parent__parent__parent__profiles=self) + | Q(areas__parent__parent__parent__parent__profiles=self) ) @property @@ -2451,12 +2954,12 @@ class UserProfile(models.Model): new_item.pk = None name = self.name for key in kwargs: - if key == 'name': + if key == "name": name = kwargs[key] setattr(new_item, key, kwargs[key]) while UserProfile.objects.filter( - name=name, profile_type=self.profile_type, - person=self.person).count(): + name=name, profile_type=self.profile_type, person=self.person + ).count(): name += str(_(" - duplicate")) new_item.name = name new_item.save() @@ -2464,17 +2967,26 @@ class UserProfile(models.Model): new_item.areas.add(area) return new_item - def save(self, force_insert=False, force_update=False, using=None, - update_fields=None): + def save( + self, + force_insert=False, + force_update=False, + using=None, + update_fields=None, + ): super(UserProfile, self).save( - force_insert=force_insert, force_update=force_update, using=using, - update_fields=update_fields) + force_insert=force_insert, + force_update=force_update, + using=using, + update_fields=update_fields, + ) # only one current profile per user if not self.current: return q = UserProfile.objects.filter( - person=self.person, current=True).exclude(pk=self.pk) + person=self.person, current=True + ).exclude(pk=self.pk) if not q.count(): return for p in q.all(): @@ -2483,9 +2995,9 @@ class UserProfile(models.Model): def post_save_userprofile(sender, **kwargs): - if not kwargs.get('instance'): + if not kwargs.get("instance"): return - instance = kwargs.get('instance') + instance = kwargs.get("instance") try: instance.person.ishtaruser.show_field_number(update=True) except IshtarUser.DoesNotExist: @@ -2497,64 +3009,72 @@ post_save.connect(post_save_userprofile, sender=UserProfile) class IshtarUser(FullSearch): SLUG = "ishtaruser" - TABLE_COLS = ('username', 'person__name', 'person__surname', - 'person__email', 'person__person_types_list', - 'person__attached_to__name') + TABLE_COLS = ( + "username", + "person__name", + "person__surname", + "person__email", + "person__person_types_list", + "person__attached_to__name", + ) BASE_SEARCH_VECTORS = [ - SearchVectorConfig('user_ptr__username'), - SearchVectorConfig('person__name'), - SearchVectorConfig('person__surname'), - SearchVectorConfig('person__email'), - SearchVectorConfig('person__town'), - SearchVectorConfig('person__attached_to__name')] + SearchVectorConfig("user_ptr__username"), + SearchVectorConfig("person__name"), + SearchVectorConfig("person__surname"), + SearchVectorConfig("person__email"), + SearchVectorConfig("person__town"), + SearchVectorConfig("person__attached_to__name"), + ] CACHED_LABELS = [] # needed to force search vector update # search parameters EXTRA_REQUEST_KEYS = { - 'person__person_types_list': 'person__person_types__label' + "person__person_types_list": "person__person_types__label" } COL_LABELS = { - 'person__attached_to__name': _("Organization"), - 'username': _("Username") + "person__attached_to__name": _("Organization"), + "username": _("Username"), } # alternative names of fields for searches ALT_NAMES = { - 'username': SearchAltName( + "username": SearchAltName( pgettext_lazy("key for text search", "username"), - 'user_ptr__username__iexact' + "user_ptr__username__iexact", ), - 'name': SearchAltName( - pgettext_lazy("key for text search", "name"), - 'person__name__iexact' + "name": SearchAltName( + pgettext_lazy("key for text search", "name"), "person__name__iexact" ), - 'surname': SearchAltName( + "surname": SearchAltName( pgettext_lazy("key for text search", "surname"), - 'person__surname__iexact' + "person__surname__iexact", ), - 'email': SearchAltName( + "email": SearchAltName( pgettext_lazy("key for text search", "email"), - 'person__email__iexact' + "person__email__iexact", ), - 'person_types': SearchAltName( + "person_types": SearchAltName( pgettext_lazy("key for text search", "type"), - 'person__person_types__label__iexact' + "person__person_types__label__iexact", ), - 'attached_to': SearchAltName( + "attached_to": SearchAltName( pgettext_lazy("key for text search", "organization"), - 'person__attached_to__cached_label__iexact' + "person__attached_to__cached_label__iexact", ), } # fields - user_ptr = models.OneToOneField(User, primary_key=True, - related_name='ishtaruser') - person = models.OneToOneField(Person, verbose_name=_("Person"), - related_name='ishtaruser') + user_ptr = models.OneToOneField( + User, primary_key=True, related_name="ishtaruser" + ) + person = models.OneToOneField( + Person, verbose_name=_("Person"), related_name="ishtaruser" + ) advanced_shortcut_menu = models.BooleanField( - _("Advanced shortcut menu"), default=False) + _("Advanced shortcut menu"), default=False + ) class Meta: verbose_name = _("Ishtar user") @@ -2564,7 +3084,7 @@ class IshtarUser(FullSearch): return str(self.person) def show_field_number(self, update=False): - cache_key, value = get_cache(self.__class__, ['show_field_number']) + cache_key, value = get_cache(self.__class__, ["show_field_number"]) if not update and value is not None: return value value = False @@ -2577,8 +3097,8 @@ class IshtarUser(FullSearch): def current_profile_name(self): q = UserProfile.objects.filter(current=True, person__ishtaruser=self) if q.count(): - vals = q.values('profile_type__label', 'name').all()[0] - return vals['name'] or vals['profile_type__label'] + vals = q.values("profile_type__label", "name").all()[0] + return vals["name"] or vals["profile_type__label"] profile = self.person.current_profile if not profile: return "" @@ -2596,14 +3116,16 @@ class IshtarUser(FullSearch): ishtaruser = q.all()[0] person = ishtaruser.person admin, created = ProfileType.objects.get_or_create( - txt_idx='administrator') + txt_idx="administrator" + ) if user.is_superuser: if UserProfile.objects.filter( - profile_type=admin, person=person).count(): + profile_type=admin, person=person + ).count(): return UserProfile.objects.get_or_create( - profile_type=admin, person=person, - defaults={'current': True}) + profile_type=admin, person=person, defaults={"current": True} + ) @classmethod def create_from_user(cls, user): @@ -2611,9 +3133,9 @@ class IshtarUser(FullSearch): surname = user.first_name or default name = user.last_name or default email = user.email - person = Person.objects.create(surname=surname, - name=name, email=email, - history_modifier=user) + person = Person.objects.create( + surname=surname, name=name, email=email, history_modifier=user + ) return cls.objects.create(user_ptr=user, person=person) def has_right(self, right_name, session=None): @@ -2634,6 +3156,7 @@ class Basket(FullSearch, OwnPerms, ValueGetter, TemplateItem): Abstract class for a basket Subclass must be defined with an "items" ManyToManyField """ + IS_BASKET = True uuid = models.UUIDField(default=uuid.uuid4) label = models.CharField(_("Label"), max_length=1000) @@ -2641,47 +3164,59 @@ class Basket(FullSearch, OwnPerms, ValueGetter, TemplateItem): slug = models.SlugField(_("Slug"), blank=True, null=True) public = models.BooleanField(_("Public"), default=False) user = models.ForeignKey( - IshtarUser, blank=True, null=True, related_name='%(class)ss', + IshtarUser, + blank=True, + null=True, + related_name="%(class)ss", on_delete=models.SET_NULL, - verbose_name=_("Owner")) + verbose_name=_("Owner"), + ) available = models.BooleanField(_("Available"), default=True) shared_with = models.ManyToManyField( - IshtarUser, verbose_name=_("Shared (read) with"), blank=True, - related_name='shared_%(class)ss' + IshtarUser, + verbose_name=_("Shared (read) with"), + blank=True, + related_name="shared_%(class)ss", ) shared_write_with = models.ManyToManyField( - IshtarUser, verbose_name=_("Shared (read/edit) with"), blank=True, - related_name='shared_write_%(class)ss' + IshtarUser, + verbose_name=_("Shared (read/edit) with"), + blank=True, + related_name="shared_write_%(class)ss", ) objects = UUIDModelManager() - TABLE_COLS = ['label', 'user'] + TABLE_COLS = ["label", "user"] BASE_SEARCH_VECTORS = [ - SearchVectorConfig('label'), SearchVectorConfig('comment', 'local'), + SearchVectorConfig("label"), + SearchVectorConfig("comment", "local"), ] - PARENT_SEARCH_VECTORS = ['user'] + PARENT_SEARCH_VECTORS = ["user"] # M2M_SEARCH_VECTORS = [SearchVectorConfig('items')] CACHED_LABELS = [] # needed to force search vector update class Meta: abstract = True - ordering = ('label', ) - unique_together = (('label', 'user'),) + ordering = ("label",) + unique_together = (("label", "user"),) def __str__(self): return self.label def natural_key(self): - return (self.uuid, ) + return (self.uuid,) @classmethod def BASE_REQUEST(cls, request): - if not request.user or not getattr(request.user, 'ishtaruser', None): + if not request.user or not getattr(request.user, "ishtaruser", None): return Q(pk=None) ishtaruser = request.user.ishtaruser - return Q(user=ishtaruser) | Q(shared_with=ishtaruser) | Q( - shared_write_with=ishtaruser) + return ( + Q(user=ishtaruser) + | Q(shared_with=ishtaruser) + | Q(shared_write_with=ishtaruser) + ) @property def cached_label(self): @@ -2693,22 +3228,25 @@ class Basket(FullSearch, OwnPerms, ValueGetter, TemplateItem): @classmethod def get_short_menu_class(cls, pk): - return 'basket' + return "basket" @property def associated_filename(self): - return "{}-{}".format(datetime.date.today().strftime( - "%Y-%m-%d"), slugify(self.label)) + return "{}-{}".format( + datetime.date.today().strftime("%Y-%m-%d"), slugify(self.label) + ) @classmethod def get_query_owns(cls, ishtaruser): - return Q(user=ishtaruser) | Q(shared_with=ishtaruser) | Q( - shared_write_with=ishtaruser) + return ( + Q(user=ishtaruser) + | Q(shared_with=ishtaruser) + | Q(shared_write_with=ishtaruser) + ) @classmethod def get_write_query_owns(cls, ishtaruser): - return Q(user=ishtaruser) | Q( - shared_write_with=ishtaruser) + return Q(user=ishtaruser) | Q(shared_write_with=ishtaruser) def duplicate(self, label=None, ishtaruser=None): """ @@ -2721,7 +3259,9 @@ class Basket(FullSearch, OwnPerms, ValueGetter, TemplateItem): basket_pk = "{}_id".format(self.SLUG) item_pk = "{}_id".format(self.items.model.SLUG) q = through.objects.filter(**{basket_pk: self.pk}) - items = [r[item_pk] for r in q.values("pk", item_pk).order_by("pk").all()] + items = [ + r[item_pk] for r in q.values("pk", item_pk).order_by("pk").all() + ] new_item = self new_item.pk = None if ishtaruser: @@ -2729,7 +3269,8 @@ class Basket(FullSearch, OwnPerms, ValueGetter, TemplateItem): if not label: label = new_item.label while self.__class__.objects.filter( - label=label, user=new_item.user).count(): + label=label, user=new_item.user + ).count(): label += str(_(" - duplicate")) new_item.label = label new_item.save() @@ -2744,7 +3285,7 @@ class AuthorType(GeneralType): class Meta: verbose_name = _("Author type") verbose_name_plural = _("Author types") - ordering = ['order', 'label'] + ordering = ["order", "label"] post_save.connect(post_save_cache, sender=AuthorType) @@ -2753,20 +3294,22 @@ post_delete.connect(post_save_cache, sender=AuthorType) class Author(FullSearch): SLUG = "author" - PARENT_SEARCH_VECTORS = ['person'] + PARENT_SEARCH_VECTORS = ["person"] uuid = models.UUIDField(default=uuid.uuid4) - person = models.ForeignKey(Person, verbose_name=_("Person"), - related_name='author') + person = models.ForeignKey( + Person, verbose_name=_("Person"), related_name="author" + ) author_type = models.ForeignKey(AuthorType, verbose_name=_("Author type")) - cached_label = models.TextField(_("Cached name"), blank=True, default="", - db_index=True) + cached_label = models.TextField( + _("Cached name"), blank=True, default="", db_index=True + ) objects = UUIDModelManager() class Meta: verbose_name = _("Author") verbose_name_plural = _("Authors") - ordering = ('author_type__order', 'person__name') + ordering = ("author_type__order", "person__name") permissions = ( ("view_author", "Can view all Authors"), ("view_own_author", "Can view own Author"), @@ -2779,39 +3322,37 @@ class Author(FullSearch): return self.cached_label or "" def natural_key(self): - return self.uuid, + return (self.uuid,) def _generate_cached_label(self): - return str(self.person) + settings.JOINT + \ - str(self.author_type) + return str(self.person) + settings.JOINT + str(self.author_type) def fancy_str(self): - return self.person.fancy_str() + settings.JOINT + \ - str(self.author_type) + return self.person.fancy_str() + settings.JOINT + str(self.author_type) def related_sources(self): - return list(self.treatmentsource_related.all()) + \ - list(self.operationsource_related.all()) + \ - list(self.findsource_related.all()) + \ - list(self.contextrecordsource_related.all()) + return ( + list(self.treatmentsource_related.all()) + + list(self.operationsource_related.all()) + + list(self.findsource_related.all()) + + list(self.contextrecordsource_related.all()) + ) def public_representation(self): - return { - "type": str(self.author_type), - "person": str(self.person) - } + return {"type": str(self.author_type), "person": str(self.person)} def merge(self, item, keep_old=False): merge_model_objects(self, item, keep_old=keep_old) def author_post_save(sender, **kwargs): - if not kwargs.get('instance'): + if not kwargs.get("instance"): return cached_label_changed(sender, **kwargs) - instance = kwargs.get('instance') - q = Author.objects.filter(person=instance.person, - author_type=instance.author_type) + instance = kwargs.get("instance") + q = Author.objects.filter( + person=instance.person, author_type=instance.author_type + ) if q.count() <= 1: return authors = list(q.all()) @@ -2823,20 +3364,23 @@ post_save.connect(author_post_save, sender=Author) class SourceType(HierarchicalType): - coins_type = models.CharField(_("COInS export - type"), default='document', - max_length=100) - coins_genre = models.CharField(_("COInS export - genre"), blank=True, - default='', max_length=100) + coins_type = models.CharField( + _("COInS export - type"), default="document", max_length=100 + ) + coins_genre = models.CharField( + _("COInS export - genre"), blank=True, default="", max_length=100 + ) is_localized = models.BooleanField( - _("Is localized"), default=False, - help_text=_("Setting a language for this type of document is relevant") + _("Is localized"), + default=False, + help_text=_("Setting a language for this type of document is relevant"), ) - code = models.CharField(_("Code"), blank=True, default='', max_length=100) + code = models.CharField(_("Code"), blank=True, default="", max_length=100) class Meta: verbose_name = _("Document type") verbose_name_plural = _("Document types") - ordering = ['label'] + ordering = ["label"] post_save.connect(post_save_cache, sender=SourceType) @@ -2845,8 +3389,10 @@ post_delete.connect(post_save_cache, sender=SourceType) class SupportType(GeneralType): document_types = models.ManyToManyField( - "SourceType", blank=True, related_name='supports', - help_text=_("Only available for these document types") + "SourceType", + blank=True, + related_name="supports", + help_text=_("Only available for these document types"), ) class Meta: @@ -2860,19 +3406,26 @@ post_delete.connect(post_save_cache, sender=SupportType) class Format(GeneralType): iframe_template = models.TextField( - _("Iframe template"), blank=True, default="", - help_text=_("Template to insert an iframe for this format. Use django " - "template with a {{document}} variable matching the " - "current document.")) + _("Iframe template"), + blank=True, + default="", + help_text=_( + "Template to insert an iframe for this format. Use django " + "template with a {{document}} variable matching the " + "current document." + ), + ) document_types = models.ManyToManyField( - "SourceType", blank=True, related_name='formats', - help_text=_("Only available for these document types") + "SourceType", + blank=True, + related_name="formats", + help_text=_("Only available for these document types"), ) class Meta: verbose_name = _("Format type") verbose_name_plural = _("Format types") - ordering = ['label'] + ordering = ["label"] post_save.connect(post_save_cache, sender=Format) @@ -2885,7 +3438,7 @@ class LicenseType(GeneralType): class Meta: verbose_name = _("License type") verbose_name_plural = _("License types") - ordering = ('label',) + ordering = ("label",) class DocumentTag(GeneralType): @@ -2894,51 +3447,89 @@ class DocumentTag(GeneralType): class Meta: verbose_name = _("Document tag") verbose_name_plural = _("Document tags") - ordering = ('label',) + ordering = ("label",) post_save.connect(post_save_cache, sender=LicenseType) post_delete.connect(post_save_cache, sender=LicenseType) -class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, - ValueGetter, MainItem): +class Document( + BaseHistorizedItem, + CompleteIdentifierItem, + OwnPerms, + ImageModel, + ValueGetter, + MainItem, +): APP = "ishtar-common" MODEL = "document" - EXTERNAL_ID_KEY = 'document_external_id' - DELETE_URL = 'delete-document' + EXTERNAL_ID_KEY = "document_external_id" + DELETE_URL = "delete-document" # order is important: put the image in the first match found # other will be symbolic links RELATED_MODELS = [ - 'treatment_files', 'treatments', 'finds', 'context_records', - 'operations', 'sites', 'warehouses', 'containers', 'files', - 'administrativeacts', + "treatment_files", + "treatments", + "finds", + "context_records", + "operations", + "sites", + "warehouses", + "containers", + "files", + "administrativeacts", ] # same fields but in order for forms RELATED_MODELS_ALT = [ - 'finds', 'context_records', 'operations', 'sites', 'files', - 'administrativeacts', 'warehouses', 'containers', 'treatments', - 'treatment_files', + "finds", + "context_records", + "operations", + "sites", + "files", + "administrativeacts", + "warehouses", + "containers", + "treatments", + "treatment_files", ] - SLUG = 'document' + SLUG = "document" LINK_SPLIT = "<||>" GET_VALUES_EXCLUDE_FIELDS = ValueGetter.GET_VALUES_EXCLUDE_FIELDS + [ - "warehouses", "operations", "treatments", - "files", "treatment_files", "administrativeacts", "id", - "associated_links", "source_type_id", - "history_creator_id", "containers", "sites", - "main_image_warehouses", "main_image_operations", - "main_image_treatments", "main_image_files", - "main_image_treatment_files", "main_image_id", - "main_image_associated_links", "main_image_source_type_id", - "main_image_history_creator_id", "main_image_containers", + "warehouses", + "operations", + "treatments", + "files", + "treatment_files", + "administrativeacts", + "id", + "associated_links", + "source_type_id", + "history_creator_id", + "containers", + "sites", + "main_image_warehouses", + "main_image_operations", + "main_image_treatments", + "main_image_files", + "main_image_treatment_files", + "main_image_id", + "main_image_associated_links", + "main_image_source_type_id", + "main_image_history_creator_id", + "main_image_containers", "main_image_sites", ] - _TABLE_COLS = ['title', 'source_type', 'cache_related_label', - 'authors__cached_label', 'associated_url'] - COL_LINK = ['associated_url'] + _TABLE_COLS = [ + "title", + "source_type", + "cache_related_label", + "authors__cached_label", + "associated_url", + ] + COL_LINK = ["associated_url"] BASE_SEARCH_VECTORS = [ SearchVectorConfig("title"), SearchVectorConfig("source_type__label"), @@ -2950,368 +3541,428 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, SearchVectorConfig("additional_information", "local"), ] BASE_SEARCH_VECTORS += [ - SearchVectorConfig('treatment_files__name'), - SearchVectorConfig('treatments__cached_label'), - SearchVectorConfig('finds__cached_label'), - SearchVectorConfig('context_records__cached_label'), - SearchVectorConfig('operations__cached_label'), - SearchVectorConfig('sites__cached_label'), - SearchVectorConfig('warehouses__name'), - SearchVectorConfig('containers__cached_label'), - SearchVectorConfig('files__cached_label') + SearchVectorConfig("treatment_files__name"), + SearchVectorConfig("treatments__cached_label"), + SearchVectorConfig("finds__cached_label"), + SearchVectorConfig("context_records__cached_label"), + SearchVectorConfig("operations__cached_label"), + SearchVectorConfig("sites__cached_label"), + SearchVectorConfig("warehouses__name"), + SearchVectorConfig("containers__cached_label"), + SearchVectorConfig("files__cached_label"), + ] + PARENT_SEARCH_VECTORS = [ + "authors", + ] + M2M_SEARCH_VECTORS = [ + SearchVectorConfig("tags__label"), ] - PARENT_SEARCH_VECTORS = ['authors', ] - M2M_SEARCH_VECTORS = [SearchVectorConfig("tags__label"), ] - BOOL_FIELDS = ['duplicate'] + BOOL_FIELDS = ["duplicate"] COL_LABELS = { "authors__cached_label": _("Authors"), "complete_identifier": _("Identifier"), } - CACHED_LABELS = ['cache_related_label'] + CACHED_LABELS = ["cache_related_label"] CACHED_COMPLETE_ID = "" EXTRA_REQUEST_KEYS = { "operations": "operations__pk", "context_records": "context_records__pk", "context_records__operation": "context_records__operation__pk", "finds": "finds__pk", - "finds__base_finds__context_record": - "finds__base_finds__context_record__pk", - "finds__base_finds__context_record__operation": - "finds__base_finds__context_record__operation__pk", - 'authors__cached_label': 'authors__cached_label', - 'complete_identifier': 'complete_identifier', - 'authors__person__pk': 'authors__person__pk', + "finds__base_finds__context_record": "finds__base_finds__context_record__pk", + "finds__base_finds__context_record__operation": "finds__base_finds__context_record__operation__pk", + "authors__cached_label": "authors__cached_label", + "complete_identifier": "complete_identifier", + "authors__person__pk": "authors__person__pk", "container_id": "container_id", - 'publisher__pk': 'publisher__pk' + "publisher__pk": "publisher__pk", } # alternative names of fields for searches ALT_NAMES = { - 'authors': SearchAltName( + "authors": SearchAltName( pgettext_lazy("key for text search", "author"), - 'authors__cached_label__iexact' + "authors__cached_label__iexact", ), - 'publisher': SearchAltName( + "publisher": SearchAltName( pgettext_lazy("key for text search", "publisher"), - 'publisher__name__iexact' + "publisher__name__iexact", ), - 'publishing_year': SearchAltName( + "publishing_year": SearchAltName( pgettext_lazy("key for text search", "publishing-year"), - 'publishing_year' + "publishing_year", ), - 'title': SearchAltName( - pgettext_lazy("key for text search", "title"), - 'title__iexact' + "title": SearchAltName( + pgettext_lazy("key for text search", "title"), "title__iexact" ), - 'source_type': SearchAltName( + "source_type": SearchAltName( pgettext_lazy("key for text search", "type"), - 'source_type__label__iexact' + "source_type__label__iexact", ), - 'reference': SearchAltName( + "reference": SearchAltName( pgettext_lazy("key for text search", "reference"), - 'reference__iexact' + "reference__iexact", ), - 'internal_reference': SearchAltName( + "internal_reference": SearchAltName( pgettext_lazy("key for text search", "internal-reference"), - 'internal_reference__iexact' + "internal_reference__iexact", ), - 'description': SearchAltName( + "description": SearchAltName( pgettext_lazy("key for text search", "description"), - 'description__iexact' + "description__iexact", ), - 'tag': SearchAltName( - pgettext_lazy("key for text search", "tag"), - 'tags__label__iexact' + "tag": SearchAltName( + pgettext_lazy("key for text search", "tag"), "tags__label__iexact" ), - 'format': SearchAltName( + "format": SearchAltName( pgettext_lazy("key for text search", "format"), - 'format_type__label__iexact' + "format_type__label__iexact", ), - 'support': SearchAltName( + "support": SearchAltName( pgettext_lazy("key for text search", "medium"), - 'support_type__label__iexact' + "support_type__label__iexact", ), - 'language': SearchAltName( + "language": SearchAltName( pgettext_lazy("key for text search", "language"), - 'language__label__iexact' + "language__label__iexact", ), - 'licenses': SearchAltName( + "licenses": SearchAltName( pgettext_lazy("key for text search", "license"), - 'licenses__label__iexact' + "licenses__label__iexact", ), - 'scale': SearchAltName( - pgettext_lazy("key for text search", "scale"), - 'scale__iexact' + "scale": SearchAltName( + pgettext_lazy("key for text search", "scale"), "scale__iexact" ), - 'associated_url': SearchAltName( + "associated_url": SearchAltName( pgettext_lazy("key for text search", "url"), - 'associated_url__iexact' + "associated_url__iexact", ), - 'isbn': SearchAltName( - pgettext_lazy("key for text search", "isbn"), - 'isbn__iexact' + "isbn": SearchAltName( + pgettext_lazy("key for text search", "isbn"), "isbn__iexact" ), - 'issn': SearchAltName( - pgettext_lazy("key for text search", "issn"), - 'issn__iexact' + "issn": SearchAltName( + pgettext_lazy("key for text search", "issn"), "issn__iexact" ), - 'source': SearchAltName( + "source": SearchAltName( pgettext_lazy("key for text search", "source"), - 'source__title__iexact' + "source__title__iexact", ), - 'source_free_input': SearchAltName( + "source_free_input": SearchAltName( pgettext_lazy("key for text search", "source-free-input"), - 'source_free_input__iexact' + "source_free_input__iexact", ), - 'warehouse_container': SearchAltName( + "warehouse_container": SearchAltName( pgettext_lazy("key for text search", "warehouse-container"), - 'container__cached_label__iexact' + "container__cached_label__iexact", ), - 'warehouse_container_ref': SearchAltName( - pgettext_lazy("key for text search", - "warehouse-container-reference"), - 'container_ref__cached_label__iexact' + "warehouse_container_ref": SearchAltName( + pgettext_lazy( + "key for text search", "warehouse-container-reference" + ), + "container_ref__cached_label__iexact", ), - 'comment': SearchAltName( - pgettext_lazy("key for text search", "comment"), - 'comment__iexact' + "comment": SearchAltName( + pgettext_lazy("key for text search", "comment"), "comment__iexact" ), - 'additional_information': SearchAltName( + "additional_information": SearchAltName( pgettext_lazy("key for text search", "additional-information"), - 'additional_information__iexact' + "additional_information__iexact", ), - 'duplicate': SearchAltName( - pgettext_lazy("key for text search", "has-duplicate"), - 'duplicate' + "duplicate": SearchAltName( + pgettext_lazy("key for text search", "has-duplicate"), "duplicate" ), - 'operation': SearchAltName( + "operation": SearchAltName( pgettext_lazy("key for text search", "operation"), - 'operations__cached_label__iexact' + "operations__cached_label__iexact", ), - 'context_record': SearchAltName( + "context_record": SearchAltName( pgettext_lazy("key for text search", "context-record"), - 'context_records__cached_label__iexact' + "context_records__cached_label__iexact", ), - 'find_basket': SearchAltName( + "find_basket": SearchAltName( pgettext_lazy("key for text search", "basket-finds"), - 'finds__basket__label__iexact' + "finds__basket__label__iexact", ), - 'find': SearchAltName( + "find": SearchAltName( pgettext_lazy("key for text search", "find"), - 'finds__cached_label__iexact' + "finds__cached_label__iexact", ), - 'find__denomination': SearchAltName( + "find__denomination": SearchAltName( pgettext_lazy("key for text search", "find-denomination"), - 'finds__denomination__iexact' + "finds__denomination__iexact", ), - 'file': SearchAltName( + "file": SearchAltName( pgettext_lazy("key for text search", "file"), - 'files__cached_label__iexact' + "files__cached_label__iexact", ), - 'containers': SearchAltName( + "containers": SearchAltName( pgettext_lazy("key for text search", "container"), - 'containers__cached_label__iexact' + "containers__cached_label__iexact", ), - 'site': SearchAltName( + "site": SearchAltName( pgettext_lazy("key for text search", "site"), - 'sites__cached_label__iexact' + "sites__cached_label__iexact", ), - 'warehouse': SearchAltName( + "warehouse": SearchAltName( pgettext_lazy("key for text search", "warehouse"), - 'warehouses__name__iexact' + "warehouses__name__iexact", + ), + "image__isnull": SearchAltName( + pgettext_lazy("key for text search", "has-image"), "image__isnull" + ), + "associated_file__isnull": SearchAltName( + pgettext_lazy("key for text search", "has-file"), + "associated_file__isnull", + ), + "receipt_date__before": SearchAltName( + pgettext_lazy("key for text search", "receipt-date-before"), + "receipt_date__lte", + ), + "receipt_date__after": SearchAltName( + pgettext_lazy("key for text search", "receipt-date-after"), + "receipt_date__gte", + ), + "receipt_date_in_documentation__before": SearchAltName( + pgettext_lazy( + "key for text search", "receipt-in-documentation-date-before" + ), + "receipt_date_in_documentation__lte", + ), + "receipt_date_in_documentation__after": SearchAltName( + pgettext_lazy( + "key for text search", "receipt-in-documentation-date-after" + ), + "receipt_date_in_documentation__gte", + ), + "creation_date__before": SearchAltName( + pgettext_lazy("key for text search", "creation-date-before"), + "creation_date__lte", + ), + "creation_date__after": SearchAltName( + pgettext_lazy("key for text search", "creation-date-after"), + "creation_date__gte", ), - 'image__isnull': - SearchAltName( - pgettext_lazy("key for text search", "has-image"), - 'image__isnull'), - 'associated_file__isnull': - SearchAltName( - pgettext_lazy("key for text search", "has-file"), - 'associated_file__isnull'), - 'receipt_date__before': - SearchAltName( - pgettext_lazy("key for text search", "receipt-date-before"), - 'receipt_date__lte'), - 'receipt_date__after': - SearchAltName( - pgettext_lazy("key for text search", "receipt-date-after"), - 'receipt_date__gte'), - 'receipt_date_in_documentation__before': - SearchAltName( - pgettext_lazy("key for text search", - "receipt-in-documentation-date-before"), - 'receipt_date_in_documentation__lte'), - 'receipt_date_in_documentation__after': - SearchAltName( - pgettext_lazy("key for text search", - "receipt-in-documentation-date-after"), - 'receipt_date_in_documentation__gte'), - 'creation_date__before': - SearchAltName( - pgettext_lazy("key for text search", "creation-date-before"), - 'creation_date__lte'), - 'creation_date__after': - SearchAltName( - pgettext_lazy("key for text search", "creation-date-after"), - 'creation_date__gte'), } ALT_NAMES.update(BaseHistorizedItem.ALT_NAMES) # search parameters - REVERSED_BOOL_FIELDS = ['image__isnull', 'associated_file__isnull'] + REVERSED_BOOL_FIELDS = ["image__isnull", "associated_file__isnull"] DATED_FIELDS = [ - 'receipt_date__lte', - 'receipt_date__gte', - 'receipt_date_in_documentation__lte', - 'receipt_date_in_documentation__gte', - 'creation_date__lte', - 'creation_date__gte', + "receipt_date__lte", + "receipt_date__gte", + "receipt_date_in_documentation__lte", + "receipt_date_in_documentation__gte", + "creation_date__lte", + "creation_date__gte", ] objects = ExternalIdManager() RELATIVE_SESSION_NAMES = [ - ('find', 'finds__pk'), - ('contextrecord', 'context_records__pk'), - ('operation', 'operations__pk'), - ('site', 'sites__pk'), - ('file', 'files__pk'), - ('warehouse', 'warehouses__pk'), - ('treatment', 'treatments__pk'), - ('treatmentfile', 'treatment_files__pk'), - ('administrativeact', 'administrativeacts__pk'), + ("find", "finds__pk"), + ("contextrecord", "context_records__pk"), + ("operation", "operations__pk"), + ("site", "sites__pk"), + ("file", "files__pk"), + ("warehouse", "warehouses__pk"), + ("treatment", "treatments__pk"), + ("treatmentfile", "treatment_files__pk"), + ("administrativeact", "administrativeacts__pk"), ] UP_MODEL_QUERY = { - "operation": (pgettext_lazy("key for text search", "operation"), - 'cached_label'), - "contextrecord": (pgettext_lazy("key for text search", - "context-record"), 'cached_label'), - "file": (pgettext_lazy("key for text search", "file"), 'cached_label'), - "find": (pgettext_lazy("key for text search", "find"), 'cached_label'), - "site": (pgettext_lazy("key for text search", "site"), 'cached_label'), - "warehouse": (pgettext_lazy("key for text search", "warehouse"), - 'cached_label'), - "treatment": (pgettext_lazy("key for text search", "treatment"), - 'cached_label'), - "treatmentfile": (pgettext_lazy("key for text search", - "treatment-file"), 'cached_label'), + "operation": ( + pgettext_lazy("key for text search", "operation"), + "cached_label", + ), + "contextrecord": ( + pgettext_lazy("key for text search", "context-record"), + "cached_label", + ), + "file": (pgettext_lazy("key for text search", "file"), "cached_label"), + "find": (pgettext_lazy("key for text search", "find"), "cached_label"), + "site": (pgettext_lazy("key for text search", "site"), "cached_label"), + "warehouse": ( + pgettext_lazy("key for text search", "warehouse"), + "cached_label", + ), + "treatment": ( + pgettext_lazy("key for text search", "treatment"), + "cached_label", + ), + "treatmentfile": ( + pgettext_lazy("key for text search", "treatment-file"), + "cached_label", + ), } QA_EDIT = QuickAction( - url="document-qa-bulk-update", icon_class="fa fa-pencil", - text=_("Bulk update"), target="many", - rights=['change_document', 'change_own_document']) + url="document-qa-bulk-update", + icon_class="fa fa-pencil", + text=_("Bulk update"), + target="many", + rights=["change_document", "change_own_document"], + ) QUICK_ACTIONS = [ QA_EDIT, QuickAction( - url="document-qa-duplicate", icon_class="fa fa-clone", - text=_("Duplicate"), target="one", - rights=['change_document', 'change_own_document']), + url="document-qa-duplicate", + icon_class="fa fa-clone", + text=_("Duplicate"), + target="one", + rights=["change_document", "change_own_document"], + ), QuickAction( - url="document-qa-packaging", icon_class="fa fa-gift", - text=_("Packaging"), target="many", - rights=['change_document', 'change_own_document'], - module='warehouse' + url="document-qa-packaging", + icon_class="fa fa-gift", + text=_("Packaging"), + target="many", + rights=["change_document", "change_own_document"], + module="warehouse", ), ] SERIALIZATION_FILES = ["image", "thumbnail", "associated_file"] - title = models.TextField(_("Title"), blank=True, default='') + title = models.TextField(_("Title"), blank=True, default="") associated_file = models.FileField( verbose_name=_("Associated file"), - upload_to=get_image_path, blank=True, null=True, max_length=255, - help_text=max_size_help()) - index = models.IntegerField(verbose_name=_("Index"), blank=True, - null=True) + upload_to=get_image_path, + blank=True, + null=True, + max_length=255, + help_text=max_size_help(), + ) + index = models.IntegerField(verbose_name=_("Index"), blank=True, null=True) external_id = models.TextField(_("External ID"), blank=True, default="") reference = models.TextField(_("Ref."), blank=True, default="") - internal_reference = models.TextField(_("Internal ref."), blank=True, - default="") - source_type = models.ForeignKey(SourceType, verbose_name=_("Type"), - on_delete=models.SET_NULL, - null=True, blank=True) + internal_reference = models.TextField( + _("Internal ref."), blank=True, default="" + ) + source_type = models.ForeignKey( + SourceType, + verbose_name=_("Type"), + on_delete=models.SET_NULL, + null=True, + blank=True, + ) publisher = models.ForeignKey( - Organization, verbose_name=_("Publisher"), blank=True, null=True, - related_name='publish') + Organization, + verbose_name=_("Publisher"), + blank=True, + null=True, + related_name="publish", + ) publishing_year = models.PositiveIntegerField( - _("Year of publication"), blank=True, null=True) - licenses = models.ManyToManyField(LicenseType, verbose_name=_("License"), - blank=True) - tags = models.ManyToManyField(DocumentTag, verbose_name=_("Tags"), - blank=True) + _("Year of publication"), blank=True, null=True + ) + licenses = models.ManyToManyField( + LicenseType, verbose_name=_("License"), blank=True + ) + tags = models.ManyToManyField( + DocumentTag, verbose_name=_("Tags"), blank=True + ) language = models.ForeignKey( - Language, verbose_name=_("Language"), blank=True, null=True) + Language, verbose_name=_("Language"), blank=True, null=True + ) issn = models.CharField(_("ISSN"), blank=True, null=True, max_length=10) isbn = models.CharField(_("ISBN"), blank=True, null=True, max_length=17) - source = models.ForeignKey("Document", verbose_name=_("Source"), - blank=True, null=True, related_name="children") + source = models.ForeignKey( + "Document", + verbose_name=_("Source"), + blank=True, + null=True, + related_name="children", + ) source_free_input = models.CharField( - verbose_name=_("Source - free input"), blank=True, null=True, - max_length=500) + verbose_name=_("Source - free input"), + blank=True, + null=True, + max_length=500, + ) source_page_range = models.CharField( - verbose_name=_("Source - page range"), blank=True, null=True, - max_length=500) - support_type = models.ForeignKey(SupportType, verbose_name=_("Medium"), - on_delete=models.SET_NULL, - blank=True, null=True, ) - format_type = models.ForeignKey(Format, verbose_name=_("Format"), - on_delete=models.SET_NULL, - blank=True, null=True) - scale = models.CharField(_("Scale"), max_length=30, null=True, - blank=True) - authors = models.ManyToManyField(Author, verbose_name=_("Authors"), - related_name="documents") - authors_raw = models.CharField(verbose_name=_("Authors (raw)"), - blank=True, null=True, max_length=250) + verbose_name=_("Source - page range"), + blank=True, + null=True, + max_length=500, + ) + support_type = models.ForeignKey( + SupportType, + verbose_name=_("Medium"), + on_delete=models.SET_NULL, + blank=True, + null=True, + ) + format_type = models.ForeignKey( + Format, + verbose_name=_("Format"), + on_delete=models.SET_NULL, + blank=True, + null=True, + ) + scale = models.CharField(_("Scale"), max_length=30, null=True, blank=True) + authors = models.ManyToManyField( + Author, verbose_name=_("Authors"), related_name="documents" + ) + authors_raw = models.CharField( + verbose_name=_("Authors (raw)"), blank=True, null=True, max_length=250 + ) associated_url = models.URLField( - blank=True, null=True, max_length=1000, - verbose_name=_("Numerical ressource (web address)")) - receipt_date = models.DateField(blank=True, null=True, - verbose_name=_("Receipt date")) - creation_date = models.DateField(blank=True, null=True, - verbose_name=_("Creation date")) + blank=True, + null=True, + max_length=1000, + verbose_name=_("Numerical ressource (web address)"), + ) + receipt_date = models.DateField( + blank=True, null=True, verbose_name=_("Receipt date") + ) + creation_date = models.DateField( + blank=True, null=True, verbose_name=_("Creation date") + ) receipt_date_in_documentation = models.DateField( - blank=True, null=True, - verbose_name=_("Receipt date in documentation")) + blank=True, null=True, verbose_name=_("Receipt date in documentation") + ) item_number = models.IntegerField(_("Number of items"), default=1) description = models.TextField(_("Description"), blank=True, default="") container_id = models.PositiveIntegerField( - verbose_name=_("Container ID"), blank=True, null=True) + verbose_name=_("Container ID"), blank=True, null=True + ) # container = models.ForeignKey("archaeological_warehouse.Container") container_ref_id = models.PositiveIntegerField( - verbose_name=_("Container ID"), blank=True, null=True) + verbose_name=_("Container ID"), blank=True, null=True + ) # container_ref = models.ForeignKey("archaeological_warehouse.Container") comment = models.TextField(_("Comment"), blank=True, default="") - additional_information = models.TextField(_("Additional information"), - blank=True, default="") - duplicate = models.NullBooleanField(_("Has a duplicate"), blank=True, - null=True) - associated_links = models.TextField(_("Symbolic links"), blank=True, - default="") + additional_information = models.TextField( + _("Additional information"), blank=True, default="" + ) + duplicate = models.NullBooleanField( + _("Has a duplicate"), blank=True, null=True + ) + associated_links = models.TextField( + _("Symbolic links"), blank=True, default="" + ) cache_related_label = models.TextField( - _("Related"), blank=True, default="", db_index=True, - help_text=_("Cached value - do not edit")) + _("Related"), + blank=True, + default="", + db_index=True, + help_text=_("Cached value - do not edit"), + ) class Meta: verbose_name = _("Document") verbose_name_plural = _("Documents") - ordering = ('title',) + ordering = ("title",) permissions = ( - ("view_document", - ugettext("Can view all Documents")), - ("view_own_document", - ugettext("Can view own Document")), - ("add_own_document", - ugettext("Can add own Document")), - ("change_own_document", - ugettext("Can change own Document")), - ("delete_own_document", - ugettext("Can delete own Document")), + ("view_document", ugettext("Can view all Documents")), + ("view_own_document", ugettext("Can view own Document")), + ("add_own_document", ugettext("Can add own Document")), + ("change_own_document", ugettext("Can change own Document")), + ("delete_own_document", ugettext("Can delete own Document")), ) indexes = [ - GinIndex(fields=['data']), + GinIndex(fields=["data"]), ] def __str__(self): @@ -3329,18 +3980,24 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, def operation_codes(self): Operation = apps.get_model("archaeological_operations", "Operation") return "|".join( - sorted([Operation.objects.get(pk=ope_id).code_patriarche - for ope_id in self.get_related_operation_ids()])) + sorted( + [ + Operation.objects.get(pk=ope_id).code_patriarche + for ope_id in self.get_related_operation_ids() + ] + ) + ) def get_related_operation_ids(self): - operations = list( - self.operations.values_list("id", flat=True).all()) + operations = list(self.operations.values_list("id", flat=True).all()) operations += list( - self.context_records.values_list( - "operation_id", flat=True).all()) + self.context_records.values_list("operation_id", flat=True).all() + ) operations += list( self.finds.values_list( - "base_finds__context_record__operation_id", flat=True).all()) + "base_finds__context_record__operation_id", flat=True + ).all() + ) return list(set(operations)) def get_index_operation(self): @@ -3348,11 +4005,17 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, if len(operations) != 1: return current_operation = operations[0] - q = Document.objects.exclude(pk=self.pk).filter( - Q(operations__id=current_operation) | - Q(context_records__operation_id=current_operation) | - Q(finds__base_finds__context_record__operation_id=current_operation) - ).order_by("-custom_index") + q = ( + Document.objects.exclude(pk=self.pk) + .filter( + Q(operations__id=current_operation) + | Q(context_records__operation_id=current_operation) + | Q( + finds__base_finds__context_record__operation_id=current_operation + ) + ) + .order_by("-custom_index") + ) current_index = None for doc in q.all(): if not doc.custom_index: @@ -3385,7 +4048,8 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, if not self.has_iframe: return "" return Template(self.format_type.iframe_template).render( - Context({"document": self})) + Context({"document": self}) + ) @property def container(self): @@ -3409,8 +4073,9 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, @property def pdf_attached(self): - if not self.associated_file and (not self.source - or not self.source.associated_file): + if not self.associated_file and ( + not self.source or not self.source.associated_file + ): return extra = "" if self.associated_file: @@ -3432,6 +4097,7 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, return "{}-{:04d}".format(self.operation.code_patriarche or '', self.index) """ + def duplicate_item(self, user=None, data=None): return duplicate_item(self, user, data) @@ -3479,18 +4145,29 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, actions = super(Document, self).get_extra_actions(request) # is_locked = self.is_locked(request.user) - can_edit_document = self.can_do(request, 'change_document') + can_edit_document = self.can_do(request, "change_document") if not can_edit_document: return actions actions += [ - (reverse("document-qa-duplicate", args=[self.pk]), - _("Duplicate"), "fa fa-clone", "", "", True), + ( + reverse("document-qa-duplicate", args=[self.pk]), + _("Duplicate"), + "fa fa-clone", + "", + "", + True, + ), ] if get_current_profile().warehouse: actions.append( - (reverse("document-qa-packaging", args=[self.pk]), - _("Packaging"), - "fa fa-gift", "", "", True) + ( + reverse("document-qa-packaging", args=[self.pk]), + _("Packaging"), + "fa fa-gift", + "", + "", + True, + ) ) return actions @@ -3508,7 +4185,8 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, def get_values(self, prefix="", no_values=False, filtr=None, **kwargs): values = super(Document, self).get_values( - prefix=prefix, no_values=no_values, filtr=filtr, **kwargs) + prefix=prefix, no_values=no_values, filtr=filtr, **kwargs + ) if not filtr or prefix + "image_path" in filtr: values[prefix + "image_path"] = self.image_path if not filtr or prefix + "thumbnail_path" in filtr: @@ -3528,8 +4206,9 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, @property def images(self): # mimic a queryset pointing to himself - return Document.objects.filter( - pk=self.pk, image__isnull=False).exclude(image='') + return Document.objects.filter(pk=self.pk, image__isnull=False).exclude( + image="" + ) @property def main_image(self): @@ -3547,9 +4226,7 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, klass = getattr(cls, rel_model).rel.related_model q_own_dct = klass._get_query_owns_dicts(ishtaruser) if q_own_dct: - query_own_list.append( - (rel_model + "__", q_own_dct) - ) + query_own_list.append((rel_model + "__", q_own_dct)) q = None for prefix, owns in query_own_list: subq = cls._construct_query_own(prefix, owns) @@ -3558,9 +4235,9 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, q = subq else: q |= subq - q |= cls._construct_query_own('', [ - {'history_creator': ishtaruser.user_ptr} - ]) + q |= cls._construct_query_own( + "", [{"history_creator": ishtaruser.user_ptr}] + ) return q def get_associated_operation(self): @@ -3568,9 +4245,11 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, @property def associated_filename(self): - values = [str(getattr(self, attr)) - for attr in ('source_type', 'title') - if getattr(self, attr)] + values = [ + str(getattr(self, attr)) + for attr in ("source_type", "title") + if getattr(self, attr) + ] return slugify("-".join(values)) def _get_base_image_paths(self): @@ -3600,14 +4279,14 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, the new_path """ - file_split = path.split('.') + file_split = path.split(".") suffix, base = "", "" if len(file_split) > 1: base = ".".join(file_split[0:-1]) suffix = file_split[-1] else: base = path - base_split = base.split('-') + base_split = base.split("-") current_nb = 0 if len(base_split) > 1: try: @@ -3617,8 +4296,11 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, pass while os.path.exists(path): - if test_link and os.path.islink(path) \ - and os.readlink(path) == test_link: + if ( + test_link + and os.path.islink(path) + and os.readlink(path) == test_link + ): return path, True current_nb += 1 path = "{}-{}.{}".format(base, current_nb, suffix) @@ -3649,7 +4331,8 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, if not os.path.exists(os.path.dirname(new_path)): os.makedirs(os.path.dirname(new_path)) new_path, match = self._get_available_filename( - new_path, test_link=reference_path) + new_path, test_link=reference_path + ) links.append(new_path) if match: # the current link is correct continue @@ -3671,11 +4354,14 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, @classmethod def get_next_index(cls): - q = cls.objects.values('index').filter( - index__isnull=False).order_by("-index") + q = ( + cls.objects.values("index") + .filter(index__isnull=False) + .order_by("-index") + ) if not q.count(): return 1 - cid = q.all()[0]['index'] + cid = q.all()[0]["index"] if not cid: cid = 0 return cid + 1 @@ -3705,23 +4391,34 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, identifier = self.associated_url elif Site.objects.count(): identifier = "http://{}{}".format( - Site.objects.all()[0].domain, self.get_absolute_url()) + Site.objects.all()[0].domain, self.get_absolute_url() + ) return identifier def dublin_core_tags(self): if not self.title: return "" tags = [ - ("link", - {"rel": "schema.DC", "href": "http://purl.org/dc/elements/1.1/"}), - ("link", - {"rel": "schema.DCTERMS", "href": "http://purl.org/dc/terms/"}), + ( + "link", + { + "rel": "schema.DC", + "href": "http://purl.org/dc/elements/1.1/", + }, + ), + ( + "link", + {"rel": "schema.DCTERMS", "href": "http://purl.org/dc/terms/"}, + ), ] title = {"name": "DC.title", "content": self.title} tags.append(("meta", title)) if self.creation_date: - date = {"name": "DC.date", "scheme": "DCTERMS.W3CDTF", - "content": self.creation_date.strftime("%Y-%m-%d")} + date = { + "name": "DC.date", + "scheme": "DCTERMS.W3CDTF", + "content": self.creation_date.strftime("%Y-%m-%d"), + } tags.append(("meta", date)) if self.tags.count(): content = ", ".join(str(t) for t in self.tags.all()) @@ -3729,55 +4426,58 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, tags.append(("meta", tg)) if self.description: tags.append( - ("meta", {"name": "DC.description", - "content": self.description})) + ( + "meta", + {"name": "DC.description", "content": self.description}, + ) + ) if self.publisher: tags.append( - ("meta", {"name": "DC.publisher", - "content": self.publisher.name})) + ( + "meta", + {"name": "DC.publisher", "content": self.publisher.name}, + ) + ) if self.authors.count(): - content = ", ".join(str(t.person.raw_name) for t in - self.authors.all()) - tags.append( - ("meta", {"name": "DC.creator", - "content": content})) + content = ", ".join( + str(t.person.raw_name) for t in self.authors.all() + ) + tags.append(("meta", {"name": "DC.creator", "content": content})) if self.source_type: tags.append( - ("meta", {"name": "DC.type", - "content": str(self.source_type)})) + ("meta", {"name": "DC.type", "content": str(self.source_type)}) + ) if self.format_type: tags.append( - ("meta", {"name": "DC.format", - "content": str(self.format_type)})) + ( + "meta", + {"name": "DC.format", "content": str(self.format_type)}, + ) + ) identifier = self.dublin_core_identifier if identifier: tags.append( - ("meta", {"name": "DC.identifier", - "content": identifier})) + ("meta", {"name": "DC.identifier", "content": identifier}) + ) if self.language: lang = self.language.iso_code - tags.append( - ("meta", {"name": "DC.language", - "content": lang})) + tags.append(("meta", {"name": "DC.language", "content": lang})) if self.licenses.count(): licences = ", ".join(str(l) for l in self.licenses.all()) - tags.append( - ("meta", {"name": "DC.rights", - "content": licences})) + tags.append(("meta", {"name": "DC.rights", "content": licences})) src = None if self.source: src = self.source.dublin_core_identifier if src: - tags.append( - ("meta", {"name": "DC.relation", - "content": src})) - tags.append( - ("meta", {"name": "DC.source", - "content": src})) + tags.append(("meta", {"name": "DC.relation", "content": src})) + tags.append(("meta", {"name": "DC.source", "content": src})) elif self.source_free_input: tags.append( - ("meta", {"name": "DC.source", - "content": self.source_free_input})) + ( + "meta", + {"name": "DC.source", "content": self.source_free_input}, + ) + ) html = "" for tag, attrs in tags: et = ET.Element(tag, attrib=attrs) @@ -3796,13 +4496,19 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, ("ctx_ver", "Z39.88-2004"), ("rft_val_fmt", "info:ofi/fmt:kev:mtx:dc"), ("rft.title", self.title), - ("rft.btitle", self.title) + ("rft.btitle", self.title), ] if self.associated_url: info.append(("rft.identifier", self.associated_url)) elif Site.objects.count(): - info.append(("rft.identifier", "http://{}{}".format( - Site.objects.all()[0].domain, self.get_absolute_url()))) + info.append( + ( + "rft.identifier", + "http://{}{}".format( + Site.objects.all()[0].domain, self.get_absolute_url() + ), + ) + ) for author in self.authors.all(): person = author.person if not person.raw_name: @@ -3810,8 +4516,9 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, if person.first_name and person.name: info.append(("rft.aulast", person.name)) info.append(("rft.aufirst", person.first_name)) - info.append(("rft.au", "{}+{}".format(person.first_name, - person.name))) + info.append( + ("rft.au", "{}+{}".format(person.first_name, person.name)) + ) else: info.append(("rft.au", person.raw_name)) if self.source_type: @@ -3826,8 +4533,9 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, if self.creation_date.day == 1 and self.creation_date.month == 1: info.append(("rft.date", self.creation_date.year)) else: - info.append(("rft.date", - self.creation_date.strftime("%Y-%m-%d"))) + info.append( + ("rft.date", self.creation_date.strftime("%Y-%m-%d")) + ) if self.source and self.source.title: info.append(("rft.source", self.source.title)) elif self.source_free_input: @@ -3844,8 +4552,9 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, return '<span class="Z3988" title="{}">'.format(urlencode(info)) def save(self, *args, **kwargs): - no_path_change = 'no_path_change' in kwargs \ - and kwargs.pop('no_path_change') + no_path_change = "no_path_change" in kwargs and kwargs.pop( + "no_path_change" + ) self.set_index() if not self.associated_url: self.associated_url = None @@ -3861,8 +4570,11 @@ class Document(BaseHistorizedItem, CompleteIdentifierItem, OwnPerms, ImageModel, self.container_ref_id = container_ref.pk super(Document, self).save(*args, **kwargs) - if self.image and not no_path_change and \ - not getattr(self, '_no_path_change', False): + if ( + self.image + and not no_path_change + and not getattr(self, "_no_path_change", False) + ): links = self._move_image() if not links: return @@ -3883,18 +4595,25 @@ class OperationType(GeneralType): class Meta: verbose_name = _("Operation type") verbose_name_plural = _("Operation types") - ordering = ['judiciary', '-preventive', 'order', 'label'] + ordering = ["judiciary", "-preventive", "order", "label"] @classmethod - def get_types(cls, dct=None, instances=False, exclude=None, - empty_first=True, default=None, initial=None): + def get_types( + cls, + dct=None, + instances=False, + exclude=None, + empty_first=True, + default=None, + initial=None, + ): dct = dct or {} exclude = exclude or [] initial = initial or [] tuples = [] - dct['available'] = True + dct["available"] = True if not instances and empty_first and not default: - tuples.append(('', '--')) + tuples.append(("", "--")) if default and not instances: try: default = cls.objects.get(txt_idx=default) @@ -3908,16 +4627,20 @@ class OperationType(GeneralType): items = items.exclude(txt_idx__in=exclude) current_preventive, current_judiciary, current_lst = None, None, None item_list = list(items.order_by(*cls._meta.ordering).all()) - new_vals = cls._get_initial_types(initial, [i.pk for i in item_list], - instance=True) + new_vals = cls._get_initial_types( + initial, [i.pk for i in item_list], instance=True + ) item_list += new_vals for item in item_list: item.rank = 0 if instances: return item_list for item in item_list: - if not current_lst or item.preventive != current_preventive \ - or item.judiciary != current_judiciary: + if ( + not current_lst + or item.preventive != current_preventive + or item.judiciary != current_judiciary + ): if current_lst: tuples.append(current_lst) if item.judiciary: @@ -3935,7 +4658,7 @@ class OperationType(GeneralType): return tuples @classmethod - def is_preventive(cls, ope_type_id, key=''): + def is_preventive(cls, ope_type_id, key=""): try: op_type = cls.objects.get(pk=ope_type_id) except cls.DoesNotExist: @@ -3964,25 +4687,27 @@ class AdministrationScript(models.Model): class Meta: verbose_name = _("Administration script") verbose_name_plural = _("Administration scripts") - ordering = ['name'] + ordering = ["name"] def __str__(self): return str(self.name) -SCRIPT_STATE = (("S", _("Scheduled")), - ("P", _("In progress")), - ("FE", _("Finished with errors")), - ("F", _("Finished")), - ) +SCRIPT_STATE = ( + ("S", _("Scheduled")), + ("P", _("In progress")), + ("FE", _("Finished with errors")), + ("F", _("Finished")), +) SCRIPT_STATE_DCT = dict(SCRIPT_STATE) class AdministrationTask(models.Model): script = models.ForeignKey(AdministrationScript) - state = models.CharField(_("State"), max_length=2, choices=SCRIPT_STATE, - default='S') + state = models.CharField( + _("State"), max_length=2, choices=SCRIPT_STATE, default="S" + ) creation_date = models.DateTimeField(default=datetime.datetime.now) launch_date = models.DateTimeField(null=True, blank=True) finished_date = models.DateTimeField(null=True, blank=True) @@ -3991,17 +4716,16 @@ class AdministrationTask(models.Model): class Meta: verbose_name = _("Administration task") verbose_name_plural = _("Administration tasks") - ordering = ['script'] + ordering = ["script"] def __str__(self): state = _("Unknown") if self.state in SCRIPT_STATE_DCT: state = str(SCRIPT_STATE_DCT[self.state]) - return "{} - {} - {}".format(self.script, self.creation_date, - state) + return "{} - {} - {}".format(self.script, self.creation_date, state) def execute(self): - if self.state != 'S': + if self.state != "S": return self.launch_date = datetime.datetime.now() @@ -4009,29 +4733,35 @@ class AdministrationTask(models.Model): if not script_dir: self.result = str( - _("ISHTAR_SCRIPT_DIR is not set in your " - "local_settings. Contact your administrator.")) - self.state = 'FE' + _( + "ISHTAR_SCRIPT_DIR is not set in your " + "local_settings. Contact your administrator." + ) + ) + self.state = "FE" self.finished_date = datetime.datetime.now() self.save() return - if '..' in script_dir: + if ".." in script_dir: self.result = str( - _("Your ISHTAR_SCRIPT_DIR is containing " - "dots \"..\". As it can refer to relative " - "paths, it can be a security issue and this is " - "not allowed. Only put a full path.")) - self.state = 'FE' + _( + "Your ISHTAR_SCRIPT_DIR is containing " + 'dots "..". As it can refer to relative ' + "paths, it can be a security issue and this is " + "not allowed. Only put a full path." + ) + ) + self.state = "FE" self.finished_date = datetime.datetime.now() self.save() return if not os.path.isdir(script_dir): self.result = str( - _("Your ISHTAR_SCRIPT_DIR: \"{}\" is not a valid directory.") + _('Your ISHTAR_SCRIPT_DIR: "{}" is not a valid directory.') ).format(script_dir) - self.state = 'FE' + self.state = "FE" self.finished_date = datetime.datetime.now() self.save() return @@ -4045,14 +4775,16 @@ class AdministrationTask(models.Model): break if not script_name: self.result = str( - _("Script \"{}\" is not available in your script directory. " - "Check your configuration.") + _( + 'Script "{}" is not available in your script directory. ' + "Check your configuration." + ) ).format(self.script.path) - self.state = 'FE' + self.state = "FE" self.finished_date = datetime.datetime.now() self.save() return - self.state = 'P' + self.state = "P" self.save() self.finished_date = datetime.datetime.now() @@ -4060,19 +4792,20 @@ class AdministrationTask(models.Model): session = Popen([script_name], stdout=PIPE, stderr=PIPE) stdout, stderr = session.communicate() except OSError as e: - self.state = 'FE' - self.result = "Error executing \"{}\" script: {}".format( - self.script.path, e) + self.state = "FE" + self.result = 'Error executing "{}" script: {}'.format( + self.script.path, e + ) self.save() return self.finished_date = datetime.datetime.now() if stderr: - self.state = 'FE' - self.result = "Error: {}".format(stderr.decode('utf-8')) + self.state = "FE" + self.result = "Error: {}".format(stderr.decode("utf-8")) else: - self.state = 'F' - self.result = "{}".format(stdout.decode('utf-8')) + self.state = "F" + self.result = "{}".format(stdout.decode("utf-8")) self.save() @@ -4093,24 +4826,40 @@ class ExportTask(models.Model): _("Filter on"), max_length=2, choices=ITEM_TYPES, null=True, blank=True ) filter_text = models.TextField( - _("Filter query"), blank=True, default="", - help_text=_("Textual query on this item (try it on the main " - "interface)")) + _("Filter query"), + blank=True, + default="", + help_text=_( + "Textual query on this item (try it on the main " "interface)" + ), + ) geo = models.BooleanField( - _("Export geographic data"), default=True, - help_text=_("Geographic data can represent large volume of " - "information. Geographic data can be excluded from the " - "export")) - state = models.CharField(_("State"), max_length=2, choices=EXPORT_STATE, - default='C') - put_locks = models.BooleanField(_("Put locks on associated items"), - default=False) + _("Export geographic data"), + default=True, + help_text=_( + "Geographic data can represent large volume of " + "information. Geographic data can be excluded from the " + "export" + ), + ) + state = models.CharField( + _("State"), max_length=2, choices=EXPORT_STATE, default="C" + ) + put_locks = models.BooleanField( + _("Put locks on associated items"), default=False + ) lock_user = models.ForeignKey( - User, related_name='+', on_delete=models.SET_NULL, - verbose_name=_("Lock user"), blank=True, null=True, - help_text=_("Owner of the lock if item are locked. Warning: if no " - "user is provided the locks can be remove by any user " - "with the permission to edit.") + User, + related_name="+", + on_delete=models.SET_NULL, + verbose_name=_("Lock user"), + blank=True, + null=True, + help_text=_( + "Owner of the lock if item are locked. Warning: if no " + "user is provided the locks can be remove by any user " + "with the permission to edit." + ), ) export_types = models.BooleanField(_("Export types"), default=True) export_conf = models.BooleanField(_("Export configuration"), default=True) @@ -4122,15 +4871,17 @@ class ExportTask(models.Model): creation_date = models.DateTimeField(default=datetime.datetime.now) launch_date = models.DateTimeField(null=True, blank=True) finished_date = models.DateTimeField(null=True, blank=True) - result = models.FileField(_("Result"), null=True, blank=True, - upload_to="exports/%Y/%m/") - result_info = models.TextField(_("Result information"), blank=True, - default="") + result = models.FileField( + _("Result"), null=True, blank=True, upload_to="exports/%Y/%m/" + ) + result_info = models.TextField( + _("Result information"), blank=True, default="" + ) class Meta: verbose_name = _("Archive - Export") verbose_name_plural = _("Archive - Exports") - ordering = ['creation_date'] + ordering = ["creation_date"] def __str__(self): state = _("Unknown") @@ -4149,7 +4900,8 @@ class ExportTask(models.Model): def clean(self): if (self.filter_text and not self.filter_type) or ( - self.filter_type and not self.filter_text): + self.filter_type and not self.filter_text + ): raise ValidationError( _("To filter filter type and filter text must be filled.") ) @@ -4160,25 +4912,35 @@ class ImportTask(models.Model): launch_date = models.DateTimeField(null=True, blank=True) finished_date = models.DateTimeField(null=True, blank=True) import_user = models.ForeignKey( - User, related_name='+', on_delete=models.SET_NULL, - verbose_name=_("Import user"), blank=True, null=True, - help_text=_("If set the \"Import user\" will be the editor for last " - "version. If the field is left empty no history will be " - "recorded.") - ) - state = models.CharField(_("State"), max_length=2, choices=EXPORT_STATE, - default='C') + User, + related_name="+", + on_delete=models.SET_NULL, + verbose_name=_("Import user"), + blank=True, + null=True, + help_text=_( + 'If set the "Import user" will be the editor for last ' + "version. If the field is left empty no history will be " + "recorded." + ), + ) + state = models.CharField( + _("State"), max_length=2, choices=EXPORT_STATE, default="C" + ) delete_before = models.BooleanField( - _("Delete before adding"), default=False, - help_text=_("Delete existing items before adding")) + _("Delete before adding"), + default=False, + help_text=_("Delete existing items before adding"), + ) releasing_locks = models.BooleanField( - _("Releasing locks on associated items"), default=False) + _("Releasing locks on associated items"), default=False + ) source = models.FileField(_("Source"), upload_to="imports/%Y/%m/") class Meta: verbose_name = _("Archive - Import") verbose_name_plural = _("Archive - Imports") - ordering = ['creation_date'] + ordering = ["creation_date"] def __str__(self): state = _("Unknown") diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py index e0685c064..7a0397c36 100644 --- a/ishtar_common/models_common.py +++ b/ishtar_common/models_common.py @@ -37,20 +37,30 @@ from django.db.models.signals import post_save, post_delete, m2m_changed from django.template.defaultfilters import slugify from django.utils.safestring import SafeText, mark_safe from django.utils.translation import activate, deactivate -from ishtar_common.utils import ugettext_lazy as _, \ - pgettext_lazy, get_image_path +from ishtar_common.utils import ugettext_lazy as _, pgettext_lazy, get_image_path from simple_history.models import HistoricalRecords as BaseHistoricalRecords -from simple_history.signals import post_create_historical_record, \ - pre_create_historical_record +from simple_history.signals import ( + post_create_historical_record, + pre_create_historical_record, +) from unidecode import unidecode from ishtar_common.model_managers import TypeManager from ishtar_common.model_merging import merge_model_objects from ishtar_common.models_imports import Import from ishtar_common.templatetags.link_to_window import simple_link_to_window -from ishtar_common.utils import get_cache, disable_for_loaddata, \ - get_all_field_names, merge_tsvectors, cached_label_changed, post_save_geo, \ - task, duplicate_item, get_generated_id, get_current_profile +from ishtar_common.utils import ( + get_cache, + disable_for_loaddata, + get_all_field_names, + merge_tsvectors, + cached_label_changed, + post_save_geo, + task, + duplicate_item, + get_generated_id, + get_current_profile, +) logger = logging.getLogger(__name__) @@ -73,7 +83,7 @@ class CachedGen(object): @classmethod def _add_cache_key_to_refresh(cls, keys): - cache_ckey, current_keys = get_cache(cls, ['_current_keys']) + cache_ckey, current_keys = get_cache(cls, ["_current_keys"]) if type(current_keys) != list: current_keys = [] if keys not in current_keys: @@ -82,17 +92,17 @@ class CachedGen(object): class Cached(CachedGen): - slug_field = 'txt_idx' + slug_field = "txt_idx" @classmethod def refresh_cache(cls): - cache_ckey, current_keys = get_cache(cls, ['_current_keys']) + cache_ckey, current_keys = get_cache(cls, ["_current_keys"]) if not current_keys: return for keys in current_keys: - if len(keys) == 2 and keys[0] == '__slug': + if len(keys) == 2 and keys[0] == "__slug": cls.get_cache(keys[1], force=True) - elif keys[0] == '__get_types': + elif keys[0] == "__get_types": default = None empty_first = True exclude = [] @@ -102,14 +112,17 @@ class Cached(CachedGen): empty_first = bool(keys.pop()) exclude = keys[1:] cls.get_types( - exclude=exclude, empty_first=empty_first, default=default, - force=True) - elif keys[0] == '__get_help': + exclude=exclude, + empty_first=empty_first, + default=default, + force=True, + ) + elif keys[0] == "__get_help": cls.get_help(force=True) @classmethod def _add_cache_key_to_refresh(cls, keys): - cache_ckey, current_keys = get_cache(cls, ['_current_keys']) + cache_ckey, current_keys = get_cache(cls, ["_current_keys"]) if type(current_keys) != list: current_keys = [] if keys not in current_keys: @@ -118,7 +131,7 @@ class Cached(CachedGen): @classmethod def get_cache(cls, slug, force=False): - cache_key, value = get_cache(cls, ['__slug', slug]) + cache_key, value = get_cache(cls, ["__slug", slug]) if not force and value: return value try: @@ -140,14 +153,18 @@ class GeneralType(Cached, models.Model): """ Abstract class for "types" """ + label = models.TextField(_("Label")) txt_idx = models.TextField( - _("Textual ID"), validators=[validate_slug], + _("Textual ID"), + validators=[validate_slug], unique=True, help_text=_( "The slug is the standardized version of the name. It contains " "only lowercase letters, numbers and hyphens. Each slug must " - "be unique.")) + "be unique." + ), + ) comment = models.TextField(_("Comment"), blank=True, default="") available = models.BooleanField(_("Available"), default=True) HELP_TEXT = "" @@ -170,16 +187,20 @@ class GeneralType(Cached, models.Model): """ Used for automatic documentation generation """ - s = "**label** {}, **txt_idx** {}".format(str(_("Label")), - str(_("Textual ID"))) + s = "**label** {}, **txt_idx** {}".format(str(_("Label")), str(_("Textual ID"))) if hasattr(cls, "extra_documentation_string"): s += cls.extra_documentation_string() return s @classmethod def admin_url(cls): - return str(reverse('admin:{}_{}_changelist'.format( - cls._meta.app_label, cls._meta.model_name))) + return str( + reverse( + "admin:{}_{}_changelist".format( + cls._meta.app_label, cls._meta.model_name + ) + ) + ) @classmethod def history_decompress(cls, value, create=False): @@ -199,7 +220,7 @@ class GeneralType(Cached, models.Model): @classmethod def create_default_for_test(cls): - return [cls.objects.create(label='Test %d' % i) for i in range(5)] + return [cls.objects.create(label="Test %d" % i) for i in range(5)] @property def short_label(self): @@ -210,7 +231,7 @@ class GeneralType(Cached, models.Model): return self.label @classmethod - def get_or_create(cls, slug, label=''): + def get_or_create(cls, slug, label=""): """ Get or create a new item. @@ -225,7 +246,8 @@ class GeneralType(Cached, models.Model): if item: return item item, created = cls.objects.get_or_create( - txt_idx=slug, defaults={'label': label}) + txt_idx=slug, defaults={"label": label} + ) return item @classmethod @@ -260,9 +282,9 @@ class GeneralType(Cached, models.Model): dct = {} if not exclude: exclude = [] - keys = ['__get_help'] + keys = ["__get_help"] keys += ["{}".format(ex) for ex in exclude] - keys += ['{}-{}'.format(str(k), dct[k]) for k in dct] + keys += ["{}-{}".format(str(k), dct[k]) for k in dct] cache_key, value = get_cache(cls, keys) if value and not force: return mark_safe(value) @@ -270,11 +292,11 @@ class GeneralType(Cached, models.Model): c_rank = -1 help_items = "\n" for item in cls.get_types(dct=dct, instances=True, exclude=exclude): - if hasattr(item, '__iter__'): + if hasattr(item, "__iter__"): pk = item[0] item = cls.objects.get(pk=pk) item.rank = c_rank + 1 - if hasattr(item, 'parent'): + if hasattr(item, "parent"): c_item = item parents = [] while c_item.parent: @@ -291,11 +313,13 @@ class GeneralType(Cached, models.Model): help_items += "<dl>\n" c_rank = item.rank help_items += "<dt>%s</dt><dd>%s</dd>" % ( - item.label, "<br/>".join(item.comment.split('\n'))) + item.label, + "<br/>".join(item.comment.split("\n")), + ) c_rank += 1 if c_rank: help_items += c_rank * "</dl>" - if help_text or help_items != '\n': + if help_text or help_items != "\n": help_text = help_text + help_items else: help_text = "" @@ -327,19 +351,27 @@ class GeneralType(Cached, models.Model): return new_vals @classmethod - def get_types(cls, dct=None, instances=False, exclude=None, - empty_first=True, default=None, initial=None, force=False, - full_hierarchy=False): + def get_types( + cls, + dct=None, + instances=False, + exclude=None, + empty_first=True, + default=None, + initial=None, + force=False, + full_hierarchy=False, + ): if not dct: dct = {} if not exclude: exclude = [] types = [] if not instances and empty_first and not default: - types = [('', '--')] - types += cls._pre_get_types(dct, instances, exclude, - default, force, - get_full_hierarchy=full_hierarchy) + types = [("", "--")] + types += cls._pre_get_types( + dct, instances, exclude, default, force, get_full_hierarchy=full_hierarchy + ) if not initial: return types new_vals = cls._get_initial_types(initial, [idx for idx, lbl in types]) @@ -347,8 +379,15 @@ class GeneralType(Cached, models.Model): return types @classmethod - def _pre_get_types(cls, dct=None, instances=False, exclude=None, - default=None, force=False, get_full_hierarchy=False): + def _pre_get_types( + cls, + dct=None, + instances=False, + exclude=None, + default=None, + force=False, + get_full_hierarchy=False, + ): if not dct: dct = {} if not exclude: @@ -356,31 +395,42 @@ class GeneralType(Cached, models.Model): # cache cache_key = None if not instances: - keys = ['__get_types'] - keys += ["{}".format(ex) for ex in exclude] + \ - ["{}".format(default)] - keys += ['{}-{}'.format(str(k), dct[k]) for k in dct] + keys = ["__get_types"] + keys += ["{}".format(ex) for ex in exclude] + ["{}".format(default)] + keys += ["{}-{}".format(str(k), dct[k]) for k in dct] cache_key, value = get_cache(cls, keys) if value and not force: return value base_dct = dct.copy() - if hasattr(cls, 'parent'): + if hasattr(cls, "parent"): if not cache_key: return cls._get_parent_types( - base_dct, instances, exclude=exclude, - default=default, get_full_hierarchy=get_full_hierarchy) - vals = [v for v in cls._get_parent_types( - base_dct, instances, exclude=exclude, - default=default, get_full_hierarchy=get_full_hierarchy)] + base_dct, + instances, + exclude=exclude, + default=default, + get_full_hierarchy=get_full_hierarchy, + ) + vals = [ + v + for v in cls._get_parent_types( + base_dct, + instances, + exclude=exclude, + default=default, + get_full_hierarchy=get_full_hierarchy, + ) + ] cache.set(cache_key, vals, settings.CACHE_TIMEOUT) return vals if not cache_key: - return cls._get_types(base_dct, instances, exclude=exclude, - default=default) + return cls._get_types(base_dct, instances, exclude=exclude, default=default) vals = [ - v for v in cls._get_types(base_dct, instances, exclude=exclude, - default=default) + v + for v in cls._get_types( + base_dct, instances, exclude=exclude, default=default + ) ] cache.set(cache_key, vals, settings.CACHE_TIMEOUT) return vals @@ -391,7 +441,7 @@ class GeneralType(Cached, models.Model): dct = {} if not exclude: exclude = [] - dct['available'] = True + dct["available"] = True if default: try: default = cls.objects.get(txt_idx=default) @@ -400,7 +450,7 @@ class GeneralType(Cached, models.Model): pass items = cls.objects.filter(**dct) if default and default != "None": - if hasattr(default, 'txt_idx'): + if hasattr(default, "txt_idx"): exclude.append(default.txt_idx) else: exclude.append(default) @@ -411,7 +461,7 @@ class GeneralType(Cached, models.Model): item.rank = 0 yield item else: - yield (item.pk, _(str(item)) if item and str(item) else '') + yield (item.pk, _(str(item)) if item and str(item) else "") @classmethod def _get_childs_list(cls, dct=None, exclude=None, instances=False): @@ -419,13 +469,13 @@ class GeneralType(Cached, models.Model): dct = {} if not exclude: exclude = [] - if 'parent' in dct: - dct.pop('parent') + if "parent" in dct: + dct.pop("parent") childs = cls.objects.filter(**dct) if exclude: childs = childs.exclude(txt_idx__in=exclude) - if hasattr(cls, 'order'): - childs = childs.order_by('order') + if hasattr(cls, "order"): + childs = childs.order_by("order") res = {} if instances: for item in childs.all(): @@ -450,8 +500,16 @@ class GeneralType(Cached, models.Model): PREFIX_CODES = ["\u2502", "\u251C", "\u2514"] @classmethod - def _get_childs(cls, item, child_list, prefix=0, instances=False, - is_last=False, last_of=None, get_full_hierarchy=False): + def _get_childs( + cls, + item, + child_list, + prefix=0, + instances=False, + is_last=False, + last_of=None, + get_full_hierarchy=False, + ): if not last_of: last_of = [] @@ -465,7 +523,7 @@ class GeneralType(Cached, models.Model): full_hierarchy_initial = get_full_hierarchy for idx, child in enumerate(current_child_lst): mylast_of = last_of[:] - p = '' + p = "" if instances: child.rank = prefix lst.append(child) @@ -495,9 +553,7 @@ class GeneralType(Cached, models.Model): p += cls.PREFIX_EMPTY else: p += cls.PREFIX - lst.append(( - child[0], SafeText(p + str(_(child[1]))) - )) + lst.append((child[0], SafeText(p + str(_(child[1]))))) clast_of = last_of[:] clast_of.append(idx + 1 == total) if instances: @@ -512,20 +568,31 @@ class GeneralType(Cached, models.Model): else: get_full_hierarchy = child[1] for sub_child in cls._get_childs( - child_id, child_list, prefix, instances, - is_last=((idx + 1) == total), last_of=clast_of, - get_full_hierarchy=get_full_hierarchy): + child_id, + child_list, + prefix, + instances, + is_last=((idx + 1) == total), + last_of=clast_of, + get_full_hierarchy=get_full_hierarchy, + ): lst.append(sub_child) return lst @classmethod - def _get_parent_types(cls, dct=None, instances=False, exclude=None, - default=None, get_full_hierarchy=False): + def _get_parent_types( + cls, + dct=None, + instances=False, + exclude=None, + default=None, + get_full_hierarchy=False, + ): if not dct: dct = {} if not exclude: exclude = [] - dct['available'] = True + dct["available"] = True child_list = cls._get_childs_list(dct, exclude, instances) if 0 in child_list: @@ -540,8 +607,11 @@ class GeneralType(Cached, models.Model): if get_full_hierarchy: get_full_hierarchy = item[1] for child in cls._get_childs( - item_id, child_list, instances=instances, - get_full_hierarchy=get_full_hierarchy): + item_id, + child_list, + instances=instances, + get_full_hierarchy=get_full_hierarchy, + ): yield child def save(self, *args, **kwargs): @@ -551,8 +621,7 @@ class GeneralType(Cached, models.Model): if isinstance(txt_idx, list): txt_idx = txt_idx[0] self.txt_idx = txt_idx - self.label = " ".join(" ".join(self.txt_idx.split('-')) - .split('_')).title() + self.label = " ".join(" ".join(self.txt_idx.split("-")).split("_")).title() if not self.txt_idx: self.txt_idx = slugify(self.label)[:100] @@ -562,34 +631,36 @@ class GeneralType(Cached, models.Model): content_type = ContentType.objects.get_for_model(self.__class__) if slugify(self.label) != slugify(old.label): ItemKey.objects.filter( - object_id=self.pk, key=slugify(old.label), - content_type=content_type).delete() + object_id=self.pk, key=slugify(old.label), content_type=content_type + ).delete() if self.txt_idx != old.txt_idx: ItemKey.objects.filter( - object_id=self.pk, key=old.txt_idx, - content_type=content_type).delete() + object_id=self.pk, key=old.txt_idx, content_type=content_type + ).delete() obj = super(GeneralType, self).save(*args, **kwargs) self.generate_key(force=True) return obj - def add_key(self, key, force=False, importer=None, group=None, - user=None): + def add_key(self, key, force=False, importer=None, group=None, user=None): ItemKey = apps.get_model("ishtar_common", "ItemKey") content_type = ContentType.objects.get_for_model(self.__class__) - if not importer and not force and ItemKey.objects.filter( - key=key, content_type=content_type).count(): + if ( + not importer + and not force + and ItemKey.objects.filter(key=key, content_type=content_type).count() + ): return - filtr = {'key': key, 'content_type': content_type} + filtr = {"key": key, "content_type": content_type} if group: - filtr['group'] = group + filtr["group"] = group elif user: - filtr['user'] = user + filtr["user"] = user else: - filtr['importer'] = importer + filtr["importer"] = importer if force: ItemKey.objects.filter(**filtr).exclude(object_id=self.pk).delete() - filtr['object_id'] = self.pk + filtr["object_id"] = self.pk ItemKey.objects.get_or_create(**filtr) def generate_key(self, force=False): @@ -601,16 +672,14 @@ class GeneralType(Cached, models.Model): keys = [self.txt_idx] content_type = ContentType.objects.get_for_model(self.__class__) base_q = Q(content_type=content_type, object_id=self.pk) - subquery = Q(importer__isnull=True, user__isnull=True, - group__isnull=True) - subquery |= Q(user__isnull=True, group__isnull=True, - importer=importer) + subquery = Q(importer__isnull=True, user__isnull=True, group__isnull=True) + subquery |= Q(user__isnull=True, group__isnull=True, importer=importer) if importer.user: - subquery |= Q(user=importer.user, group__isnull=True, - importer=importer) + subquery |= Q(user=importer.user, group__isnull=True, importer=importer) if importer.associated_group: - subquery |= Q(user__isnull=True, group=importer.associated_group, - importer=importer) + subquery |= Q( + user__isnull=True, group=importer.associated_group, importer=importer + ) q = ItemKey.objects.filter(base_q & subquery) for ik in q.exclude(key=self.txt_idx).all(): keys.append(ik.key) @@ -624,9 +693,13 @@ class GeneralType(Cached, models.Model): class HierarchicalType(GeneralType): - parent = models.ForeignKey('self', blank=True, null=True, - on_delete=models.SET_NULL, - verbose_name=_("Parent")) + parent = models.ForeignKey( + "self", + blank=True, + null=True, + on_delete=models.SET_NULL, + verbose_name=_("Parent"), + ) class Meta: abstract = True @@ -665,16 +738,13 @@ class StatisticItem: class TemplateItem: @classmethod def _label_templates_q(cls): - model_name = "{}.{}".format( - cls.__module__, cls.__name__) - q = Q(associated_model__klass=model_name, - for_labels=True, available=True) - alt_model_name = model_name.replace( - "models_finds", "models").replace( - "models_treatments", "models") + model_name = "{}.{}".format(cls.__module__, cls.__name__) + q = Q(associated_model__klass=model_name, for_labels=True, available=True) + alt_model_name = model_name.replace("models_finds", "models").replace( + "models_treatments", "models" + ) if alt_model_name != model_name: - q |= Q(associated_model__klass=model_name, - for_labels=True, available=True) + q |= Q(associated_model__klass=model_name, for_labels=True, available=True) DocumentTemplate = apps.get_model("ishtar_common", "DocumentTemplate") return DocumentTemplate.objects.filter(q) @@ -695,33 +765,35 @@ class TemplateItem: if "models_finds" in name or "models_treatments" in name: names = [ name, - name.replace("models_finds", "models" - ).replace("models_treatments", "models") + name.replace("models_finds", "models").replace( + "models_treatments", "models" + ), ] else: - names = [name, name.replace("models", "models_finds"), - name.replace("models", "models_treatments")] + names = [ + name, + name.replace("models", "models_finds"), + name.replace("models", "models_treatments"), + ] else: names = [name] - model_names = [ - "{}.{}".format(module, name) for name in names - ] + model_names = ["{}.{}".format(module, name) for name in names] DocumentTemplate = apps.get_model("ishtar_common", "DocumentTemplate") q = DocumentTemplate.objects.filter( - associated_model__klass__in=model_names, - for_labels=False, available=True) + associated_model__klass__in=model_names, for_labels=False, available=True + ) for template in q.all(): urlname = "generate-document" templates.append( - (template.name, reverse( - urlname, args=[template.slug, self.pk])) + (template.name, reverse(urlname, args=[template.slug, self.pk])) ) return templates class FullSearch(models.Model): - search_vector = SearchVectorField(_("Search vector"), blank=True, null=True, - help_text=_("Auto filled at save")) + search_vector = SearchVectorField( + _("Search vector"), blank=True, null=True, help_text=_("Auto filled at save") + ) EXTRA_REQUEST_KEYS = {} DYNAMIC_REQUESTS = {} @@ -742,7 +814,7 @@ class FullSearch(models.Model): def general_types(cls): for k in get_all_field_names(cls): field = cls._meta.get_field(k) - if not hasattr(field, 'rel') or not field.rel: + if not hasattr(field, "rel") or not field.rel: continue rel_model = field.rel.to if issubclass(rel_model, (GeneralType, HierarchicalType)): @@ -768,8 +840,9 @@ class FullSearch(models.Model): def _update_search_field(self, search_vector_conf, search_vectors, data): for value in search_vector_conf.format(data): with connection.cursor() as cursor: - cursor.execute("SELECT to_tsvector(%s, %s)", [ - search_vector_conf.language, value]) + cursor.execute( + "SELECT to_tsvector(%s, %s)", [search_vector_conf.language, value] + ) row = cursor.fetchone() search_vectors.append(row[0]) @@ -782,20 +855,22 @@ class FullSearch(models.Model): :param save: True if you want to save the object immediately :return: True if modified """ - if not hasattr(self, 'search_vector'): + if not hasattr(self, "search_vector"): return if not self.pk: # logger.warning("Cannot update search vector before save or " # "after deletion.") return - if not self.BASE_SEARCH_VECTORS and not self.M2M_SEARCH_VECTORS \ - and not self.INT_SEARCH_VECTORS \ - and not self.PROPERTY_SEARCH_VECTORS \ - and not self.PARENT_SEARCH_VECTORS: - logger.warning("No search_vectors defined for {}".format( - self.__class__)) + if ( + not self.BASE_SEARCH_VECTORS + and not self.M2M_SEARCH_VECTORS + and not self.INT_SEARCH_VECTORS + and not self.PROPERTY_SEARCH_VECTORS + and not self.PARENT_SEARCH_VECTORS + ): + logger.warning("No search_vectors defined for {}".format(self.__class__)) return - if getattr(self, '_search_updated', None): + if getattr(self, "_search_updated", None): return JsonDataField = apps.get_model("ishtar_common", "JsonDataField") self._search_updated = True @@ -808,30 +883,29 @@ class FullSearch(models.Model): # many to many have to be queried one by one otherwise only one is fetch for m2m_search_vector in self.M2M_SEARCH_VECTORS: - key = m2m_search_vector.key.split('__')[0] + key = m2m_search_vector.key.split("__")[0] rel_key = getattr(self, key) - for item in rel_key.values('pk').all(): - query_dct = {key + "__pk": item['pk']} + for item in rel_key.values("pk").all(): + query_dct = {key + "__pk": item["pk"]} q = copy.copy(base_q).filter(**query_dct) q = q.annotate( search=SearchVector( - m2m_search_vector.key, - config=m2m_search_vector.language) - ).values('search') - search_vectors.append(q.all()[0]['search']) + m2m_search_vector.key, config=m2m_search_vector.language + ) + ).values("search") + search_vectors.append(q.all()[0]["search"]) # int/float are not well managed by the SearchVector for int_search_vector in self.INT_SEARCH_VECTORS: q = base_q.values(int_search_vector.key) - for val in int_search_vector.format( - q.all()[0][int_search_vector.key]): + for val in int_search_vector.format(q.all()[0][int_search_vector.key]): self._update_search_number_field(search_vectors, val) if not exclude_parent: # copy parent vector fields for PARENT_SEARCH_VECTOR in self.PARENT_SEARCH_VECTORS: parent = getattr(self, PARENT_SEARCH_VECTOR) - if hasattr(parent, 'all'): # m2m + if hasattr(parent, "all"): # m2m for p in parent.all(): search_vectors.append(p.search_vector) elif parent: @@ -839,7 +913,7 @@ class FullSearch(models.Model): for PARENT_ONLY_SEARCH_VECTOR in self.PARENT_ONLY_SEARCH_VECTORS: parent = getattr(self, PARENT_ONLY_SEARCH_VECTOR) - if hasattr(parent, 'all'): # m2m + if hasattr(parent, "all"): # m2m for p in parent.all(): search_vectors.append( p.update_search_vector(save=False, exclude_parent=True) @@ -856,8 +930,7 @@ class FullSearch(models.Model): for base_search_vector in self.BASE_SEARCH_VECTORS: data = res[base_search_vector.key] data = unidecode(str(data)) - self._update_search_field(base_search_vector, - search_vectors, data) + self._update_search_field(base_search_vector, search_vectors, data) if self.PROPERTY_SEARCH_VECTORS: for property_search_vector in self.PROPERTY_SEARCH_VECTORS: @@ -867,17 +940,16 @@ class FullSearch(models.Model): if not data: continue data = str(data) - self._update_search_field(property_search_vector, - search_vectors, data) + self._update_search_field(property_search_vector, search_vectors, data) - if hasattr(self, 'data') and self.data: + if hasattr(self, "data") and self.data: content_type = ContentType.objects.get_for_model(self) for json_field in JsonDataField.objects.filter( - content_type=content_type, - search_index=True).all(): + content_type=content_type, search_index=True + ).all(): data = copy.deepcopy(self.data) no_data = False - for key in json_field.key.split('__'): + for key in json_field.key.split("__"): if key not in data: no_data = True break @@ -885,22 +957,21 @@ class FullSearch(models.Model): if no_data or not data: continue - if json_field.value_type == 'B': + if json_field.value_type == "B": if data is True: data = json_field.name else: continue - elif json_field.value_type in ('I', 'F'): + elif json_field.value_type in ("I", "F"): self._update_search_number_field(search_vectors, data) continue - elif json_field.value_type == 'D': + elif json_field.value_type == "D": # only index year self._update_search_number_field(search_vectors, data.year) continue for lang in ("simple", settings.ISHTAR_SEARCH_LANGUAGE): with connection.cursor() as cursor: - cursor.execute("SELECT to_tsvector(%s, %s)", - [lang, data]) + cursor.execute("SELECT to_tsvector(%s, %s)", [lang, data]) row = cursor.fetchone() search_vectors.append(row[0]) new_search_vector = merge_tsvectors(search_vectors) @@ -908,7 +979,8 @@ class FullSearch(models.Model): self.search_vector = new_search_vector if save and changed: self.__class__.objects.filter(pk=self.pk).update( - search_vector=new_search_vector) + search_vector=new_search_vector + ) elif not save: return new_search_vector return changed @@ -916,8 +988,8 @@ class FullSearch(models.Model): class Imported(models.Model): imports = models.ManyToManyField( - Import, blank=True, - related_name="imported_%(app_label)s_%(class)s") + Import, blank=True, related_name="imported_%(app_label)s_%(class)s" + ) class Meta: abstract = True @@ -941,18 +1013,24 @@ class JsonData(models.Model, CachedGen): except ContentType.DoesNotExists: return sections JsonDataField = apps.get_model("ishtar_common", "JsonDataField") - fields = list(JsonDataField.objects.filter( - content_type=content_type, display=True, section__isnull=True - ).all()) # no section fields - - fields += list(JsonDataField.objects.filter( - content_type=content_type, display=True, section__isnull=False - ).order_by('section__order', 'order').all()) + fields = list( + JsonDataField.objects.filter( + content_type=content_type, display=True, section__isnull=True + ).all() + ) # no section fields + + fields += list( + JsonDataField.objects.filter( + content_type=content_type, display=True, section__isnull=False + ) + .order_by("section__order", "order") + .all() + ) for field in fields: value = None data = self.data.copy() - for key in field.key.split('__'): + for key in field.key.split("__"): if key in data: value = copy.copy(data[key]) data = data[key] @@ -972,14 +1050,14 @@ class JsonData(models.Model, CachedGen): @classmethod def refresh_cache(cls): - __, refreshed = get_cache(cls, ['cache_refreshed']) + __, refreshed = get_cache(cls, ["cache_refreshed"]) if refreshed and time.time() - refreshed < 1: return - cache_ckey, current_keys = get_cache(cls, ['_current_keys']) + cache_ckey, current_keys = get_cache(cls, ["_current_keys"]) if not current_keys: return for keys in current_keys: - if keys[0] == '__get_dynamic_choices': + if keys[0] == "__get_dynamic_choices": cls._get_dynamic_choices(keys[1], force=True) @classmethod @@ -990,18 +1068,19 @@ class JsonData(models.Model, CachedGen): :param force: if set to True do not use cache :return: tuple of choices (id, value) """ - cache_key, value = get_cache(cls, ['__get_dynamic_choices', key]) + cache_key, value = get_cache(cls, ["__get_dynamic_choices", key]) if not force and value: return value choices = set() - splitted_key = key[len('data__'):].split('__') - q = cls.objects.filter( - data__has_key=key[len('data__'):]).values_list('data', flat=True) + splitted_key = key[len("data__") :].split("__") + q = cls.objects.filter(data__has_key=key[len("data__") :]).values_list( + "data", flat=True + ) for value in q.all(): for k in splitted_key: value = value[k] choices.add(value) - choices = [('', '')] + [(v, v) for v in sorted(list(choices))] + choices = [("", "")] + [(v, v) for v in sorted(list(choices))] cache.set(cache_key, choices, settings.CACHE_SMALLTIMEOUT) return choices @@ -1022,8 +1101,9 @@ class FixAssociated: expected_values = [expected_values] if hasattr(ctype, "txt_idx"): try: - expected_values = [ctype.objects.get(txt_idx=v) - for v in expected_values] + expected_values = [ + ctype.objects.get(txt_idx=v) for v in expected_values + ] except ctype.DoesNotExist: # type not yet initialized return @@ -1066,8 +1146,9 @@ class CascasdeUpdate: class SearchAltName(object): - def __init__(self, search_key, search_query, extra_query=None, - distinct_query=False): + def __init__( + self, search_key, search_query, extra_query=None, distinct_query=False + ): self.search_key = search_key self.search_query = search_query self.extra_query = extra_query or {} @@ -1083,8 +1164,17 @@ class HistoryError(Exception): class HistoricalRecords(BaseHistoricalRecords): - def _save_historic(self, manager, instance, history_date, history_type, - history_user, history_change_reason, using, attrs): + def _save_historic( + self, + manager, + instance, + history_date, + history_type, + history_user, + history_change_reason, + using, + attrs, + ): history_instance = manager.model( history_date=history_date, history_type=history_type, @@ -1117,98 +1207,132 @@ class HistoricalRecords(BaseHistoricalRecords): def create_historical_record(self, instance, history_type, using=None): try: - history_modifier = getattr(instance, 'history_modifier', None) + history_modifier = getattr(instance, "history_modifier", None) assert history_modifier except (User.DoesNotExist, AssertionError): # on batch removing of users, user could have disappeared return - history_date = getattr(instance, "_history_date", - datetime.datetime.now()) + history_date = getattr(instance, "_history_date", datetime.datetime.now()) history_change_reason = getattr(instance, "changeReason", None) force = getattr(instance, "_force_history", False) manager = getattr(instance, self.manager_name) attrs = {} for field in instance._meta.fields: attrs[field.attname] = getattr(instance, field.attname) - q_history = instance.history \ - .filter(history_modifier_id=history_modifier.pk) \ - .order_by('-history_date', '-history_id') + q_history = instance.history.filter( + history_modifier_id=history_modifier.pk + ).order_by("-history_date", "-history_id") # instance.skip_history_when_saving = True if not q_history.count(): if force: - delattr(instance, '_force_history') + delattr(instance, "_force_history") self._save_historic( - manager, instance, history_date, history_type, history_modifier, - history_change_reason, using, attrs) + manager, + instance, + history_date, + history_type, + history_modifier, + history_change_reason, + using, + attrs, + ) return old_instance = q_history.all()[0] # multiple saving by the same user in a very short time are generaly # caused by post_save signals it is not relevant to keep them - min_history_date = datetime.datetime.now() \ - - datetime.timedelta(seconds=5) - q = q_history.filter(history_date__isnull=False, - history_date__gt=min_history_date) \ - .order_by('-history_date', '-history_id') + min_history_date = datetime.datetime.now() - datetime.timedelta(seconds=5) + q = q_history.filter( + history_date__isnull=False, history_date__gt=min_history_date + ).order_by("-history_date", "-history_id") if not force and q.count(): return if force: - delattr(instance, '_force_history') + delattr(instance, "_force_history") # record a new version only if data have been changed for field in instance._meta.fields: if getattr(old_instance, field.attname) != attrs[field.attname]: - self._save_historic(manager, instance, history_date, - history_type, history_modifier, - history_change_reason, using, attrs) + self._save_historic( + manager, + instance, + history_date, + history_type, + history_modifier, + history_change_reason, + using, + attrs, + ) return -class BaseHistorizedItem(StatisticItem, TemplateItem, FullSearch, Imported, - JsonData, FixAssociated, CascasdeUpdate): +class BaseHistorizedItem( + StatisticItem, + TemplateItem, + FullSearch, + Imported, + JsonData, + FixAssociated, + CascasdeUpdate, +): """ Historized item with external ID management. All historized items are searchable and have a data json field. Historized items can be "locked" for edition. """ + IS_BASKET = False SHOW_URL = None - EXTERNAL_ID_KEY = '' + EXTERNAL_ID_KEY = "" EXTERNAL_ID_DEPENDENCIES = [] HISTORICAL_M2M = [] history_modifier = models.ForeignKey( - User, related_name='+', on_delete=models.SET_NULL, - verbose_name=_("Last editor"), blank=True, null=True) + User, + related_name="+", + on_delete=models.SET_NULL, + verbose_name=_("Last editor"), + blank=True, + null=True, + ) history_creator = models.ForeignKey( - User, related_name='+', on_delete=models.SET_NULL, - verbose_name=_("Creator"), blank=True, null=True) + User, + related_name="+", + on_delete=models.SET_NULL, + verbose_name=_("Creator"), + blank=True, + null=True, + ) last_modified = models.DateTimeField(auto_now=True) history_m2m = JSONField(default={}, blank=True) - need_update = models.BooleanField( - verbose_name=_("Need update"), default=False) + need_update = models.BooleanField(verbose_name=_("Need update"), default=False) locked = models.BooleanField( - verbose_name=_("Item locked for edition"), default=False) + verbose_name=_("Item locked for edition"), default=False + ) lock_user = models.ForeignKey( - User, related_name='+', on_delete=models.SET_NULL, - verbose_name=_("Locked by"), blank=True, null=True) + User, + related_name="+", + on_delete=models.SET_NULL, + verbose_name=_("Locked by"), + blank=True, + null=True, + ) ALT_NAMES = { - 'history_creator': SearchAltName( + "history_creator": SearchAltName( pgettext_lazy("key for text search", "created-by"), - 'history_creator__ishtaruser__person__cached_label__iexact' + "history_creator__ishtaruser__person__cached_label__iexact", ), - 'history_modifier': SearchAltName( + "history_modifier": SearchAltName( pgettext_lazy("key for text search", "modified-by"), - 'history_modifier__ishtaruser__person__cached_label__iexact' + "history_modifier__ishtaruser__person__cached_label__iexact", ), - 'modified_before': SearchAltName( + "modified_before": SearchAltName( pgettext_lazy("key for text search", "modified-before"), - 'last_modified__lte' + "last_modified__lte", ), - 'modified_after': SearchAltName( - pgettext_lazy("key for text search", "modified-after"), - 'last_modified__gte' + "modified_after": SearchAltName( + pgettext_lazy("key for text search", "modified-after"), "last_modified__gte" ), } @@ -1235,8 +1359,8 @@ class BaseHistorizedItem(StatisticItem, TemplateItem, FullSearch, Imported, def update_external_id(self, save=False): if not self.EXTERNAL_ID_KEY or ( - self.external_id and - not getattr(self, 'auto_external_id', False)): + self.external_id and not getattr(self, "auto_external_id", False) + ): return external_id = get_generated_id(self.EXTERNAL_ID_KEY, self) if external_id == self.external_id: @@ -1250,10 +1374,10 @@ class BaseHistorizedItem(StatisticItem, TemplateItem, FullSearch, Imported, return external_id def get_last_history_date(self): - q = self.history.values("history_date").order_by('-history_date') + q = self.history.values("history_date").order_by("-history_date") if not q.count(): return - return q.all()[0]['history_date'] + return q.all()[0]["history_date"] def get_previous(self, step=None, date=None, strict=False): """ @@ -1288,11 +1412,11 @@ class BaseHistorizedItem(StatisticItem, TemplateItem, FullSearch, Imported, model = self.__class__ for k in get_all_field_names(model): field = model._meta.get_field(k) - if hasattr(field, 'rel') and field.rel: - if not hasattr(item, k + '_id'): + if hasattr(field, "rel") and field.rel: + if not hasattr(item, k + "_id"): setattr(item, k, getattr(self, k)) continue - val = getattr(item, k + '_id') + val = getattr(item, k + "_id") if not val: setattr(item, k, None) continue @@ -1301,8 +1425,9 @@ class BaseHistorizedItem(StatisticItem, TemplateItem, FullSearch, Imported, setattr(item, k, val) except ObjectDoesNotExist: if strict: - raise HistoryError("The class %s has no pk %d" % ( - str(field.rel.to), val)) + raise HistoryError( + "The class %s has no pk %d" % (str(field.rel.to), val) + ) setattr(item, k, None) item.pk = self.pk return item @@ -1310,14 +1435,14 @@ class BaseHistorizedItem(StatisticItem, TemplateItem, FullSearch, Imported, @property def last_edition_date(self): try: - return self.history.order_by('-history_date').all()[0].history_date + return self.history.order_by("-history_date").all()[0].history_date except (AttributeError, IndexError): return @property def history_creation_date(self): try: - return self.history.order_by('history_date').all()[0].history_date + return self.history.order_by("history_date").all()[0].history_date except (AttributeError, IndexError): return @@ -1336,14 +1461,15 @@ class BaseHistorizedItem(StatisticItem, TemplateItem, FullSearch, Imported, try: field_keys = [f.name for f in self._meta.fields] for k in field_keys: - if k != 'id' and hasattr(self, k): + if k != "id" and hasattr(self, k): if not hasattr(new_item, k): k = k + "_id" setattr(self, k, getattr(new_item, k)) try: self.history_modifier = User.objects.get( - pk=new_item.history_modifier_id) + pk=new_item.history_modifier_id + ) except User.ObjectDoesNotExist: pass self.save() @@ -1373,51 +1499,64 @@ class BaseHistorizedItem(StatisticItem, TemplateItem, FullSearch, Imported, values = {} for f in self._meta.fields: k = f.name - if k != 'id': + if k != "id": values[k] = getattr(self, k) return values def get_absolute_url(self): try: - return reverse('display-item', args=[self.SLUG, self.pk]) + return reverse("display-item", args=[self.SLUG, self.pk]) except NoReverseMatch: return def get_show_url(self): show_url = self.SHOW_URL if not show_url: - show_url = 'show-' + self.__class__.__name__.lower() + show_url = "show-" + self.__class__.__name__.lower() try: - return reverse(show_url, args=[self.pk, '']) + return reverse(show_url, args=[self.pk, ""]) except NoReverseMatch: return @property def associated_filename(self): - if [True for attr in ('get_town_label', 'get_department', 'reference', - 'short_class_name') if not hasattr(self, attr)]: - return '' - items = [slugify(self.get_department()), - slugify(self.get_town_label()).upper(), - slugify(self.short_class_name), - slugify(self.reference), - slugify(self.name or '').replace('-', '_').capitalize()] + if [ + True + for attr in ( + "get_town_label", + "get_department", + "reference", + "short_class_name", + ) + if not hasattr(self, attr) + ]: + return "" + items = [ + slugify(self.get_department()), + slugify(self.get_town_label()).upper(), + slugify(self.short_class_name), + slugify(self.reference), + slugify(self.name or "").replace("-", "_").capitalize(), + ] last_edition_date = self.last_edition_date if last_edition_date: - items.append(last_edition_date.strftime('%Y%m%d')) + items.append(last_edition_date.strftime("%Y%m%d")) else: - items.append('00000000') + items.append("00000000") return "-".join([str(item) for item in items]) def save(self, *args, **kwargs): created = not self.pk - if not getattr(self, 'skip_history_when_saving', False): - assert hasattr(self, 'history_modifier') + if not getattr(self, "skip_history_when_saving", False): + assert hasattr(self, "history_modifier") if created: self.history_creator = self.history_modifier # external ID can have related item not available before save - external_id_updated = kwargs.pop('external_id_updated') \ - if 'external_id_updated' in kwargs else False + external_id_updated = ( + kwargs.pop("external_id_updated") + if "external_id_updated" in kwargs + else False + ) if not created and not external_id_updated: self.update_external_id() super(BaseHistorizedItem, self).save(*args, **kwargs) @@ -1471,16 +1610,17 @@ class OwnPerms(object): checked :return: boolean """ - if not getattr(request.user, 'ishtaruser', None): + if not getattr(request.user, "ishtaruser", None): return False - splited = action_name.split('_') - action_own_name = splited[0] + '_own_' + '_'.join(splited[1:]) + splited = action_name.split("_") + action_own_name = splited[0] + "_own_" + "_".join(splited[1:]) user = request.user if action_own_name == "view_own_findbasket": action_own_name = "view_own_find" - return user.ishtaruser.has_right(action_name, request.session) or \ - (user.ishtaruser.has_right(action_own_name, request.session) - and self.is_own(user.ishtaruser)) + return user.ishtaruser.has_right(action_name, request.session) or ( + user.ishtaruser.has_right(action_own_name, request.session) + and self.is_own(user.ishtaruser) + ) def is_own(self, user, alt_query_own=None): """ @@ -1489,7 +1629,7 @@ class OwnPerms(object): IshtarUser = apps.get_model("ishtar_common", "IshtarUser") if isinstance(user, IshtarUser): ishtaruser = user - elif hasattr(user, 'ishtaruser'): + elif hasattr(user, "ishtaruser"): ishtaruser = user.ishtaruser else: return False @@ -1510,7 +1650,7 @@ class OwnPerms(object): IshtarUser = apps.get_model("ishtar_common", "IshtarUser") if isinstance(user, IshtarUser): ishtaruser = user - elif hasattr(user, 'ishtaruser'): + elif hasattr(user, "ishtaruser"): ishtaruser = user.ishtaruser else: return False @@ -1520,12 +1660,13 @@ class OwnPerms(object): return cls.objects.filter(query).count() @classmethod - def _return_get_owns(cls, owns, values, get_short_menu_class, - label_key='cached_label'): + def _return_get_owns( + cls, owns, values, get_short_menu_class, label_key="cached_label" + ): if not owns: return [] sorted_values = [] - if hasattr(cls, 'BASKET_MODEL'): + if hasattr(cls, "BASKET_MODEL"): owns_len = len(owns) for idx, item in enumerate(reversed(owns)): if get_short_menu_class: @@ -1537,24 +1678,31 @@ class OwnPerms(object): if not values: if not get_short_menu_class: return sorted_values + list( - sorted(owns, key=lambda x: getattr(x, label_key) or "")) + sorted(owns, key=lambda x: getattr(x, label_key) or "") + ) return sorted_values + list( - sorted(owns, key=lambda x: getattr(x[0], label_key) or "")) + sorted(owns, key=lambda x: getattr(x[0], label_key) or "") + ) if not get_short_menu_class: - return sorted_values + list( - sorted(owns, key=lambda x: x[label_key] or "")) - return sorted_values + list( - sorted(owns, key=lambda x: x[0][label_key] or "")) + return sorted_values + list(sorted(owns, key=lambda x: x[label_key] or "")) + return sorted_values + list(sorted(owns, key=lambda x: x[0][label_key] or "")) @classmethod - def get_owns(cls, user, replace_query=None, limit=None, values=None, - get_short_menu_class=False, menu_filtr=None): + def get_owns( + cls, + user, + replace_query=None, + limit=None, + values=None, + get_short_menu_class=False, + menu_filtr=None, + ): """ Get Own items """ if not replace_query: replace_query = {} - if hasattr(user, 'is_authenticated') and not user.is_authenticated(): + if hasattr(user, "is_authenticated") and not user.is_authenticated(): returned = cls.objects.filter(pk__isnull=True) if values: returned = [] @@ -1575,7 +1723,7 @@ class OwnPerms(object): return [] return cls.objects.filter(pk__isnull=True) items = [] - if hasattr(cls, 'BASKET_MODEL'): + if hasattr(cls, "BASKET_MODEL"): items = list(cls.BASKET_MODEL.objects.filter(user=ishtaruser).all()) query = cls.get_query_owns(ishtaruser) if not query and not replace_query: @@ -1590,24 +1738,25 @@ class OwnPerms(object): if values: q = q.values(*values) if limit: - items += list(q.order_by('-pk')[:limit]) + items += list(q.order_by("-pk")[:limit]) else: items += list(q.order_by(*cls._meta.ordering).all()) if get_short_menu_class: if values: - if 'id' not in values: + if "id" not in values: raise NotImplementedError( "Call of get_owns with get_short_menu_class option and" - " no 'id' in values is not implemented") + " no 'id' in values is not implemented" + ) my_items = [] for i in items: - if hasattr(cls, 'BASKET_MODEL') and \ - type(i) == cls.BASKET_MODEL: + if hasattr(cls, "BASKET_MODEL") and type(i) == cls.BASKET_MODEL: dct = dict([(k, getattr(i, k)) for k in values]) my_items.append( - (dct, cls.BASKET_MODEL.get_short_menu_class(i.pk))) + (dct, cls.BASKET_MODEL.get_short_menu_class(i.pk)) + ) else: - my_items.append((i, cls.get_short_menu_class(i['id']))) + my_items.append((i, cls.get_short_menu_class(i["id"]))) items = my_items else: items = [(i, cls.get_short_menu_class(i.pk)) for i in items] @@ -1654,7 +1803,7 @@ class State(models.Model): class Meta: verbose_name = _("State") - ordering = ['number'] + ordering = ["number"] def __str__(self): return self.label @@ -1667,7 +1816,10 @@ class Department(models.Model): label = models.CharField(_("Label"), max_length=30) number = models.CharField(_("Number"), unique=True, max_length=3) state = models.ForeignKey( - 'State', verbose_name=_("State"), blank=True, null=True, + "State", + verbose_name=_("State"), + blank=True, + null=True, on_delete=models.SET_NULL, ) objects = NumberManager() @@ -1675,7 +1827,7 @@ class Department(models.Model): class Meta: verbose_name = _("Department") verbose_name_plural = _("Departments") - ordering = ['number'] + ordering = ["number"] def __str__(self): return self.label @@ -1709,12 +1861,10 @@ class Arrondissement(models.Model): class Canton(models.Model): name = models.CharField("Nom", max_length=30) - arrondissement = models.ForeignKey(Arrondissement, - verbose_name="Arrondissement") + arrondissement = models.ForeignKey(Arrondissement, verbose_name="Arrondissement") def __str__(self): - return settings.JOINT.join( - (self.name, str(self.arrondissement))) + return settings.JOINT.join((self.name, str(self.arrondissement))) class TownManager(models.GeoManager): @@ -1725,37 +1875,46 @@ class TownManager(models.GeoManager): class Town(Imported, models.Model): name = models.CharField(_("Name"), max_length=100) surface = models.IntegerField(_("Surface (m2)"), blank=True, null=True) - center = models.PointField(_("Localisation"), srid=settings.SRID, - blank=True, null=True) + center = models.PointField( + _("Localisation"), srid=settings.SRID, blank=True, null=True + ) limit = models.MultiPolygonField(_("Limit"), blank=True, null=True) - numero_insee = models.CharField("Code commune (numéro INSEE)", - max_length=120) + numero_insee = models.CharField("Code commune (numéro INSEE)", max_length=120) departement = models.ForeignKey( - Department, verbose_name=_("Department"), - on_delete=models.SET_NULL, null=True, blank=True) + Department, + verbose_name=_("Department"), + on_delete=models.SET_NULL, + null=True, + blank=True, + ) year = models.IntegerField( - _("Year of creation"), null=True, blank=True, - help_text=_("Filling this field is relevant to distinguish old towns " - "from new towns.")) + _("Year of creation"), + null=True, + blank=True, + help_text=_( + "Filling this field is relevant to distinguish old towns " "from new towns." + ), + ) children = models.ManyToManyField( - 'Town', verbose_name=_("Town children"), blank=True, - related_name='parents') - cached_label = models.CharField(_("Cached name"), max_length=500, - null=True, blank=True, db_index=True) + "Town", verbose_name=_("Town children"), blank=True, related_name="parents" + ) + cached_label = models.CharField( + _("Cached name"), max_length=500, null=True, blank=True, db_index=True + ) objects = TownManager() class Meta: verbose_name = _("Town") verbose_name_plural = _("Towns") - if settings.COUNTRY == 'fr': - ordering = ['numero_insee'] - unique_together = (('numero_insee', 'year'),) + if settings.COUNTRY == "fr": + ordering = ["numero_insee"] + unique_together = (("numero_insee", "year"),) def natural_key(self): return (self.numero_insee, self.year) def history_compress(self): - return {'numero_insee': self.numero_insee, 'year': self.year or ""} + return {"numero_insee": self.numero_insee, "year": self.year or ""} @classmethod def get_documentation_string(cls): @@ -1763,13 +1922,14 @@ class Town(Imported, models.Model): Used for automatic documentation generation """ return "**name** {}, **numero_insee** {}, **cached_label** {}".format( - _("Name"), "Code commune (numéro INSEE)", _("Cached name")) + _("Name"), "Code commune (numéro INSEE)", _("Cached name") + ) - def get_values(self, prefix='', **kwargs): + def get_values(self, prefix="", **kwargs): return { prefix or "label": str(self), prefix + "name": self.name, - prefix + "numero_insee": self.numero_insee + prefix + "numero_insee": self.numero_insee, } @classmethod @@ -1780,8 +1940,10 @@ class Town(Imported, models.Model): for value in full_value: try: res.append( - cls.objects.get(numero_insee=value['numero_insee'], - year=value['year'] or None)) + cls.objects.get( + numero_insee=value["numero_insee"], year=value["year"] or None + ) + ) except cls.DoesNotExist: continue return res @@ -1818,8 +1980,8 @@ class Town(Imported, models.Model): else: parents = parents.union(parent.limit) # if union is a simple polygon make it a multi - if 'MULTI' not in parents.wkt: - parents = parents.wkt.replace('POLYGON', 'MULTIPOLYGON(') + ")" + if "MULTI" not in parents.wkt: + parents = parents.wkt.replace("POLYGON", "MULTIPOLYGON(") + ")" if not parents: return self.limit = parents @@ -1838,8 +2000,7 @@ class Town(Imported, models.Model): def generate_area(self, force=False): if not force and (self.surface or not self.limit): return - surface = self.limit.transform(settings.SURFACE_SRID, - clone=True).area + surface = self.limit.transform(settings.SURFACE_SRID, clone=True).area if surface > 214748364 or not surface: return False self.surface = surface @@ -1850,7 +2011,7 @@ class Town(Imported, models.Model): if not self.numero_insee or not self.children.count() or not self.year: return old_num = self.numero_insee[:] - numero = old_num.split('-')[0] + numero = old_num.split("-")[0] self.numero_insee = "{}-{}".format(numero, self.year) if self.numero_insee != old_num: return True @@ -1859,10 +2020,12 @@ class Town(Imported, models.Model): cached_label = self.name if settings.COUNTRY == "fr" and self.numero_insee: dpt_len = 2 - if self.numero_insee.startswith('97') or \ - self.numero_insee.startswith('98') or \ - self.numero_insee[0] not in ('0', '1', '2', '3', '4', '5', - '6', '7', '8', '9'): + if ( + self.numero_insee.startswith("97") + or self.numero_insee.startswith("98") + or self.numero_insee[0] + not in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9") + ): dpt_len = 3 cached_label = "%s - %s" % (self.name, self.numero_insee[:dpt_len]) if self.year and self.children.count(): @@ -1872,7 +2035,7 @@ class Town(Imported, models.Model): def post_save_town(sender, **kwargs): cached_label_changed(sender, **kwargs) - town = kwargs['instance'] + town = kwargs["instance"] town.generate_geo() if town.update_town_code(): town.save() @@ -1882,7 +2045,7 @@ post_save.connect(post_save_town, sender=Town) def town_child_changed(sender, **kwargs): - town = kwargs['instance'] + town = kwargs["instance"] if town.update_town_code(): town.save() @@ -1892,53 +2055,75 @@ m2m_changed.connect(town_child_changed, sender=Town.children.through) class Address(BaseHistorizedItem): FIELDS = ( - "address", "address_complement", "postal_code", "town", - "precise_town", "country", - "alt_address", "alt_address_complement", "alt_postal_code", "alt_town", + "address", + "address_complement", + "postal_code", + "town", + "precise_town", + "country", + "alt_address", + "alt_address_complement", + "alt_postal_code", + "alt_town", "alt_country", - "phone", "phone_desc", "phone2", "phone_desc2", "phone3", "phone_desc3", - "raw_phone", "mobile_phone", "email", "alt_address_is_prefered" + "phone", + "phone_desc", + "phone2", + "phone_desc2", + "phone3", + "phone_desc3", + "raw_phone", + "mobile_phone", + "email", + "alt_address_is_prefered", ) address = models.TextField(_("Address"), blank=True, default="") address_complement = models.TextField( - _("Address complement"), blank=True, default="") - postal_code = models.CharField(_("Postal code"), max_length=10, null=True, - blank=True) - town = models.CharField(_("Town (freeform)"), max_length=150, null=True, - blank=True) + _("Address complement"), blank=True, default="" + ) + postal_code = models.CharField( + _("Postal code"), max_length=10, null=True, blank=True + ) + town = models.CharField(_("Town (freeform)"), max_length=150, null=True, blank=True) precise_town = models.ForeignKey( - Town, verbose_name=_("Town (precise)"), null=True, - blank=True) - country = models.CharField(_("Country"), max_length=30, null=True, - blank=True) - alt_address = models.TextField( - _("Other address: address"), blank=True, default="") + Town, verbose_name=_("Town (precise)"), null=True, blank=True + ) + country = models.CharField(_("Country"), max_length=30, null=True, blank=True) + alt_address = models.TextField(_("Other address: address"), blank=True, default="") alt_address_complement = models.TextField( - _("Other address: address complement"), blank=True, default="") - alt_postal_code = models.CharField(_("Other address: postal code"), - max_length=10, null=True, blank=True) - alt_town = models.CharField(_("Other address: town"), max_length=70, - null=True, blank=True) - alt_country = models.CharField(_("Other address: country"), - max_length=30, null=True, blank=True) + _("Other address: address complement"), blank=True, default="" + ) + alt_postal_code = models.CharField( + _("Other address: postal code"), max_length=10, null=True, blank=True + ) + alt_town = models.CharField( + _("Other address: town"), max_length=70, null=True, blank=True + ) + alt_country = models.CharField( + _("Other address: country"), max_length=30, null=True, blank=True + ) phone = models.CharField(_("Phone"), max_length=18, null=True, blank=True) - phone_desc = models.CharField(_("Phone description"), max_length=300, - null=True, blank=True) - phone2 = models.CharField(_("Phone description 2"), max_length=18, - null=True, blank=True) - phone_desc2 = models.CharField(_("Phone description 2"), max_length=300, - null=True, blank=True) - phone3 = models.CharField(_("Phone 3"), max_length=18, null=True, - blank=True) - phone_desc3 = models.CharField(_("Phone description 3"), max_length=300, - null=True, blank=True) + phone_desc = models.CharField( + _("Phone description"), max_length=300, null=True, blank=True + ) + phone2 = models.CharField( + _("Phone description 2"), max_length=18, null=True, blank=True + ) + phone_desc2 = models.CharField( + _("Phone description 2"), max_length=300, null=True, blank=True + ) + phone3 = models.CharField(_("Phone 3"), max_length=18, null=True, blank=True) + phone_desc3 = models.CharField( + _("Phone description 3"), max_length=300, null=True, blank=True + ) raw_phone = models.TextField(_("Raw phone"), blank=True, default="") - mobile_phone = models.CharField(_("Mobile phone"), max_length=18, - null=True, blank=True) - email = models.EmailField( - _("Email"), max_length=300, blank=True, null=True) + mobile_phone = models.CharField( + _("Mobile phone"), max_length=18, null=True, blank=True + ) + email = models.EmailField(_("Email"), max_length=300, blank=True, null=True) alt_address_is_prefered = models.BooleanField( - _("Alternative address is prefered"), default=False) + _("Alternative address is prefered"), default=False + ) history = HistoricalRecords(inherit=True) SUB_ADDRESSES = [] @@ -1948,28 +2133,25 @@ class Address(BaseHistorizedItem): def get_short_html_items(self): items = [] if self.address: - items.append( - """<span class="subadress">{}</span>""".format(self.address)) + items.append("""<span class="subadress">{}</span>""".format(self.address)) if self.address_complement: items.append( """<span class="subadress-complement">{}</span>""".format( - self.address_complement)) + self.address_complement + ) + ) if self.postal_code: items.append( - """<span class="postal-code">{}</span>""".format( - self.postal_code)) + """<span class="postal-code">{}</span>""".format(self.postal_code) + ) if self.precise_town: items.append( - """<span class="town">{}</span>""".format( - self.precise_town.name)) + """<span class="town">{}</span>""".format(self.precise_town.name) + ) elif self.town: - items.append( - """<span class="town">{}</span>""".format( - self.town)) + items.append("""<span class="town">{}</span>""".format(self.town)) if self.country: - items.append( - """<span class="country">{}</span>""".format( - self.country)) + items.append("""<span class="country">{}</span>""".format(self.country)) return items def get_short_html_detail(self): @@ -1977,9 +2159,7 @@ class Address(BaseHistorizedItem): items = self.get_short_html_items() if not items: items = [ - "<span class='no-address'>{}</span>".format( - _("No associated address") - ) + "<span class='no-address'>{}</span>".format(_("No associated address")) ] html += "".join(items) html += """</div>""" @@ -2041,25 +2221,24 @@ class Address(BaseHistorizedItem): return lbl def address_lbl(self): - lbl = '' - prefix = '' + lbl = "" + prefix = "" if self.alt_address_is_prefered: - prefix = 'alt_' - if getattr(self, prefix + 'address'): - lbl += getattr(self, prefix + 'address') - if getattr(self, prefix + 'address_complement'): + prefix = "alt_" + if getattr(self, prefix + "address"): + lbl += getattr(self, prefix + "address") + if getattr(self, prefix + "address_complement"): if lbl: lbl += "\n" - lbl += getattr(self, prefix + 'address_complement') - postal_code = getattr(self, prefix + 'postal_code') - town = getattr(self, prefix + 'town') + lbl += getattr(self, prefix + "address_complement") + postal_code = getattr(self, prefix + "postal_code") + town = getattr(self, prefix + "town") if postal_code or town: if lbl: lbl += "\n" lbl += "{}{}{}".format( - postal_code or '', - " " if postal_code and town else '', - town or '') + postal_code or "", " " if postal_code and town else "", town or "" + ) if self.phone: if lbl: lbl += "\n" @@ -2079,11 +2258,10 @@ class Merge(models.Model): merge_key = models.TextField(_("Merge key"), blank=True, null=True) merge_candidate = models.ManyToManyField("self", blank=True) merge_exclusion = models.ManyToManyField("self", blank=True) - archived = models.NullBooleanField(default=False, - blank=True, null=True) + archived = models.NullBooleanField(default=False, blank=True, null=True) # 1 for one word similarity, 2 for two word similarity, etc. MERGE_CLEMENCY = None - EMPTY_MERGE_KEY = '--' + EMPTY_MERGE_KEY = "--" MERGE_ATTRIBUTE = "name" class Meta: @@ -2093,7 +2271,7 @@ class Merge(models.Model): if self.archived: return merge_attr = getattr(self, self.MERGE_ATTRIBUTE) - self.merge_key = slugify(merge_attr if merge_attr else '') + self.merge_key = slugify(merge_attr if merge_attr else "") if not self.merge_key: self.merge_key = self.EMPTY_MERGE_KEY self.merge_key = self.merge_key @@ -2106,28 +2284,29 @@ class Merge(models.Model): self.save(merge_key_generated=True) if not self.pk or self.merge_key == self.EMPTY_MERGE_KEY: return - q = self.__class__.objects \ - .exclude(pk=self.pk) \ - .exclude(merge_exclusion=self) \ - .exclude(merge_candidate=self) \ + q = ( + self.__class__.objects.exclude(pk=self.pk) + .exclude(merge_exclusion=self) + .exclude(merge_candidate=self) .exclude(archived=True) + ) if not self.MERGE_CLEMENCY: q = q.filter(merge_key=self.merge_key) else: - subkeys_front = "-".join( - self.merge_key.split('-')[:self.MERGE_CLEMENCY]) - subkeys_back = "-".join( - self.merge_key.split('-')[-self.MERGE_CLEMENCY:]) - q = q.filter(Q(merge_key__istartswith=subkeys_front) | - Q(merge_key__iendswith=subkeys_back)) + subkeys_front = "-".join(self.merge_key.split("-")[: self.MERGE_CLEMENCY]) + subkeys_back = "-".join(self.merge_key.split("-")[-self.MERGE_CLEMENCY :]) + q = q.filter( + Q(merge_key__istartswith=subkeys_front) + | Q(merge_key__iendswith=subkeys_back) + ) for item in q.all(): self.merge_candidate.add(item) def save(self, *args, **kwargs): # prevent circular save merge_key_generated = False - if 'merge_key_generated' in kwargs: - merge_key_generated = kwargs.pop('merge_key_generated') + if "merge_key_generated" in kwargs: + merge_key_generated = kwargs.pop("merge_key_generated") self.generate_merge_key() item = super(Merge, self).save(*args, **kwargs) if not merge_key_generated: @@ -2142,26 +2321,22 @@ class Merge(models.Model): self.merge_exclusion.clear() def merge(self, item, keep_old=False, exclude_fields=None): - merge_model_objects(self, item, keep_old=keep_old, - exclude_fields=exclude_fields) + merge_model_objects( + self, item, keep_old=keep_old, exclude_fields=exclude_fields + ) self.generate_merge_candidate() - def __get_stats_cache_values(model_name, model_pk): StatsCache = apps.get_model("ishtar_common", "StatsCache") - q = StatsCache.objects.filter( - model=model_name, model_pk=model_pk - ) + q = StatsCache.objects.filter(model=model_name, model_pk=model_pk) nb = q.count() if nb >= 1: sc = q.all()[0] for extra in q.order_by("-id").all()[1:]: extra.delete() else: - sc = StatsCache.objects.create( - model=model_name, model_pk=model_pk - ) + sc = StatsCache.objects.create(model=model_name, model_pk=model_pk) values = sc.values if not values: values = {} @@ -2184,6 +2359,7 @@ def _update_stats(app, model, model_pk, funcname): sc.updated = datetime.datetime.now() sc.save() + def update_stats(statscache, item, funcname): if not settings.USE_BACKGROUND_TASK: current_values = statscache.values @@ -2213,19 +2389,17 @@ class DashboardFormItem: def last_stats_update(self): model_name = self._meta.app_label + "." + self._meta.model_name StatsCache = apps.get_model("ishtar_common", "StatsCache") - q = StatsCache.objects.filter( - model=model_name, model_pk=self.pk).order_by("-updated") + q = StatsCache.objects.filter(model=model_name, model_pk=self.pk).order_by( + "-updated" + ) if not q.count(): return return q.all()[0].updated - def _get_or_set_stats(self, funcname, update=False, - expected_type=None): + def _get_or_set_stats(self, funcname, update=False, expected_type=None): model_name = self._meta.app_label + "." + self._meta.model_name StatsCache = apps.get_model("ishtar_common", "StatsCache") - sc, __ = StatsCache.objects.get_or_create( - model=model_name, model_pk=self.pk - ) + sc, __ = StatsCache.objects.get_or_create(model=model_name, model_pk=self.pk) if not update: values = sc.values if funcname not in values: @@ -2243,62 +2417,62 @@ class DashboardFormItem: return values @classmethod - def get_periods(cls, slice='month', fltr={}, date_source='creation'): - date_var = date_source + '_date' - q = cls.objects.filter(**{date_var + '__isnull': False}) + def get_periods(cls, slice="month", fltr={}, date_source="creation"): + date_var = date_source + "_date" + q = cls.objects.filter(**{date_var + "__isnull": False}) if fltr: q = q.filter(**fltr) - if slice == 'year': - return [res[date_var].year for res in list(q.values(date_var) - .annotate( - Count("id")).order_by())] - elif slice == 'month': - return [(res[date_var].year, res[date_var].month) - for res in list(q.values(date_var) - .annotate(Count("id")).order_by())] + if slice == "year": + return [ + res[date_var].year + for res in list(q.values(date_var).annotate(Count("id")).order_by()) + ] + elif slice == "month": + return [ + (res[date_var].year, res[date_var].month) + for res in list(q.values(date_var).annotate(Count("id")).order_by()) + ] return [] @classmethod - def get_by_year(cls, year, fltr={}, date_source='creation'): - date_var = date_source + '_date' - q = cls.objects.filter(**{date_var + '__isnull': False}) + def get_by_year(cls, year, fltr={}, date_source="creation"): + date_var = date_source + "_date" + q = cls.objects.filter(**{date_var + "__isnull": False}) if fltr: q = q.filter(**fltr) - return q.filter( - **{date_var + '__year': year}).order_by('pk').distinct('pk') + return q.filter(**{date_var + "__year": year}).order_by("pk").distinct("pk") @classmethod - def get_by_month(cls, year, month, fltr={}, date_source='creation'): - date_var = date_source + '_date' - q = cls.objects.filter(**{date_var + '__isnull': False}) + def get_by_month(cls, year, month, fltr={}, date_source="creation"): + date_var = date_source + "_date" + q = cls.objects.filter(**{date_var + "__isnull": False}) if fltr: q = q.filter(**fltr) - q = q.filter( - **{date_var + '__year': year, date_var + '__month': month}) - return q.order_by('pk').distinct('pk') + q = q.filter(**{date_var + "__year": year, date_var + "__month": month}) + return q.order_by("pk").distinct("pk") @classmethod def get_total_number(cls, fltr=None): q = cls.objects if fltr: q = q.filter(**fltr) - return q.order_by('pk').distinct('pk').count() + return q.order_by("pk").distinct("pk").count() class DocumentItem: ALT_NAMES = { - 'documents__image__isnull': - SearchAltName( - pgettext_lazy("key for text search", "has-image"), - 'documents__image__isnull'), - 'documents__associated_url__isnull': - SearchAltName( - pgettext_lazy("key for text search", "has-url"), - 'documents__associated_url__isnull'), - 'documents__associated_file__isnull': - SearchAltName( - pgettext_lazy("key for text search", "has-attached-file"), - 'documents__associated_file__isnull'), + "documents__image__isnull": SearchAltName( + pgettext_lazy("key for text search", "has-image"), + "documents__image__isnull", + ), + "documents__associated_url__isnull": SearchAltName( + pgettext_lazy("key for text search", "has-url"), + "documents__associated_url__isnull", + ), + "documents__associated_file__isnull": SearchAltName( + pgettext_lazy("key for text search", "has-attached-file"), + "documents__associated_file__isnull", + ), } def public_representation(self): @@ -2313,29 +2487,35 @@ class DocumentItem: @property def images(self): - if not hasattr(self, 'documents'): + if not hasattr(self, "documents"): Document = apps.get_model("ishtar_common", "Document") return Document.objects.none() - return self.documents.filter( - image__isnull=False).exclude(image="").order_by("pk") + return ( + self.documents.filter(image__isnull=False).exclude(image="").order_by("pk") + ) @property def images_without_main_image(self): - if not hasattr(self, 'main_image') or not hasattr(self, 'documents'): + if not hasattr(self, "main_image") or not hasattr(self, "documents"): return self.images if not self.main_image: - return self.documents.filter( - image__isnull=False).exclude( - image="").order_by("pk") - return self.documents.filter( - image__isnull=False).exclude( - image="").exclude(pk=self.main_image.pk).order_by("pk") + return ( + self.documents.filter(image__isnull=False) + .exclude(image="") + .order_by("pk") + ) + return ( + self.documents.filter(image__isnull=False) + .exclude(image="") + .exclude(pk=self.main_image.pk) + .order_by("pk") + ) @property def pdf_attached(self): for document in self.documents.filter( - Q(associated_file__isnull=False) | - Q(source__associated_file__isnull=False)).all(): + Q(associated_file__isnull=False) | Q(source__associated_file__isnull=False) + ).all(): return document.pdf_attached def get_extra_actions(self, request): @@ -2348,22 +2528,21 @@ class DocumentItem: except AttributeError: actions = [] - if not hasattr(self, 'SLUG'): + if not hasattr(self, "SLUG"): return actions - can_add_doc = self.can_do(request, 'add_document') + can_add_doc = self.can_do(request, "add_document") if can_add_doc and ( - not hasattr(self, "is_locked") or - not self.is_locked(request.user)): + not hasattr(self, "is_locked") or not self.is_locked(request.user) + ): actions += [ ( - reverse("create-document") + "?{}={}".format( - self.SLUG, self.pk), + reverse("create-document") + "?{}={}".format(self.SLUG, self.pk), _("Add document/image"), "fa fa-plus", _("doc./image"), "", - False + False, ) ] return actions @@ -2378,27 +2557,27 @@ def clean_duplicate_association(document, related_item, action): return if class_name == "Find": for cr in document.context_records.filter( - base_finds__find__pk=related_item.pk).all(): + base_finds__find__pk=related_item.pk + ).all(): document.context_records.remove(cr) for ope in document.operations.filter( - context_record__base_finds__find__pk=related_item.pk).all(): + context_record__base_finds__find__pk=related_item.pk + ).all(): document.operations.remove(ope) return if class_name == "ContextRecord": - for ope in document.operations.filter( - context_record__pk=related_item.pk).all(): + for ope in document.operations.filter(context_record__pk=related_item.pk).all(): document.operations.remove(ope) - if document.finds.filter( - base_finds__context_record=related_item.pk).count(): + if document.finds.filter(base_finds__context_record=related_item.pk).count(): document.context_records.remove(related_item) return if class_name == "Operation": - if document.context_records.filter( - operation=related_item.pk).count(): + if document.context_records.filter(operation=related_item.pk).count(): document.operations.remove(related_item) return if document.finds.filter( - base_finds__context_record__operation=related_item.pk).count(): + base_finds__context_record__operation=related_item.pk + ).count(): document.operations.remove(related_item) return @@ -2422,12 +2601,10 @@ def document_attached_changed(sender, **kwargs): return for item in items: - clean_duplicate_association(instance, item, - kwargs.get("action", None)) + clean_duplicate_association(instance, item, kwargs.get("action", None)) for doc in item.documents.all(): doc.regenerate_all_ids() - q = item.documents.filter( - image__isnull=False).exclude(image='') + q = item.documents.filter(image__isnull=False).exclude(image="") if item.main_image: if q.filter(pk=item.main_image.pk).count(): return @@ -2438,7 +2615,7 @@ def document_attached_changed(sender, **kwargs): if not q.count(): return # by default get the lowest pk - item.main_image = q.order_by('pk').all()[0] + item.main_image = q.order_by("pk").all()[0] item.skip_history_when_saving = True item.save() @@ -2448,22 +2625,23 @@ class QuickAction: Quick action available from tables """ - def __init__(self, url, icon_class='', text='', target=None, rights=None, - module=None): + def __init__( + self, url, icon_class="", text="", target=None, rights=None, module=None + ): self.url = url self.icon_class = icon_class self.text = text self.rights = rights self.target = target self.module = module - assert self.target in ('one', 'many', None) + assert self.target in ("one", "many", None) def is_available(self, user, session=None, obj=None): if self.module and not getattr(get_current_profile(), self.module): return False if not self.rights: # no restriction return True - if not user or not hasattr(user, 'ishtaruser') or not user.ishtaruser: + if not user or not hasattr(user, "ishtaruser") or not user.ishtaruser: return False user = user.ishtaruser @@ -2491,8 +2669,16 @@ class QuickAction: class DynamicRequest: - def __init__(self, label, app_name, model_name, form_key, search_key, - type_query, search_query): + def __init__( + self, + label, + app_name, + model_name, + form_key, + search_key, + type_query, + search_query, + ): self.label = label self.form_key = form_key self.search_key = search_key @@ -2509,36 +2695,34 @@ class DynamicRequest: fields = {} for item in self.get_all_types().all(): fields[self.form_key + "-" + item.txt_idx] = forms.CharField( - label=str(self.label) + " " + str(item), - required=False + label=str(self.label) + " " + str(item), required=False ) return fields def get_extra_query(self, slug): - return { - self.type_query: slug - } + return {self.type_query: slug} def get_alt_names(self): alt_names = {} for item in self.get_all_types().all(): alt_names[self.form_key + "-" + item.txt_idx] = SearchAltName( - self.search_key + "-" + item.txt_idx, self.search_query, - self.get_extra_query(item.txt_idx), distinct_query=True + self.search_key + "-" + item.txt_idx, + self.search_query, + self.get_extra_query(item.txt_idx), + distinct_query=True, ) return alt_names class SpatialReferenceSystem(GeneralType): order = models.IntegerField(_("Order"), default=10) - auth_name = models.CharField( - _("Authority name"), default='EPSG', max_length=256) + auth_name = models.CharField(_("Authority name"), default="EPSG", max_length=256) srid = models.IntegerField(_("Authority SRID")) class Meta: verbose_name = _("Spatial reference system") verbose_name_plural = _("Spatial reference systems") - ordering = ('label',) + ordering = ("label",) @classmethod def get_documentation_string(cls): @@ -2557,37 +2741,46 @@ post_delete.connect(post_save_cache, sender=SpatialReferenceSystem) class GeoItem(models.Model): - GEO_SOURCE = ( - ('T', _("Town")), ('P', _("Precise")), ('M', _("Polygon")) - ) + GEO_SOURCE = (("T", _("Town")), ("P", _("Precise")), ("M", _("Polygon"))) # gis - x = models.FloatField(_('X'), blank=True, null=True) - y = models.FloatField(_('Y'), blank=True, null=True) - z = models.FloatField(_('Z'), blank=True, null=True) - estimated_error_x = models.FloatField(_('Estimated error for X'), - blank=True, null=True) - estimated_error_y = models.FloatField(_('Estimated error for Y'), - blank=True, null=True) - estimated_error_z = models.FloatField(_('Estimated error for Z'), - blank=True, null=True) + x = models.FloatField(_("X"), blank=True, null=True) + y = models.FloatField(_("Y"), blank=True, null=True) + z = models.FloatField(_("Z"), blank=True, null=True) + estimated_error_x = models.FloatField( + _("Estimated error for X"), blank=True, null=True + ) + estimated_error_y = models.FloatField( + _("Estimated error for Y"), blank=True, null=True + ) + estimated_error_z = models.FloatField( + _("Estimated error for Z"), blank=True, null=True + ) spatial_reference_system = models.ForeignKey( - SpatialReferenceSystem, verbose_name=_("Spatial Reference System"), - blank=True, null=True) + SpatialReferenceSystem, + verbose_name=_("Spatial Reference System"), + blank=True, + null=True, + ) point = models.PointField(_("Point"), blank=True, null=True, dim=3) point_2d = models.PointField(_("Point (2D)"), blank=True, null=True) point_source = models.CharField( - _("Point source"), choices=GEO_SOURCE, max_length=1, blank=True, - null=True) + _("Point source"), choices=GEO_SOURCE, max_length=1, blank=True, null=True + ) point_source_item = models.CharField( - _("Point source item"), max_length=100, blank=True, null=True) - multi_polygon = models.MultiPolygonField(_("Multi polygon"), blank=True, - null=True) + _("Point source item"), max_length=100, blank=True, null=True + ) + multi_polygon = models.MultiPolygonField(_("Multi polygon"), blank=True, null=True) multi_polygon_source = models.CharField( - _("Multi-polygon source"), choices=GEO_SOURCE, max_length=1, - blank=True, null=True) + _("Multi-polygon source"), + choices=GEO_SOURCE, + max_length=1, + blank=True, + null=True, + ) multi_polygon_source_item = models.CharField( - _("Multi polygon source item"), max_length=100, blank=True, null=True) + _("Multi polygon source item"), max_length=100, blank=True, null=True + ) GEO_LABEL = "" @@ -2621,13 +2814,18 @@ class GeoItem(models.Model): if not self.point_2d: return "" profile = get_current_profile() - if not profile.display_srs or not profile.display_srs.srid or ( + if ( + not profile.display_srs + or not profile.display_srs.srid + or ( profile.display_srs == self.spatial_reference_system - and self.x and self.y): + and self.x + and self.y + ) + ): x, y = self.x, self.y else: - point = self.point_2d.transform(profile.display_srs.srid, - clone=True) + point = self.point_2d.transform(profile.display_srs.srid, clone=True) x, y = point.x, point.y if rounded: return round(x, 5), round(y, 5) @@ -2641,40 +2839,38 @@ class GeoItem(models.Model): return profile.display_srs def get_precise_points(self): - if self.point_source == 'P' and self.point_2d: + if self.point_source == "P" and self.point_2d: return self.point_2d, self.point, self.point_source_item def get_precise_polygons(self): - if self.multi_polygon_source == 'P' and self.multi_polygon: + if self.multi_polygon_source == "P" and self.multi_polygon: return self.multi_polygon, self.multi_polygon_source_item def most_precise_geo(self): - if self.point_source == 'M': - return 'multi_polygon' + if self.point_source == "M": + return "multi_polygon" current_source = str(self.__class__._meta.verbose_name) - if self.multi_polygon_source_item == current_source \ - and (self.multi_polygon_source == "P" or - (self.point_source_item != current_source and - self.point_source != "P")): - return 'multi_polygon' - if self.point_source_item == current_source \ - and self.point_source == 'P': - return 'point' - if self.multi_polygon_source == 'P': - return 'multi_polygon' - if self.point_source == 'P': - return 'point' + if self.multi_polygon_source_item == current_source and ( + self.multi_polygon_source == "P" + or (self.point_source_item != current_source and self.point_source != "P") + ): + return "multi_polygon" + if self.point_source_item == current_source and self.point_source == "P": + return "point" + if self.multi_polygon_source == "P": + return "multi_polygon" + if self.point_source == "P": + return "point" if self.multi_polygon: - return 'multi_polygon' + return "multi_polygon" if self.point_2d: - return 'point' + return "point" def geo_point_source(self): if not self.point_source: return "" return "{} - {}".format( - dict(self.GEO_SOURCE)[self.point_source], - self.point_source_item + dict(self.GEO_SOURCE)[self.point_source], self.point_source_item ) def geo_polygon_source(self): @@ -2682,31 +2878,33 @@ class GeoItem(models.Model): return "" return "{} - {}".format( dict(self.GEO_SOURCE)[self.multi_polygon_source], - self.multi_polygon_source_item + self.multi_polygon_source_item, ) def _geojson_serialize(self, geom_attr): if not hasattr(self, geom_attr): return "" - cached_label_key = 'cached_label' + cached_label_key = "cached_label" if self.GEO_LABEL: cached_label_key = self.GEO_LABEL if getattr(self, "CACHED_LABELS", None): cached_label_key = self.CACHED_LABELS[-1] geojson = serialize( - 'geojson', + "geojson", self.__class__.objects.filter(pk=self.pk), - geometry_field=geom_attr, fields=(cached_label_key,)) + geometry_field=geom_attr, + fields=(cached_label_key,), + ) geojson_dct = json.loads(geojson) profile = get_current_profile() precision = profile.point_precision - features = geojson_dct.pop('features') + features = geojson_dct.pop("features") for idx in range(len(features)): feature = features[idx] - lbl = feature['properties'].pop(cached_label_key) - feature['properties']['name'] = lbl - feature['properties']['id'] = self.pk + lbl = feature["properties"].pop(cached_label_key) + feature["properties"]["name"] = lbl + feature["properties"]["id"] = self.pk if precision is not None: geom_type = feature["geometry"].get("type", None) if geom_type == "Point": @@ -2714,20 +2912,20 @@ class GeoItem(models.Model): round(coord, precision) for coord in feature["geometry"]["coordinates"] ] - geojson_dct['features'] = features - geojson_dct['link_template'] = simple_link_to_window(self).replace( - '999999', '<pk>' + geojson_dct["features"] = features + geojson_dct["link_template"] = simple_link_to_window(self).replace( + "999999", "<pk>" ) geojson = json.dumps(geojson_dct) return geojson @property def point_2d_geojson(self): - return self._geojson_serialize('point_2d') + return self._geojson_serialize("point_2d") @property def multi_polygon_geojson(self): - return self._geojson_serialize('multi_polygon') + return self._geojson_serialize("multi_polygon") class ImageContainerModel: @@ -2742,10 +2940,12 @@ class ImageContainerModel: class CompleteIdentifierItem(models.Model, ImageContainerModel): HAS_QR_CODE = True complete_identifier = models.TextField( - _("Complete identifier"), blank=True, default="") + _("Complete identifier"), blank=True, default="" + ) custom_index = models.IntegerField("Custom index", blank=True, null=True) - qrcode = models.ImageField(upload_to=get_image_path, blank=True, null=True, - max_length=255) + qrcode = models.ImageField( + upload_to=get_image_path, blank=True, null=True, max_length=255 + ) class Meta: abstract = True @@ -2773,16 +2973,20 @@ class CompleteIdentifierItem(models.Model, ImageContainerModel): tiny_url = TinyUrl() tiny_url.link = url tiny_url.save() - short_url = scheme + "://" + site.domain + reverse( - 'tiny-redirect', args=[tiny_url.get_short_id()]) + short_url = ( + scheme + + "://" + + site.domain + + reverse("tiny-redirect", args=[tiny_url.get_short_id()]) + ) qr = pyqrcode.create(short_url, version=settings.ISHTAR_QRCODE_VERSION) tmpdir_created = False if not tmpdir: tmpdir = tempfile.mkdtemp("-qrcode") tmpdir_created = True - filename = tmpdir + os.sep + 'qrcode.png' + filename = tmpdir + os.sep + "qrcode.png" qr.png(filename, scale=settings.ISHTAR_QRCODE_SCALE) - with open(filename, 'rb') as qrfile: + with open(filename, "rb") as qrfile: self.qrcode.save("qrcode.png", File(qrfile)) self.skip_history_when_saving = True self._no_move = True @@ -2794,13 +2998,12 @@ class CompleteIdentifierItem(models.Model, ImageContainerModel): SLUG = getattr(self, "SLUG", None) if not SLUG: return "" - complete_identifier = get_generated_id( - SLUG + "_complete_identifier", self) + complete_identifier = get_generated_id(SLUG + "_complete_identifier", self) if complete_identifier: return complete_identifier - cached_label_key = 'cached_label' - if getattr(self, 'GEO_LABEL', None): - cached_label_key = getattr(self, 'GEO_LABEL', None) + cached_label_key = "cached_label" + if getattr(self, "GEO_LABEL", None): + cached_label_key = getattr(self, "GEO_LABEL", None) if hasattr(self, "CACHED_COMPLETE_ID"): cached_label_key = self.CACHED_COMPLETE_ID if not cached_label_key: @@ -2829,8 +3032,7 @@ class CompleteIdentifierItem(models.Model, ImageContainerModel): return getattr(self, "get_index_" + key)() model = self.__class__ try: - self_keys = set( - list(model.objects.filter(pk=self.pk).values_list(*keys))) + self_keys = set(list(model.objects.filter(pk=self.pk).values_list(*keys))) except Exception: # bad settings - not managed here return if len(self_keys) != 1: # key is not distinct @@ -2846,12 +3048,12 @@ class CompleteIdentifierItem(models.Model, ImageContainerModel): for idx, key in enumerate(keys): q = q.filter(**{key: self_keys[idx]}) try: - r = q.aggregate(max_index=Max('custom_index')) + r = q.aggregate(max_index=Max("custom_index")) except Exception: # bad settings return - if not r['max_index']: + if not r["max_index"]: return 1 - return r['max_index'] + 1 + return r["max_index"] + 1 def save(self, *args, **kwargs): super(CompleteIdentifierItem, self).save(*args, **kwargs) @@ -2887,8 +3089,8 @@ class SearchVectorConfig: self.func = func def format(self, value): - if value == 'None': - value = '' + if value == "None": + value = "" if not self.func: return [value] return self.func(value) @@ -2898,11 +3100,12 @@ class ShortMenuItem: """ Item available in the short menu """ + UP_MODEL_QUERY = {} @classmethod def get_short_menu_class(cls, pk): - return '' + return "" @property def short_class_name(self): @@ -2914,6 +3117,7 @@ class MainItem(ShortMenuItem): Item with quick actions available from tables Extra actions are available from sheets """ + QUICK_ACTIONS = [] @classmethod @@ -2925,10 +3129,14 @@ class MainItem(ShortMenuItem): for action in cls.QUICK_ACTIONS: if not action.is_available(user, session=session, obj=obj): continue - qas.append([action.base_url, - mark_safe(action.text), - mark_safe(action.rendered_icon), - action.target or ""]) + qas.append( + [ + action.base_url, + mark_safe(action.text), + mark_safe(action.rendered_icon), + action.target or "", + ] + ) return qas @classmethod @@ -2947,21 +3155,21 @@ class MainItem(ShortMenuItem): self.save() def get_extra_actions(self, request): - if not hasattr(self, 'SLUG'): + if not hasattr(self, "SLUG"): return [] actions = [] if request.user.is_superuser and hasattr(self, "auto_external_id"): actions += [ ( - reverse("regenerate-external-id") + "?{}={}".format( - self.SLUG, self.pk), + reverse("regenerate-external-id") + + "?{}={}".format(self.SLUG, self.pk), _("Regenerate ID"), "fa fa-key", _("regen."), "btn-info", True, - 200 + 200, ) ] diff --git a/ishtar_common/models_imports.py b/ishtar_common/models_imports.py index 7462f55be..5d522df27 100644 --- a/ishtar_common/models_imports.py +++ b/ishtar_common/models_imports.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2017 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> diff --git a/ishtar_common/serializers.py b/ishtar_common/serializers.py index 55989adcb..507173642 100644 --- a/ishtar_common/serializers.py +++ b/ishtar_common/serializers.py @@ -9,25 +9,30 @@ from django.apps import apps from django.conf import settings from django.core.serializers import deserialize -from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import Group, Permission from . import models from .models_common import State, Department from archaeological_operations.models import ActType -from ishtar_common.serializers_utils import generic_get_results, \ - archive_serialization, generic_archive_files, SERIALIZATION_VERSION, \ - get_model_from_filename - -from archaeological_operations.serializers import operation_serialization, \ - OPERATION_MODEL_LIST -from archaeological_context_records.serializers import cr_serialization, \ - CR_MODEL_LIST -from archaeological_finds.serializers import find_serialization, \ - FIND_MODEL_LIST -from archaeological_warehouse.serializers import warehouse_serialization, \ - WAREHOUSE_MODEL_LIST +from ishtar_common.serializers_utils import ( + generic_get_results, + archive_serialization, + generic_archive_files, + SERIALIZATION_VERSION, + get_model_from_filename, +) + +from archaeological_operations.serializers import ( + operation_serialization, + OPERATION_MODEL_LIST, +) +from archaeological_context_records.serializers import cr_serialization, CR_MODEL_LIST +from archaeological_finds.serializers import find_serialization, FIND_MODEL_LIST +from archaeological_warehouse.serializers import ( + warehouse_serialization, + WAREHOUSE_MODEL_LIST, +) from django.contrib.contenttypes.management import create_contenttypes @@ -42,116 +47,155 @@ TYPE_MODEL_EXCLUDE = ["Area", "OperationTypeOld", "ProfileTypeSummary"] def get_type_models(): return [Permission, Group] + [ - model for model in apps.get_models() - if isinstance(model(), models.GeneralType) and ( - model.__name__ not in TYPE_MODEL_EXCLUDE) + model + for model in apps.get_models() + if isinstance(model(), models.GeneralType) + and (model.__name__ not in TYPE_MODEL_EXCLUDE) ] -def type_serialization(archive=False, return_empty_types=False, - archive_name=None, info=None): +def type_serialization( + archive=False, return_empty_types=False, archive_name=None, info=None +): result = generic_get_results(get_type_models(), "types") - return archive_serialization(result, archive_dir="types", archive=archive, - return_empty_types=return_empty_types, - archive_name=archive_name, info=info) + return archive_serialization( + result, + archive_dir="types", + archive=archive, + return_empty_types=return_empty_types, + archive_name=archive_name, + info=info, + ) CONF_MODEL_LIST = [ - models.IshtarSiteProfile, models.GlobalVar, models.CustomForm, - models.ExcludedField, models.JsonDataSection, models.JsonDataField, - models.CustomFormJsonField, models.ImporterModel, - models.DocumentTemplate, ActType + models.IshtarSiteProfile, + models.GlobalVar, + models.CustomForm, + models.ExcludedField, + models.JsonDataSection, + models.JsonDataField, + models.CustomFormJsonField, + models.ImporterModel, + models.DocumentTemplate, + ActType, ] CONF_SERIALIZATION_INCLUDE = {ActType.__name__: ["associated_template"]} -def conf_serialization(archive=False, return_empty_types=False, - archive_name=None): +def conf_serialization(archive=False, return_empty_types=False, archive_name=None): media_archive = None if archive: media_archive = generic_archive_files(CONF_MODEL_LIST) result = generic_get_results( - CONF_MODEL_LIST, "common_configuration", - serialization_include=CONF_SERIALIZATION_INCLUDE) + CONF_MODEL_LIST, + "common_configuration", + serialization_include=CONF_SERIALIZATION_INCLUDE, + ) full_archive = archive_serialization( - result, archive_dir="common_configuration", archive=archive, - return_empty_types=return_empty_types, archive_name=archive_name) + result, + archive_dir="common_configuration", + archive=archive, + return_empty_types=return_empty_types, + archive_name=archive_name, + ) if not media_archive: return full_archive - with ZipFile(full_archive, 'a') as current_zip: + with ZipFile(full_archive, "a") as current_zip: current_zip.write(media_archive, arcname="media.zip") return full_archive IMPORT_MODEL_LIST = [ - models.Regexp, models.ImporterModel, models.ImporterType, - models.ValueFormater, models.ImporterColumn, - models.FormaterType, models.ImporterDefault, models.ImporterDefaultValues, - models.ImportTarget, models.ImporterDefaultValues, - models.ImporterDuplicateField + models.Regexp, + models.ImporterModel, + models.ImporterType, + models.ValueFormater, + models.ImporterColumn, + models.FormaterType, + models.ImporterDefault, + models.ImporterDefaultValues, + models.ImportTarget, + models.ImporterDefaultValues, + models.ImporterDuplicateField, ] -def importer_serialization(archive=False, return_empty_types=False, - archive_name=None): +def importer_serialization(archive=False, return_empty_types=False, archive_name=None): result = generic_get_results(IMPORT_MODEL_LIST, "common_imports") full_archive = archive_serialization( - result, archive_dir="common_imports", archive=archive, - return_empty_types=return_empty_types, archive_name=archive_name) + result, + archive_dir="common_imports", + archive=archive, + return_empty_types=return_empty_types, + archive_name=archive_name, + ) return full_archive -GEO_MODEL_LIST = [ - State, Department, models.Town, models.Area -] +GEO_MODEL_LIST = [State, Department, models.Town, models.Area] -def geo_serialization(archive=False, return_empty_types=False, - archive_name=None, no_geo=True): +def geo_serialization( + archive=False, return_empty_types=False, archive_name=None, no_geo=True +): result = generic_get_results(GEO_MODEL_LIST, "common_geo", no_geo=no_geo) full_archive = archive_serialization( - result, archive_dir="common_geo", archive=archive, - return_empty_types=return_empty_types, archive_name=archive_name) + result, + archive_dir="common_geo", + archive=archive, + return_empty_types=return_empty_types, + archive_name=archive_name, + ) return full_archive -DIRECTORY_MODEL_LIST = [ - models.Organization, models.Person, models.Author -] +DIRECTORY_MODEL_LIST = [models.Organization, models.Person, models.Author] -def directory_serialization(archive=False, return_empty_types=False, - archive_name=None): +def directory_serialization(archive=False, return_empty_types=False, archive_name=None): result = generic_get_results(DIRECTORY_MODEL_LIST, "common_directory") full_archive = archive_serialization( - result, archive_dir="common_directory", archive=archive, - return_empty_types=return_empty_types, archive_name=archive_name) + result, + archive_dir="common_directory", + archive=archive, + return_empty_types=return_empty_types, + archive_name=archive_name, + ) return full_archive -def document_serialization(archive=False, return_empty_types=False, - archive_name=None, operation_queryset=None, - site_queryset=None, cr_queryset=None, - find_queryset=None, warehouse_queryset=None, - put_locks=False, lock_user=None): +def document_serialization( + archive=False, + return_empty_types=False, + archive_name=None, + operation_queryset=None, + site_queryset=None, + cr_queryset=None, + find_queryset=None, + warehouse_queryset=None, + put_locks=False, + lock_user=None, +): result_queryset = {} get_queryset_attr = None if operation_queryset: - get_queryset_attr = {"operation_queryset": operation_queryset, - "get_queryset": True} + get_queryset_attr = { + "operation_queryset": operation_queryset, + "get_queryset": True, + } elif site_queryset: - get_queryset_attr = {"site_queryset": site_queryset, - "get_queryset": True} + get_queryset_attr = {"site_queryset": site_queryset, "get_queryset": True} elif cr_queryset: - get_queryset_attr = {"cr_queryset": cr_queryset, - "get_queryset": True} + get_queryset_attr = {"cr_queryset": cr_queryset, "get_queryset": True} elif find_queryset: - get_queryset_attr = {"find_queryset": find_queryset, - "get_queryset": True} + get_queryset_attr = {"find_queryset": find_queryset, "get_queryset": True} elif warehouse_queryset: - get_queryset_attr = {"warehouse_queryset": warehouse_queryset, - "get_queryset": True} + get_queryset_attr = { + "warehouse_queryset": warehouse_queryset, + "get_queryset": True, + } if get_queryset_attr: queries = operation_serialization(**get_queryset_attr) @@ -160,22 +204,26 @@ def document_serialization(archive=False, return_empty_types=False, queries.update(warehouse_serialization(**get_queryset_attr)) document_ids = set() for model, attr in ( - ("Operation", "operations"), - ("ArchaeologicalSite", "sites"), - ("ContextRecord", "context_records"), - ("Find", "finds"), - ("Warehouse", "warehouses"), - ("Container", "containers")): + ("Operation", "operations"), + ("ArchaeologicalSite", "sites"), + ("ContextRecord", "context_records"), + ("Find", "finds"), + ("Warehouse", "warehouses"), + ("Container", "containers"), + ): values = list(queries[model].values_list("id", flat=True)) document_ids.update( models.Document.objects.filter( - **{attr + "__id__in": values}).values_list( - "id", flat=True)) + **{attr + "__id__in": values} + ).values_list("id", flat=True) + ) result_queryset["Document"] = models.Document.objects.filter( - id__in=document_ids) + id__in=document_ids + ) - result = generic_get_results([models.Document], "documents", - result_queryset=result_queryset) + result = generic_get_results( + [models.Document], "documents", result_queryset=result_queryset + ) if put_locks: q = models.Document.objects if result_queryset: @@ -184,36 +232,39 @@ def document_serialization(archive=False, return_empty_types=False, media_archive = None if archive: - media_archive = generic_archive_files([models.Document], - result_queryset=result_queryset) + media_archive = generic_archive_files( + [models.Document], result_queryset=result_queryset + ) full_archive = archive_serialization( - result, archive_dir="documents", archive=archive, - return_empty_types=return_empty_types, archive_name=archive_name) + result, + archive_dir="documents", + archive=archive, + return_empty_types=return_empty_types, + archive_name=archive_name, + ) if not media_archive: return full_archive - has_media = "media.zip" in ZipFile(full_archive, 'r').namelist() + has_media = "media.zip" in ZipFile(full_archive, "r").namelist() if not has_media: - with ZipFile(full_archive, 'a') as current_zip: + with ZipFile(full_archive, "a") as current_zip: current_zip.write(media_archive, arcname="media.zip") os.remove(media_archive) return full_archive with tempfile.TemporaryDirectory() as tmp_dir_name: # extract the current archive - current_zip = ZipFile(full_archive, 'r') + current_zip = ZipFile(full_archive, "r") name_list = current_zip.namelist() for name in name_list: current_zip.extract(name, tmp_dir_name) current_zip.close() # extract the media and recreate a media.zip - old_media_archive = ZipFile( - os.path.join(tmp_dir_name, "media.zip"), "r") + old_media_archive = ZipFile(os.path.join(tmp_dir_name, "media.zip"), "r") with ZipFile(media_archive, "a") as new_zip: for name in old_media_archive.namelist(): - new_zip.writestr( - name, old_media_archive.open(name).read()) + new_zip.writestr(name, old_media_archive.open(name).read()) # rewrite the archive with ZipFile(full_archive + "_new", "w") as new_zip: @@ -228,76 +279,119 @@ def document_serialization(archive=False, return_empty_types=False, return full_archive -def full_serialization(operation_queryset=None, site_queryset=None, - cr_queryset=None, find_queryset=None, - warehouse_queryset=None, archive=True, no_geo=True, - info=None, export_types=True, export_conf=True, - export_importers=True, export_geo=True, export_dir=True, - export_docs=True, export_items=True, put_locks=False, - lock_user=None): +def full_serialization( + operation_queryset=None, + site_queryset=None, + cr_queryset=None, + find_queryset=None, + warehouse_queryset=None, + archive=True, + no_geo=True, + info=None, + export_types=True, + export_conf=True, + export_importers=True, + export_geo=True, + export_dir=True, + export_docs=True, + export_items=True, + put_locks=False, + lock_user=None, +): archive_name = None if export_types: # print("type") archive_name = type_serialization(archive=archive, info=info) if export_conf: # print("conf") - archive_name = conf_serialization(archive=archive, - archive_name=archive_name) + archive_name = conf_serialization(archive=archive, archive_name=archive_name) if export_importers: # print("importer") - archive_name = importer_serialization(archive=archive, - archive_name=archive_name) + archive_name = importer_serialization( + archive=archive, archive_name=archive_name + ) if export_geo: # print("geo") archive_name = geo_serialization( - archive=archive, archive_name=archive_name, no_geo=no_geo) + archive=archive, archive_name=archive_name, no_geo=no_geo + ) if export_dir: # print("directory") - archive_name = directory_serialization(archive=archive, - archive_name=archive_name) + archive_name = directory_serialization( + archive=archive, archive_name=archive_name + ) if export_docs: # print("document") archive_name = document_serialization( - archive=archive, archive_name=archive_name, - operation_queryset=operation_queryset, site_queryset=site_queryset, - cr_queryset=cr_queryset, find_queryset=find_queryset, + archive=archive, + archive_name=archive_name, + operation_queryset=operation_queryset, + site_queryset=site_queryset, + cr_queryset=cr_queryset, + find_queryset=find_queryset, warehouse_queryset=warehouse_queryset, - put_locks=put_locks, lock_user=lock_user + put_locks=put_locks, + lock_user=lock_user, ) if export_items: # print("operation") archive_name = operation_serialization( archive=archive, - archive_name=archive_name, operation_queryset=operation_queryset, - site_queryset=site_queryset, cr_queryset=cr_queryset, - find_queryset=find_queryset, warehouse_queryset=warehouse_queryset, - no_geo=no_geo, put_locks=put_locks, lock_user=lock_user) + archive_name=archive_name, + operation_queryset=operation_queryset, + site_queryset=site_queryset, + cr_queryset=cr_queryset, + find_queryset=find_queryset, + warehouse_queryset=warehouse_queryset, + no_geo=no_geo, + put_locks=put_locks, + lock_user=lock_user, + ) # print("cr") cr_serialization( archive=archive, - archive_name=archive_name, operation_queryset=operation_queryset, - site_queryset=site_queryset, cr_queryset=cr_queryset, - find_queryset=find_queryset, warehouse_queryset=warehouse_queryset, - no_geo=no_geo, put_locks=put_locks, lock_user=lock_user) + archive_name=archive_name, + operation_queryset=operation_queryset, + site_queryset=site_queryset, + cr_queryset=cr_queryset, + find_queryset=find_queryset, + warehouse_queryset=warehouse_queryset, + no_geo=no_geo, + put_locks=put_locks, + lock_user=lock_user, + ) # print("find") find_serialization( archive=archive, - archive_name=archive_name, operation_queryset=operation_queryset, - site_queryset=site_queryset, cr_queryset=cr_queryset, - find_queryset=find_queryset, warehouse_queryset=warehouse_queryset, - no_geo=no_geo, put_locks=put_locks, lock_user=lock_user) + archive_name=archive_name, + operation_queryset=operation_queryset, + site_queryset=site_queryset, + cr_queryset=cr_queryset, + find_queryset=find_queryset, + warehouse_queryset=warehouse_queryset, + no_geo=no_geo, + put_locks=put_locks, + lock_user=lock_user, + ) # print("warehouse") warehouse_serialization( archive=archive, - archive_name=archive_name, operation_queryset=operation_queryset, - site_queryset=site_queryset, cr_queryset=cr_queryset, - find_queryset=find_queryset, warehouse_queryset=warehouse_queryset, - no_geo=no_geo, put_locks=put_locks, lock_user=lock_user) + archive_name=archive_name, + operation_queryset=operation_queryset, + site_queryset=site_queryset, + cr_queryset=cr_queryset, + find_queryset=find_queryset, + warehouse_queryset=warehouse_queryset, + no_geo=no_geo, + put_locks=put_locks, + lock_user=lock_user, + ) return archive_name -def restore_serialized(archive_name, user=None, delete_existing=False, - release_locks=False): +def restore_serialized( + archive_name, user=None, delete_existing=False, release_locks=False +): for app in apps.get_app_configs(): create_contenttypes(app, verbosity=1, interactive=False) @@ -330,7 +424,8 @@ def restore_serialized(archive_name, user=None, delete_existing=False, with tempfile.TemporaryDirectory() as tmp_dir_name: zip_file.extract("media.zip", tmp_dir_name) with zipfile.ZipFile( - tmp_dir_name + os.sep + "media.zip", 'r') as media_zip: + tmp_dir_name + os.sep + "media.zip", "r" + ) as media_zip: media_zip.extractall(settings.MEDIA_ROOT) for current_dir, model_list in DIRS: @@ -347,12 +442,15 @@ def restore_serialized(archive_name, user=None, delete_existing=False, data = zip_file.read(json_filename).decode("utf-8") # regenerate labels, add a new version, etc. historized = hasattr(model, "history_modifier") and ( - hasattr(model, "history_creator")) - releasing_locks = hasattr(model, "locked") and ( - release_locks) - need_resave = hasattr(model, "CACHED_LABELS") or \ - hasattr(model, "cached_label") or \ - releasing_locks or (user and historized) + hasattr(model, "history_creator") + ) + releasing_locks = hasattr(model, "locked") and (release_locks) + need_resave = ( + hasattr(model, "CACHED_LABELS") + or hasattr(model, "cached_label") + or releasing_locks + or (user and historized) + ) idx = -1 for idx, obj in enumerate(deserialize("json", data)): extra_attrs = {} @@ -360,37 +458,36 @@ def restore_serialized(archive_name, user=None, delete_existing=False, keys = obj.object.natural_key() old_obj = None try: - old_obj = model.objects.get_by_natural_key( - *keys) + old_obj = model.objects.get_by_natural_key(*keys) except model.DoesNotExist: pass if old_obj: - if historized and (old_obj.history_creator or - old_obj.history_modifier): + if historized and ( + old_obj.history_creator or old_obj.history_modifier + ): extra_attrs = { - "history_modifier_id": - old_obj.history_modifier_id, - "history_creator_id": - old_obj.history_creator_id + "history_modifier_id": old_obj.history_modifier_id, + "history_creator_id": old_obj.history_creator_id, } if hasattr(model, "locked") and old_obj.locked: - extra_attrs.update({ - "locked": old_obj.locked, - "lock_user": old_obj.lock_user, - }) + extra_attrs.update( + { + "locked": old_obj.locked, + "lock_user": old_obj.lock_user, + } + ) obj.save() if need_resave or extra_attrs: obj = model.objects.get(id=obj.object.id) if user: obj.history_modifier = user - if extra_attrs and \ - "history_creator_id" in extra_attrs: + if extra_attrs and "history_creator_id" in extra_attrs: obj.history_creator_id = extra_attrs[ - "history_creator_id"] + "history_creator_id" + ] else: obj.history_creator = user - if extra_attrs and \ - "locked" in extra_attrs: + if extra_attrs and "locked" in extra_attrs: obj.locked = extra_attrs["locked"] obj.lock_user = extra_attrs["lock_user"] elif extra_attrs: diff --git a/ishtar_common/serializers_utils.py b/ishtar_common/serializers_utils.py index c03a55e35..99ebf9f4e 100644 --- a/ishtar_common/serializers_utils.py +++ b/ishtar_common/serializers_utils.py @@ -27,9 +27,8 @@ def get_model_from_filename(filename): if module_name == "django": if model_name in ("Group", "Permission"): module = importlib.import_module("django.contrib.auth.models") - elif model_name in ("ContentType", ): - module = importlib.import_module( - "django.contrib.contenttypes.models") + elif model_name in ("ContentType",): + module = importlib.import_module("django.contrib.contenttypes.models") else: return else: @@ -44,16 +43,21 @@ def serialization_info(info=None): "ishtar-version": get_version(), "domain": site.domain, "name": site.name, - "date": datetime.datetime.now().isoformat() + "date": datetime.datetime.now().isoformat(), } if info: base_info.update(info) return base_info -def archive_serialization(result, archive_dir=None, archive=False, - return_empty_types=False, archive_name=None, - info=None): +def archive_serialization( + result, + archive_dir=None, + archive=False, + return_empty_types=False, + archive_name=None, + info=None, +): """ Serialize all types models to JSON Used for import and export scripts @@ -94,9 +98,7 @@ def archive_serialization(result, archive_dir=None, archive=False, base_filename = "info.json" filename = tmpdirname + os.sep + base_filename with open(filename, "w") as json_file: - json_file.write( - json.dumps(serialization_info(info=info), indent=2) - ) + json_file.write(json.dumps(serialization_info(info=info), indent=2)) current_zip.write(filename, arcname=base_filename) for dir_name, model_name in result: @@ -112,27 +114,30 @@ def archive_serialization(result, archive_dir=None, archive=False, GENERIC_QUERYSET_FILTER = { - "Regexp": {"ImporterType": 'columns__importer_type__pk__in'}, - "ImporterModel": {"ImporterType": ['importer_type_associated__pk__in', - 'importer_type_created__pk__in']}, - "ValueFormater": {"ImporterType": 'columns__importer_type__pk__in'}, - "ImporterColumn": {"ImporterType": 'importer_type__pk__in'}, - "ImporterDefault": {"ImporterType": 'importer_type__pk__in'}, - "ImportTarget": {"ImporterType": 'column__importer_type__pk__in'}, - "FormaterType": {"ImporterType": 'targets__column__importer_type__pk__in'}, - "ImporterDefaultValues": { - "ImporterType": 'default_target__importer_type__pk__in'}, - "ImporterDuplicateField": {"ImporterType": 'column__importer_type__pk__in'}, + "Regexp": {"ImporterType": "columns__importer_type__pk__in"}, + "ImporterModel": { + "ImporterType": [ + "importer_type_associated__pk__in", + "importer_type_created__pk__in", + ] + }, + "ValueFormater": {"ImporterType": "columns__importer_type__pk__in"}, + "ImporterColumn": {"ImporterType": "importer_type__pk__in"}, + "ImporterDefault": {"ImporterType": "importer_type__pk__in"}, + "ImportTarget": {"ImporterType": "column__importer_type__pk__in"}, + "FormaterType": {"ImporterType": "targets__column__importer_type__pk__in"}, + "ImporterDefaultValues": {"ImporterType": "default_target__importer_type__pk__in"}, + "ImporterDuplicateField": {"ImporterType": "column__importer_type__pk__in"}, } -def generic_get_results(model_list, dirname, no_geo=True, - result_queryset=None, serialization_include=None): +def generic_get_results( + model_list, dirname, no_geo=True, result_queryset=None, serialization_include=None +): result = OrderedDict() for model in model_list: base_model_name = model.__name__ - model_name = str(model.__module__).split(".")[0] + "__" + \ - base_model_name + model_name = str(model.__module__).split(".")[0] + "__" + base_model_name base_q = model.objects if result_queryset: if result_queryset and base_model_name in result_queryset: @@ -144,8 +149,7 @@ def generic_get_results(model_list, dirname, no_geo=True, terms = alt_filter[k] if not isinstance(terms, (list, tuple)): terms = [terms] - ids = [r["pk"] - for r in result_queryset[k].values("pk").all()] + ids = [r["pk"] for r in result_queryset[k].values("pk").all()] q = None for term in terms: if not q: @@ -167,9 +171,11 @@ def generic_get_results(model_list, dirname, no_geo=True, key = (dirname, model_name) result[key] = serialize( - "json", q.distinct().all(), + "json", + q.distinct().all(), indent=2, - use_natural_foreign_keys=True, use_natural_primary_keys=True, + use_natural_foreign_keys=True, + use_natural_primary_keys=True, ) if recursion: @@ -178,24 +184,32 @@ def generic_get_results(model_list, dirname, no_geo=True, if not recursion.endswith("_id"): recursion_in += "_id" recursion_in += "__in" - q = base_q.filter(**{recursion_in: serialized} - ).exclude(id__in=serialized) + q = base_q.filter(**{recursion_in: serialized}).exclude(id__in=serialized) while q.count(): v = serialize( - "json", q.all(), indent=2, use_natural_foreign_keys=True, - use_natural_primary_keys=True) + "json", + q.all(), + indent=2, + use_natural_foreign_keys=True, + use_natural_primary_keys=True, + ) new_result = json.loads(result[key]) new_result += json.loads(v) result[key] = json.dumps(new_result, indent=2) serialized += [item["id"] for item in q.values("id").all()] - q = base_q.filter(**{recursion_in: serialized} - ).exclude(id__in=serialized) + q = base_q.filter(**{recursion_in: serialized}).exclude( + id__in=serialized + ) # managed circular q = base_q.exclude(id__in=serialized) if q.count(): v = serialize( - "json", q.all(), indent=2, use_natural_foreign_keys=True, - use_natural_primary_keys=True) + "json", + q.all(), + indent=2, + use_natural_foreign_keys=True, + use_natural_primary_keys=True, + ) result_to_add = json.loads(v) result_cleaned = deepcopy(result_to_add) for res in result_cleaned: # first add with no recursion @@ -205,8 +219,13 @@ def generic_get_results(model_list, dirname, no_geo=True, new_result += result_to_add result[key] = json.dumps(new_result, indent=2) - excluded_fields = ["history_modifier", "history_creator", "imports", - "locked", "lock_user"] + excluded_fields = [ + "history_modifier", + "history_creator", + "imports", + "locked", + "lock_user", + ] if hasattr(model, "SERIALIZATION_EXCLUDE"): excluded_fields += list(model.SERIALIZATION_EXCLUDE) if no_geo: diff --git a/ishtar_common/tasks.py b/ishtar_common/tasks.py index 96db07b1b..03b8a6338 100644 --- a/ishtar_common/tasks.py +++ b/ishtar_common/tasks.py @@ -45,7 +45,7 @@ from archaeological_warehouse.views import get_warehouse @task() def trigger_error(): - return 1/0 + return 1 / 0 @task() @@ -54,18 +54,20 @@ def launch_import(import_task_id): import_task = ImportTask.objects.get(pk=import_task_id) except ImportTask.DoesNotExist: return - if import_task.state != 'S': + if import_task.state != "S": return import_task.launch_date = datetime.datetime.now() - import_task.state = 'P' + import_task.state = "P" import_task.save() - restore_serialized(import_task.source.path, - import_task.import_user, - delete_existing=import_task.delete_before, - release_locks=import_task.releasing_locks) + restore_serialized( + import_task.source.path, + import_task.import_user, + delete_existing=import_task.delete_before, + release_locks=import_task.releasing_locks, + ) import_task.finished_date = datetime.datetime.now() - import_task.state = 'F' + import_task.state = "F" import_task.save() @@ -75,18 +77,27 @@ def launch_export(export_task_id): export_task = ExportTask.objects.get(pk=export_task_id) except ExportTask.DoesNotExist: return - if export_task.state != 'S': + if export_task.state != "S": return export_task.launch_date = datetime.datetime.now() - export_task.state = 'P' + export_task.state = "P" export_task.save() - kwargs = {"info": {}, - "put_locks": export_task.put_locks, - "lock_user": export_task.lock_user} - - for fltr_key in ("export_types", "export_conf", "export_importers", - "export_geo", "export_dir", "export_docs", "export_items"): + kwargs = { + "info": {}, + "put_locks": export_task.put_locks, + "lock_user": export_task.lock_user, + } + + for fltr_key in ( + "export_types", + "export_conf", + "export_importers", + "export_geo", + "export_dir", + "export_docs", + "export_items", + ): kwargs["info"][fltr_key] = getattr(export_task, fltr_key) kwargs[fltr_key] = getattr(export_task, fltr_key) @@ -96,62 +107,68 @@ def launch_export(export_task_id): if export_task.filter_type == "O": kwargs["info"]["query"]["model"] = "Operation" ids = list( - get_operation( - None, query=query, return_query=True).values_list( - "id", flat=True)) + get_operation(None, query=query, return_query=True).values_list( + "id", flat=True + ) + ) if not ids: - export_task.state = 'F' + export_task.state = "F" export_task.result_info = str(_("No data to export")) export_task.save() return - kwargs["operation_queryset"] = Operation.objects.filter( - pk__in=ids) + kwargs["operation_queryset"] = Operation.objects.filter(pk__in=ids) elif export_task.filter_type == "S": kwargs["info"]["query"]["model"] = "ArchaeologicalSite" - ids = list(get_site( - None, query=query, return_query=True).values_list( - "id", flat=True)) + ids = list( + get_site(None, query=query, return_query=True).values_list( + "id", flat=True + ) + ) if not ids: - export_task.state = 'F' + export_task.state = "F" export_task.result_info = str(_("No data to export")) export_task.save() return - kwargs["site_queryset"] = ArchaeologicalSite.objects.filter( - pk__in=ids) + kwargs["site_queryset"] = ArchaeologicalSite.objects.filter(pk__in=ids) elif export_task.filter_type == "CR": kwargs["info"]["query"]["model"] = "ArchaeologicalSite" - ids = list(get_contextrecord( - None, query=query, return_query=True).values_list( - "id", flat=True)) + ids = list( + get_contextrecord(None, query=query, return_query=True).values_list( + "id", flat=True + ) + ) if not ids: - export_task.state = 'F' + export_task.state = "F" export_task.result_info = str(_("No data to export")) export_task.save() return kwargs["cr_queryset"] = ContextRecord.objects.filter(pk__in=ids) elif export_task.filter_type == "F": kwargs["info"]["query"]["model"] = "Find" - ids = list(get_find( - None, query=query, return_query=True - ).values_list("id", flat=True)) + ids = list( + get_find(None, query=query, return_query=True).values_list( + "id", flat=True + ) + ) if not ids: - export_task.state = 'F' + export_task.state = "F" export_task.result_info = str(_("No data to export")) export_task.save() return kwargs["find_queryset"] = Find.objects.filter(pk__in=ids) elif export_task.filter_type == "W": kwargs["info"]["query"]["model"] = "Warehouse" - ids = list(get_warehouse( - None, query=query, return_query=True - ).values_list("id", flat=True)) + ids = list( + get_warehouse(None, query=query, return_query=True).values_list( + "id", flat=True + ) + ) if not ids: - export_task.state = 'F' + export_task.state = "F" export_task.result_info = str(_("No data to export")) export_task.save() return - kwargs["warehouse_queryset"] = Warehouse.objects.filter( - pk__in=ids) + kwargs["warehouse_queryset"] = Warehouse.objects.filter(pk__in=ids) kwargs["info"]["geo"] = export_task.geo if not export_task.geo: kwargs["no_geo"] = True @@ -160,7 +177,7 @@ def launch_export(export_task_id): export_task.result.save(archive_name.split(os.sep)[-1], File(result)) os.remove(archive_name) export_task.finished_date = datetime.datetime.now() - export_task.state = 'F' + export_task.state = "F" export_task.result_info = str(_("Export finished")) export_task.save() @@ -168,6 +185,7 @@ def launch_export(export_task_id): def load_towns(): # TODO: remove? from geodjangofla.models import Commune + q = None for dpt_number in settings.ISHTAR_DPTS: query = Q(insee_com__istartswith=dpt_number) @@ -183,10 +201,10 @@ def load_towns(): for town in q.all(): surface = town.superficie or 0 surface = surface * 10000 - defaults = {'name':town.nom_comm, 'surface':surface, - 'center':town.centroid} - town, created = Town.objects.get_or_create(numero_insee=town.insee_com, - defaults=defaults) + defaults = {"name": town.nom_comm, "surface": surface, "center": town.centroid} + town, created = Town.objects.get_or_create( + numero_insee=town.insee_com, defaults=defaults + ) if created: nb += 1 else: @@ -196,6 +214,7 @@ def load_towns(): town.save() return nb, updated + def update_towns(): # TODO: remove? nb, updated = 0, 0 @@ -203,15 +222,16 @@ def update_towns(): q = Town.objects.filter(numero_insee__isnull=False) total = q.count() for idx, town in enumerate(q.all()): - sys.stdout.write('\rProcessing... %s/%d' % ( - str(idx+1).zfill(len(str(total))), total)) + sys.stdout.write( + "\rProcessing... %s/%d" % (str(idx + 1).zfill(len(str(total))), total) + ) if len(town.numero_insee) < 2: continue dpt_code = town.numero_insee[:2] - if dpt_code.startswith('9') and int(dpt_code) > 95: + if dpt_code.startswith("9") and int(dpt_code) > 95: dpt_code = town.numero_insee[:3] if dpt_code not in dpts: - sys.stdout.write('Missing department with INSEE code: %s' % dpt_code) + sys.stdout.write("Missing department with INSEE code: %s" % dpt_code) continue if town.departement == dpts[dpt_code]: continue @@ -221,5 +241,5 @@ def update_towns(): nb += 1 town.departement = dpts[dpt_code] town.save() - sys.stdout.write('\n') + sys.stdout.write("\n") return nb, updated diff --git a/ishtar_common/tests.py b/ishtar_common/tests.py index 20f87b3bc..85d658160 100644 --- a/ishtar_common/tests.py +++ b/ishtar_common/tests.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2015-2017 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> @@ -53,101 +53,109 @@ from django.test.runner import DiscoverRunner from ishtar_common import models, models_common from ishtar_common import views from ishtar_common.apps import admin_site -from ishtar_common.serializers import type_serialization, \ - SERIALIZATION_VERSION, \ - restore_serialized, conf_serialization, CONF_MODEL_LIST, \ - importer_serialization, IMPORT_MODEL_LIST, geo_serialization, \ - GEO_MODEL_LIST, directory_serialization, DIRECTORY_MODEL_LIST, \ - document_serialization, get_type_models, full_serialization +from ishtar_common.serializers import ( + type_serialization, + SERIALIZATION_VERSION, + restore_serialized, + conf_serialization, + CONF_MODEL_LIST, + importer_serialization, + IMPORT_MODEL_LIST, + geo_serialization, + GEO_MODEL_LIST, + directory_serialization, + DIRECTORY_MODEL_LIST, + document_serialization, + get_type_models, + full_serialization, +) from archaeological_operations.serializers import OPERATION_MODEL_LIST from archaeological_context_records.serializers import CR_MODEL_LIST from archaeological_finds.serializers import FIND_MODEL_LIST from archaeological_warehouse.serializers import WAREHOUSE_MODEL_LIST from ishtar_common.serializers_utils import serialization_info -from ishtar_common.utils import post_save_geo, update_data, move_dict_data, \ - rename_and_simplify_media_name, try_fix_file +from ishtar_common.utils import ( + post_save_geo, + update_data, + move_dict_data, + rename_and_simplify_media_name, + try_fix_file, +) from ishtar_common.tasks import launch_export from ishtar_common import utils_secretary COMMON_FIXTURES = [ - settings.ROOT_PATH + '../fixtures/initial_data-auth-fr.json', - settings.ROOT_PATH + '../ishtar_common/fixtures/initial_data-fr.json', - settings.ROOT_PATH + - '../ishtar_common/fixtures/initial_spatialrefsystem-fr.json', - settings.ROOT_PATH + - '../ishtar_common/fixtures/initial_importtypes-fr.json', - ] + settings.ROOT_PATH + "../fixtures/initial_data-auth-fr.json", + settings.ROOT_PATH + "../ishtar_common/fixtures/initial_data-fr.json", + settings.ROOT_PATH + "../ishtar_common/fixtures/initial_spatialrefsystem-fr.json", + settings.ROOT_PATH + "../ishtar_common/fixtures/initial_importtypes-fr.json", +] OPERATION_FIXTURES = COMMON_FIXTURES + [ - settings.ROOT_PATH + - '../archaeological_operations/fixtures/initial_data-fr.json', - settings.ROOT_PATH + - '../archaeological_operations/fixtures/initial_data_relation_type_norel-fr.json', - settings.ROOT_PATH + - '../archaeological_operations/fixtures/initial_data_relation_type-fr.json', + settings.ROOT_PATH + "../archaeological_operations/fixtures/initial_data-fr.json", + settings.ROOT_PATH + + "../archaeological_operations/fixtures/initial_data_relation_type_norel-fr.json", + settings.ROOT_PATH + + "../archaeological_operations/fixtures/initial_data_relation_type-fr.json", ] -OPERATION_TOWNS_FIXTURES = \ - OPERATION_FIXTURES + \ - [settings.ROOT_PATH + '../ishtar_common/fixtures/test_towns.json'] +OPERATION_TOWNS_FIXTURES = OPERATION_FIXTURES + [ + settings.ROOT_PATH + "../ishtar_common/fixtures/test_towns.json" +] FILE_FIXTURES = OPERATION_FIXTURES + [ - settings.ROOT_PATH + - '../archaeological_files/fixtures/initial_data-fr.json'] + settings.ROOT_PATH + "../archaeological_files/fixtures/initial_data-fr.json" +] FILE_TOWNS_FIXTURES = OPERATION_TOWNS_FIXTURES + [ - settings.ROOT_PATH + - '../archaeological_files/fixtures/initial_data-fr.json'] + settings.ROOT_PATH + "../archaeological_files/fixtures/initial_data-fr.json" +] CONTEXT_RECORD_FIXTURES = FILE_FIXTURES + [ - settings.ROOT_PATH + - '../archaeological_context_records/fixtures/initial_data-fr.json', - settings.ROOT_PATH + - '../archaeological_context_records/fixtures/initial_data_relation_type_norel-fr.json', - settings.ROOT_PATH + - '../archaeological_context_records/fixtures/initial_data_relation_type-fr.json', - ] + settings.ROOT_PATH + + "../archaeological_context_records/fixtures/initial_data-fr.json", + settings.ROOT_PATH + + "../archaeological_context_records/fixtures/initial_data_relation_type_norel-fr.json", + settings.ROOT_PATH + + "../archaeological_context_records/fixtures/initial_data_relation_type-fr.json", +] CONTEXT_RECORD_TOWNS_FIXTURES = FILE_TOWNS_FIXTURES + [ - settings.ROOT_PATH + - '../archaeological_context_records/fixtures/initial_data-fr.json', - settings.ROOT_PATH + - '../archaeological_context_records/fixtures/initial_data_relation_type_norel-fr.json', - settings.ROOT_PATH + - '../archaeological_context_records/fixtures/initial_data_relation_type-fr.json', - ] + settings.ROOT_PATH + + "../archaeological_context_records/fixtures/initial_data-fr.json", + settings.ROOT_PATH + + "../archaeological_context_records/fixtures/initial_data_relation_type_norel-fr.json", + settings.ROOT_PATH + + "../archaeological_context_records/fixtures/initial_data_relation_type-fr.json", +] FIND_FIXTURES = CONTEXT_RECORD_FIXTURES + [ - settings.ROOT_PATH + - '../archaeological_finds/fixtures/initial_data-fr.json', - ] + settings.ROOT_PATH + "../archaeological_finds/fixtures/initial_data-fr.json", +] FIND_TOWNS_FIXTURES = CONTEXT_RECORD_TOWNS_FIXTURES + [ - settings.ROOT_PATH + - '../archaeological_finds/fixtures/initial_data-fr.json', - ] + settings.ROOT_PATH + "../archaeological_finds/fixtures/initial_data-fr.json", +] WAREHOUSE_FIXTURES = FIND_FIXTURES + [ - settings.ROOT_PATH + - '../archaeological_warehouse/fixtures/initial_data-fr.json', - ] + settings.ROOT_PATH + "../archaeological_warehouse/fixtures/initial_data-fr.json", +] def create_superuser(): - username = 'username4277' - password = 'dcbqj756456!@%' + username = "username4277" + password = "dcbqj756456!@%" q = User.objects.filter(username=username) if q.count(): return username, password, q.all()[0] - user = User.objects.create_superuser(username, "nomail@nomail.com", - password) + user = User.objects.create_superuser(username, "nomail@nomail.com", password) user.set_password(password) user.save() return username, password, user -def create_user(username='username678', password='dcbqj756aaa456!@%'): +def create_user(username="username678", password="dcbqj756aaa456!@%"): q = User.objects.filter(username=username) if q.count(): return username, password, q.all()[0] @@ -163,62 +171,31 @@ class TestCase(BaseTestCase): class UtilsTest(TestCase): def test_update_data(self): - data_1 = { - 'old': - {'youpi': - {'plouf': 'tralalalère'}} - } - data_2 = { - 'tsoin_tsoin': 'hop', - 'old': - {'hoppy': 'hop'} - } + data_1 = {"old": {"youpi": {"plouf": "tralalalère"}}} + data_2 = {"tsoin_tsoin": "hop", "old": {"hoppy": "hop"}} expected = { - 'tsoin_tsoin': 'hop', - 'old': - {'youpi': - {'plouf': 'tralalalère'}, - 'hoppy': 'hop'} + "tsoin_tsoin": "hop", + "old": {"youpi": {"plouf": "tralalalère"}, "hoppy": "hop"}, } res = update_data(data_1, data_2) self.assertEqual(res, expected) def test_move_dict_data(self): - data = { - 'old': {'daté': "value"} - } - expected = { - 'old': {'date': "value"} - } - res = move_dict_data( - data, 'data__old__daté', "data__old__date") + data = {"old": {"daté": "value"}} + expected = {"old": {"date": "value"}} + res = move_dict_data(data, "data__old__daté", "data__old__date") self.assertEqual(res, expected) - data = { - '': {'hop': "value"} - } - expected = { - 'hop': "value", - '': {} - } - res = move_dict_data(data, 'data____hop', "data__hop") + data = {"": {"hop": "value"}} + expected = {"hop": "value", "": {}} + res = move_dict_data(data, "data____hop", "data__hop") self.assertEqual(res, expected) - data = { - 'old': { - 'traitement': { - 'constat_état': 'aaa' - } - } - } - expected = { - 'old': { - 'traitement': { - 'constat_etat': 'aaa' - } - } - } - res = move_dict_data(data, - 'data__old__traitement__constat_état', - 'data__old__traitement__constat_etat') + data = {"old": {"traitement": {"constat_état": "aaa"}}} + expected = {"old": {"traitement": {"constat_etat": "aaa"}}} + res = move_dict_data( + data, + "data__old__traitement__constat_état", + "data__old__traitement__constat_etat", + ) self.assertEqual(res, expected) def test_tinyfy_url(self): @@ -235,44 +212,44 @@ class UtilsTest(TestCase): ty = models.TinyUrl.objects.get(id=db_id) self.assertEqual(base_url, ty.link) c = Client() - response = c.get(reverse('tiny-redirect', args=[short_id])) - self.assertEqual(response['Location'], base_url) + response = c.get(reverse("tiny-redirect", args=[short_id])) + self.assertEqual(response["Location"], base_url) self.assertEqual(response.status_code, 302) class SearchText: - SEARCH_URL = '' + SEARCH_URL = "" def _test_search(self, client, result, context=""): assert self.SEARCH_URL for q, expected_result in result: - search = {'search_vector': q} + search = {"search_vector": q} response = client.get(reverse(self.SEARCH_URL), search) self.assertEqual(response.status_code, 200) res = json.loads(response.content.decode()) - msg = "{} result(s) where expected for search: {} - found {}" \ - "".format(expected_result, q, res['recordsTotal']) + msg = "{} result(s) where expected for search: {} - found {}" "".format( + expected_result, q, res["recordsTotal"] + ) if context: msg = context + " - " + msg - self.assertEqual(res['recordsTotal'], expected_result, - msg=msg) + self.assertEqual(res["recordsTotal"], expected_result, msg=msg) class CommandsTestCase(TestCase): - fixtures = [settings.ROOT_PATH + - '../ishtar_common/fixtures/test_towns.json'] + fixtures = [settings.ROOT_PATH + "../ishtar_common/fixtures/test_towns.json"] def test_clean_ishtar(self): """ Clean ishtar db """ from archaeological_operations.models import Parcel + p = Parcel.objects.create( - town=models.Town.objects.create(name='test', numero_insee='25000'), + town=models.Town.objects.create(name="test", numero_insee="25000"), ) parcel_nb = Parcel.objects.count() out = StringIO() - call_command('clean_ishtar', stdout=out) + call_command("clean_ishtar", stdout=out) # no operation or file attached - the parcel should have disappear self.assertEqual(parcel_nb - 1, Parcel.objects.count()) self.assertEqual(Parcel.objects.filter(pk=p.pk).count(), 0) @@ -281,56 +258,75 @@ class CommandsTestCase(TestCase): q = models.Town.objects town_nb = q.count() out = StringIO() - call_command('import_geofla_csv', - settings.ROOT_PATH + - '../ishtar_common/tests/geofla-test.csv', '--quiet', - stdout=out) + call_command( + "import_geofla_csv", + settings.ROOT_PATH + "../ishtar_common/tests/geofla-test.csv", + "--quiet", + stdout=out, + ) self.assertEqual(town_nb + 9, models.Town.objects.count()) - call_command('import_geofla_csv', - settings.ROOT_PATH + - '../ishtar_common/tests/geofla-test.csv', '--quiet', - stdout=out) + call_command( + "import_geofla_csv", + settings.ROOT_PATH + "../ishtar_common/tests/geofla-test.csv", + "--quiet", + stdout=out, + ) # no new town self.assertEqual(town_nb + 9, models.Town.objects.count()) def test_import_insee(self): q = models.Town.objects town_nb = q.count() - first, union_start, union_end = '', '', [] + first, union_start, union_end = "", "", [] new_nums = [] for idx, town in enumerate(q.all()): x1 = float(idx) / 10 if not x1: x1 = 0 x2 = float(idx) / 10 + 0.1 - l = 'MULTIPOLYGON((({x1} 0.1,{x2} 0.1,{x2} 0,{x1} 0,' \ - '{x1} 0.1)))'.format(x1=x1, x2=x2) + l = "MULTIPOLYGON((({x1} 0.1,{x2} 0.1,{x2} 0,{x1} 0," "{x1} 0.1)))".format( + x1=x1, x2=x2 + ) if union_start: union_start += ", " else: - first = '{x1} 0.1'.format(x1=x1) - union_start += '{x2} 0.1'.format(x1=x1, x2=x2) - union_end.append('{x2} 0'.format(x1=x1, x2=x2)) + first = "{x1} 0.1".format(x1=x1) + union_start += "{x2} 0.1".format(x1=x1, x2=x2) + union_end.append("{x2} 0".format(x1=x1, x2=x2)) town.limit = l town.year = 1792 new_nums.append("{}-{}".format(town.numero_insee, town.year)) town.save() - union = 'MULTIPOLYGON (((' + first + ", " + union_start + \ - ", " + ", ".join(reversed(union_end)) + ", 0 0, " + first + ")))" + union = ( + "MULTIPOLYGON (((" + + first + + ", " + + union_start + + ", " + + ", ".join(reversed(union_end)) + + ", 0 0, " + + first + + ")))" + ) out = StringIO() - call_command('import_insee_comm_csv', - settings.ROOT_PATH + - '../ishtar_common/tests/insee-test.csv', '--quiet', - stdout=out) + call_command( + "import_insee_comm_csv", + settings.ROOT_PATH + "../ishtar_common/tests/insee-test.csv", + "--quiet", + stdout=out, + ) self.assertEqual(town_nb + 1, models.Town.objects.count()) - new = models.Town.objects.order_by('-pk').all()[0] + new = models.Town.objects.order_by("-pk").all()[0] self.assertEqual(new.parents.count(), 2) self.assertEqual(new.limit.wkt, union) - call_command('import_insee_comm_csv', '--quiet', - settings.ROOT_PATH + - '../ishtar_common/tests/insee-test.csv', stdout=out) + call_command( + "import_insee_comm_csv", + "--quiet", + settings.ROOT_PATH + "../ishtar_common/tests/insee-test.csv", + stdout=out, + ) # no new town self.assertEqual(town_nb + 1, models.Town.objects.count()) @@ -342,8 +338,16 @@ class WizardTestFormData(object): """ Test set to simulate wizard steps """ - def __init__(self, name, form_datas=None, ignored=None, pre_tests=None, - extra_tests=None, error_expected=None): + + def __init__( + self, + name, + form_datas=None, + ignored=None, + pre_tests=None, + extra_tests=None, + error_expected=None, + ): """ :param name: explicit name of the test :param form_datas: dict with data for each step - dict key are wizard @@ -392,7 +396,7 @@ class WizardTestFormData(object): Initialisations before the wizard. """ - suffix = '-' + test_object.url_name + suffix = "-" + test_object.url_name # if form names are defined without url_name fix it for form_name in list(self.form_datas.keys()): if suffix in form_name: @@ -412,7 +416,6 @@ class WizardTestFormData(object): class TimedTextTestResult(TextTestResult): - def __init__(self, *args, **kwargs): super(TimedTextTestResult, self).__init__(*args, **kwargs) self.clocks = {} @@ -430,7 +433,7 @@ class TimedTextTestResult(TextTestResult): if self.showAll: self.stream.writeln("OK (%.6fs)" % (time() - self.clocks[test])) elif self.dots: - self.stream.write('.') + self.stream.write(".") self.stream.flush() @@ -444,20 +447,19 @@ class ManagedModelTestRunner(DiscoverRunner): project managed for the duration of the test run, so that one doesn't need to execute the SQL manually to create them. """ + test_runner = TimedTextTestRunner def setup_test_environment(self, *args, **kwargs): from django.apps import apps - self.unmanaged_models = [m for m in apps.get_models() - if not m._meta.managed] + + self.unmanaged_models = [m for m in apps.get_models() if not m._meta.managed] for m in self.unmanaged_models: m._meta.managed = True - super(ManagedModelTestRunner, self).setup_test_environment(*args, - **kwargs) + super(ManagedModelTestRunner, self).setup_test_environment(*args, **kwargs) def teardown_test_environment(self, *args, **kwargs): - super(ManagedModelTestRunner, self).teardown_test_environment(*args, - **kwargs) + super(ManagedModelTestRunner, self).teardown_test_environment(*args, **kwargs) # reset unmanaged models for m in self.unmanaged_models: m._meta.managed = False @@ -466,7 +468,7 @@ class ManagedModelTestRunner(DiscoverRunner): class WizardTest(object): url_name = None url_uri = None - wizard_name = '' + wizard_name = "" steps = None condition_dict = None form_datas = [] @@ -479,8 +481,7 @@ class WizardTest(object): self.username, self.password, self.user = create_superuser() def pre_wizard(self): - self.client.login(**{'username': self.username, - 'password': self.password}) + self.client.login(**{"username": self.username, "password": self.password}) def post_wizard(self): pass @@ -491,22 +492,24 @@ class WizardTest(object): def check_response(self, response, current_step, data_idx): if "errorlist" in response.content.decode(): soup = Soup(response.content.decode(), "lxml") - errorlist = soup.findAll( - "ul", {"class": "errorlist"}) + errorlist = soup.findAll("ul", {"class": "errorlist"}) errors = [] for li in errorlist: lbl = li.findParent().findParent().findChild().text errors.append("{} - {}".format(lbl, li.text)) - raise ValidationError("Errors: {} on {} - dataset {}.".format( - " ".join(errors), current_step, data_idx + 1)) + raise ValidationError( + "Errors: {} on {} - dataset {}.".format( + " ".join(errors), current_step, data_idx + 1 + ) + ) - def wizard_post(self, client, url, current_step, form_data=None, - follow=True, extra_data=None): + def wizard_post( + self, client, url, current_step, form_data=None, follow=True, extra_data=None + ): if not url: url = reverse(self.url_name) data = { - '{}{}-current_step'.format(self.url_name, - self.wizard_name): [current_step], + "{}{}-current_step".format(self.url_name, self.wizard_name): [current_step], } if not form_data: form_data = [] @@ -515,34 +518,36 @@ class WizardTest(object): if type(form_data) in (list, tuple): # is a formset for d_idx, item in enumerate(form_data): for k in item: - data['{}-{}-{}'.format( - current_step, d_idx, k)] = item[k] + data["{}-{}-{}".format(current_step, d_idx, k)] = item[k] else: for k in form_data: - data['{}-{}'.format(current_step, k)] = form_data[k] + data["{}-{}".format(current_step, k)] = form_data[k] if extra_data: data.update(extra_data) try: response = client.post(url, data, follow=follow) except ValidationError as e: - msg = "Errors: {} on {}. On \"ManagementForm data is " \ - "missing or...\" error verify the wizard_name or " \ - "step name".format(" - ".join(e.messages), - current_step) + msg = ( + 'Errors: {} on {}. On "ManagementForm data is ' + 'missing or..." error verify the wizard_name or ' + "step name".format(" - ".join(e.messages), current_step) + ) raise ValidationError(msg) return response - def wizard_post_test(self, idx, data_idx, url, current_step, form_data, - test_form_data, ignored): - next_form_is_checked = len(self.steps) > idx + 1 and \ - self.steps[idx + 1][0] not in ignored + def wizard_post_test( + self, idx, data_idx, url, current_step, form_data, test_form_data, ignored + ): + next_form_is_checked = ( + len(self.steps) > idx + 1 and self.steps[idx + 1][0] not in ignored + ) data = [] if current_step in form_data: data = form_data[current_step] response = self.wizard_post( - self.client, url, current_step, data, - not next_form_is_checked) + self.client, url, current_step, data, not next_form_is_checked + ) if current_step == test_form_data.error_expected: with self.assertRaises(ValidationError): @@ -554,23 +559,20 @@ class WizardTest(object): next_form = self.steps[idx + 1][0] self.assertRedirects( response, - '/{}/{}'.format(self.url_uri, next_form), + "/{}/{}".format(self.url_uri, next_form), msg_prefix="Dataset n{} Redirection to {} has failed - " - "Error on previous form ({})?".format( - data_idx + 1, next_form, current_step) + "Error on previous form ({})?".format( + data_idx + 1, next_form, current_step + ), ) if idx == len(self.steps) - 1: # last form if not self.redirect_url: - redirect_url = '/{}/done'.format(self.url_uri) + redirect_url = "/{}/done".format(self.url_uri) else: - dct = { - "url_name": self.url_name, - "url_uri": self.url_uri - } - form_key = 'selec-' + self.url_name - if form_key in form_data and self.current_id_key in form_data[ - form_key]: + dct = {"url_name": self.url_name, "url_uri": self.url_uri} + form_key = "selec-" + self.url_name + if form_key in form_data and self.current_id_key in form_data[form_key]: dct["current_id"] = form_data[form_key][self.current_id_key] if self.model: q = self.model.objects @@ -601,35 +603,37 @@ class WizardTest(object): elif self.test_back and not back_tested: # test going back on a form response = self.wizard_post( - self.client, url, current_step, None, - extra_data={"form_prev_step": previous_step} + self.client, + url, + current_step, + None, + extra_data={"form_prev_step": previous_step}, ) self.assertEqual(response.status_code, 200) back_tested = True response = self.wizard_post_test( - idx, data_idx, url, current_step, form_data, test_form_data, - ignored) + idx, data_idx, url, current_step, form_data, test_form_data, ignored + ) test_form_data.tests(self, response) self.post_wizard() class CacheTest(TestCase): - fixtures = [settings.ROOT_PATH + - '../fixtures/initial_data-auth-fr.json', - settings.ROOT_PATH + - '../ishtar_common/fixtures/initial_data-fr.json',] + fixtures = [ + settings.ROOT_PATH + "../fixtures/initial_data-auth-fr.json", + settings.ROOT_PATH + "../ishtar_common/fixtures/initial_data-fr.json", + ] def test_add(self): models.OrganizationType.refresh_cache() - cached = models.OrganizationType.get_cache('test') + cached = models.OrganizationType.get_cache("test") self.assertEqual(cached, None) - orga = models.OrganizationType.objects.create( - txt_idx='test', label='testy') - cached = models.OrganizationType.get_cache('test') + orga = models.OrganizationType.objects.create(txt_idx="test", label="testy") + cached = models.OrganizationType.get_cache("test") self.assertEqual(cached.pk, orga.pk) - orga.txt_idx = 'testy' + orga.txt_idx = "testy" orga.save() - cached = models.OrganizationType.get_cache('testy') + cached = models.OrganizationType.get_cache("testy") self.assertEqual(cached.pk, orga.pk) def test_list(self): @@ -637,30 +641,30 @@ class CacheTest(TestCase): types = models.OrganizationType.get_types() # only empty self.assertTrue(len(types), 1) - org = models.OrganizationType.objects.create( - txt_idx='test', label='testy') - types = [ - str(lbl) for idx, lbl in models.OrganizationType.get_types()] - self.assertTrue('testy' in types) + org = models.OrganizationType.objects.create(txt_idx="test", label="testy") + types = [str(lbl) for idx, lbl in models.OrganizationType.get_types()] + self.assertTrue("testy" in types) org.delete() - types = [ - str(lbl) for idx, lbl in models.OrganizationType.get_types()] - self.assertFalse('testy' in types) + types = [str(lbl) for idx, lbl in models.OrganizationType.get_types()] + self.assertFalse("testy" in types) def test_menu_cache(self): - admin_username = 'username4277' - admin_password = 'dcbqj756456!@%' - User.objects.create_superuser(admin_username, "nomail@nomail.com", - admin_password) - readonly_user = 'username4456' - readonly_password = 'xshqu744456!@%' - user = User.objects.create_user(readonly_user, "nomail@nomail.com", - readonly_password) + admin_username = "username4277" + admin_password = "dcbqj756456!@%" + User.objects.create_superuser( + admin_username, "nomail@nomail.com", admin_password + ) + readonly_user = "username4456" + readonly_password = "xshqu744456!@%" + user = User.objects.create_user( + readonly_user, "nomail@nomail.com", readonly_password + ) ishtuser = models.IshtarUser.objects.get(user_ptr=user) models.UserProfile.objects.get_or_create( - current=True, person=ishtuser.person, - profile_type=models.ProfileType.objects.get( - txt_idx='reader_access')) + current=True, + person=ishtuser.person, + profile_type=models.ProfileType.objects.get(txt_idx="reader_access"), + ) c = Client() c.login(username=admin_username, password=admin_password) @@ -680,17 +684,22 @@ class CacheTest(TestCase): class GenericSerializationTest: def create_document_default(self): - image_path = os.path.join(settings.ROOT_PATH, "..", "ishtar_common", - "tests", "test.png") + image_path = os.path.join( + settings.ROOT_PATH, "..", "ishtar_common", "tests", "test.png" + ) self.documents = [] for idx in range(12): - self.documents.append(models.Document.objects.create( - title="Test{}".format(idx), - associated_file=SimpleUploadedFile( - 'test.txt', b'no real content'), - image=SimpleUploadedFile( - name='test.png', content=open(image_path, 'rb').read(), - content_type='image/png'))) + self.documents.append( + models.Document.objects.create( + title="Test{}".format(idx), + associated_file=SimpleUploadedFile("test.txt", b"no real content"), + image=SimpleUploadedFile( + name="test.png", + content=open(image_path, "rb").read(), + content_type="image/png", + ), + ) + ) def generic_serialization_test(self, serialize, no_test=False, kwargs=None): if not kwargs: @@ -703,11 +712,11 @@ class GenericSerializationTest: module_name, model_name = k.split("__") if module_name == "django": if model_name in ("Group", "Permission"): - module = importlib.import_module( - "django.contrib.auth.models") + module = importlib.import_module("django.contrib.auth.models") elif model_name in ("ContentType",): module = importlib.import_module( - "django.contrib.contenttypes.models") + "django.contrib.contenttypes.models" + ) else: return else: @@ -720,62 +729,87 @@ class GenericSerializationTest: result = json.loads(json_result[key]) serialization_count = len(result) # all serialization have to be tested - self.assertTrue(serialization_count, - msg="No data to test for {}".format(key)) + self.assertTrue( + serialization_count, msg="No data to test for {}".format(key) + ) # only "natural" serialization self.assertNotIn( - "pk", result[0], - msg="Serialization for {} do not use natural keys".format(key)) + "pk", + result[0], + msg="Serialization for {} do not use natural keys".format(key), + ) self.assertNotIn( - "id", result[0], - msg="Serialization for {} do not use natural keys".format(key)) + "id", + result[0], + msg="Serialization for {} do not use natural keys".format(key), + ) # has to be at least equal (can be superior for model with # recursion) self.assertTrue( serialization_count >= current_count, msg="Serialization for model {}.{} failed. {} serialized {} " - "expected".format(module.__name__, model_name, - serialization_count, current_count)) + "expected".format( + module.__name__, model_name, serialization_count, current_count + ), + ) return json_result - def generic_restore_test_genzip(self, model_list, serialization, - kwargs=None): + def generic_restore_test_genzip(self, model_list, serialization, kwargs=None): current_number = {} for model in model_list: - current_number[(model.__module__, model.__name__)] = \ - model.objects.count() + current_number[(model.__module__, model.__name__)] = model.objects.count() if not kwargs: kwargs = {} kwargs["archive"] = True zip_filename = serialization(**kwargs) return current_number, zip_filename - def generic_restore_test(self, zip_filename, current_number, model_list, - release_locks=False, delete_existing=True): - restore_serialized(zip_filename, delete_existing=delete_existing, - release_locks=release_locks) + def generic_restore_test( + self, + zip_filename, + current_number, + model_list, + release_locks=False, + delete_existing=True, + ): + restore_serialized( + zip_filename, delete_existing=delete_existing, release_locks=release_locks + ) for model in model_list: previous_nb = current_number[(model.__module__, model.__name__)] current_nb = model.objects.count() self.assertEqual( - previous_nb, current_nb, + previous_nb, + current_nb, msg="Restore for model {}.{} failed. Initial: {}, restored: " - "{}.".format(model.__module__, model.__name__, - previous_nb, current_nb)) + "{}.".format(model.__module__, model.__name__, previous_nb, current_nb), + ) class SerializationTest(GenericSerializationTest, TestCase): fixtures = COMMON_FIXTURES + WAREHOUSE_FIXTURES def create_types(self): - from archaeological_finds.models import MaterialTypeQualityType, \ - ObjectTypeQualityType, AlterationType, AlterationCauseType, \ - TreatmentEmergencyType, CommunicabilityType + from archaeological_finds.models import ( + MaterialTypeQualityType, + ObjectTypeQualityType, + AlterationType, + AlterationCauseType, + TreatmentEmergencyType, + CommunicabilityType, + ) from archaeological_operations.models import CulturalAttributionType - for model in (models.LicenseType, MaterialTypeQualityType, - ObjectTypeQualityType, AlterationType, - AlterationCauseType, TreatmentEmergencyType, - CommunicabilityType, CulturalAttributionType): + + for model in ( + models.LicenseType, + MaterialTypeQualityType, + ObjectTypeQualityType, + AlterationType, + AlterationCauseType, + TreatmentEmergencyType, + CommunicabilityType, + CulturalAttributionType, + ): model.objects.create(txt_idx="test", label="Test") def test_type_serialization(self): @@ -787,9 +821,9 @@ class SerializationTest(GenericSerializationTest, TestCase): models.get_current_profile(force=True) # create a default profile models.GlobalVar.objects.create(slug="test") cform = models.CustomForm.objects.create( - name="Test", form='ishtar_common.forms.TestForm') - models.ExcludedField.objects.create(custom_form=cform, - field="ExcludedField") + name="Test", form="ishtar_common.forms.TestForm" + ) + models.ExcludedField.objects.create(custom_form=cform, field="ExcludedField") CT = ContentType.objects.get_for_model(models.OrganizationType) models.JsonDataSection.objects.create( content_type=CT, @@ -805,8 +839,10 @@ class SerializationTest(GenericSerializationTest, TestCase): klass="ishtar_common.models.Organization" ) values["document_template"] = models.DocumentTemplate.objects.create( - name="Test", slug="test", associated_model=mod, - template=SimpleUploadedFile('test.txt', b'no real content') + name="Test", + slug="test", + associated_model=mod, + template=SimpleUploadedFile("test.txt", b"no real content"), ) return values @@ -825,20 +861,21 @@ class SerializationTest(GenericSerializationTest, TestCase): def create_geo_default(self): s = models_common.State.objects.create(label="test", number="999") - d = models.Department.objects.create(label="test", number="999", - state=s) + d = models.Department.objects.create(label="test", number="999", state=s) t1 = models.Town.objects.create( name="Test town", center="SRID=4326;POINT(-44.3 60.1)", - numero_insee="12345", departement=d + numero_insee="12345", + departement=d, ) t2 = models.Town.objects.create( name="Test town 2", center="SRID=4326;POINT(-44.2 60.2)", - numero_insee="12346", departement=d + numero_insee="12346", + departement=d, ) t2.children.add(t1) - a = models.Area.objects.create(label="Test", txt_idx='test') + a = models.Area.objects.create(label="Test", txt_idx="test") a.towns.add(t1) def test_geo_serialization(self): @@ -847,13 +884,12 @@ class SerializationTest(GenericSerializationTest, TestCase): def create_directory_default(self): org = models.Organization.objects.create( - name="Test", - organization_type=models.OrganizationType.objects.all()[0]) - person = models.Person.objects.create( - name="Test", attached_to=org + name="Test", organization_type=models.OrganizationType.objects.all()[0] ) + person = models.Person.objects.create(name="Test", attached_to=org) models.Author.objects.create( - person=person, author_type=models.AuthorType.objects.all()[0]) + person=person, author_type=models.AuthorType.objects.all()[0] + ) def test_directory_serialization(self): self.create_directory_default() @@ -861,17 +897,29 @@ class SerializationTest(GenericSerializationTest, TestCase): def create_document_default(self): super(SerializationTest, self).create_document_default() - from archaeological_operations.models import Operation, \ - ArchaeologicalSite, OperationType + from archaeological_operations.models import ( + Operation, + ArchaeologicalSite, + OperationType, + ) from archaeological_context_records.models import ContextRecord from archaeological_finds.models import Find, BaseFind - from archaeological_warehouse.models import Warehouse, Container, \ - ContainerLocalisation, WarehouseDivision, WarehouseDivisionLink, \ - WarehouseType, ContainerType + from archaeological_warehouse.models import ( + Warehouse, + Container, + ContainerLocalisation, + WarehouseDivision, + WarehouseDivisionLink, + WarehouseType, + ContainerType, + ) operation_type = OperationType.objects.all()[0] - dct = {'year': 2010, 'operation_type_id': operation_type.pk, - "code_patriarche": "66666"} + dct = { + "year": 2010, + "operation_type_id": operation_type.pk, + "code_patriarche": "66666", + } operation1 = Operation.objects.create(**dct) operation1.documents.add(self.documents[0]) dct["code_patriarche"] = "66667" @@ -885,23 +933,23 @@ class SerializationTest(GenericSerializationTest, TestCase): operation2.archaeological_sites.add(site2) site2.documents.add(self.documents[3]) - dct = {'label': "Context record1", "operation": operation1} + dct = {"label": "Context record1", "operation": operation1} cr1 = ContextRecord.objects.create(**dct) cr1.documents.add(self.documents[4]) - dct = {'label': "Context record2", "operation": operation2} + dct = {"label": "Context record2", "operation": operation2} cr2 = ContextRecord.objects.create(**dct) cr2.documents.add(self.documents[5]) - dct = {'label': "Base find", "context_record": cr1} + dct = {"label": "Base find", "context_record": cr1} base_find1 = BaseFind.objects.create(**dct) - dct = {'label': "Base find2", "context_record": cr2} + dct = {"label": "Base find2", "context_record": cr2} base_find2 = BaseFind.objects.create(**dct) - dct = {'label': "Find1"} + dct = {"label": "Find1"} find1 = Find.objects.create(**dct) find1.documents.add(self.documents[6]) find1.base_finds.add(base_find1) - dct = {'label': "Find2"} + dct = {"label": "Find2"} find2 = Find.objects.create(**dct) find2.documents.add(self.documents[7]) find2.base_finds.add(base_find2) @@ -925,7 +973,7 @@ class SerializationTest(GenericSerializationTest, TestCase): container_type=ContainerType.objects.all()[0], reference="Réf1", index=1, - external_id="ref1-1" + external_id="ref1-1", ) c1.documents.add(self.documents[10]) c2 = Container.objects.create( @@ -934,7 +982,7 @@ class SerializationTest(GenericSerializationTest, TestCase): container_type=ContainerType.objects.all()[0], reference="Réf2", index=2, - external_id="ref2-2" + external_id="ref2-2", ) c2.documents.add(self.documents[11]) find1.container = c1 @@ -943,30 +991,18 @@ class SerializationTest(GenericSerializationTest, TestCase): find2.container = c2 find2.container_ref = c2 find2.save() - wd1 = WarehouseDivision.objects.create( - label="Étagère", txt_idx="etagere" - ) - wd2 = WarehouseDivision.objects.create( - label="Allée", txt_idx="allee" - ) + wd1 = WarehouseDivision.objects.create(label="Étagère", txt_idx="etagere") + wd2 = WarehouseDivision.objects.create(label="Allée", txt_idx="allee") wl1 = WarehouseDivisionLink.objects.create( warehouse=w1, container_type=ContainerType.objects.all()[0], ) wl2 = WarehouseDivisionLink.objects.create( warehouse=w2, - container_type = ContainerType.objects.all()[1], - ) - ContainerLocalisation.objects.create( - container=c1, - division=wl1, - reference="A1" - ) - ContainerLocalisation.objects.create( - container=c2, - division=wl2, - reference="A2" + container_type=ContainerType.objects.all()[1], ) + ContainerLocalisation.objects.create(container=c1, division=wl1, reference="A1") + ContainerLocalisation.objects.create(container=c2, division=wl2, reference="A2") def test_base_document_serialization(self): self.create_document_default() @@ -974,71 +1010,65 @@ class SerializationTest(GenericSerializationTest, TestCase): def test_document_serialization(self): self.create_document_default() - res = self.generic_serialization_test( - document_serialization) - docs = json.loads( - res[('documents', 'ishtar_common__Document')] - ) + res = self.generic_serialization_test(document_serialization) + docs = json.loads(res[("documents", "ishtar_common__Document")]) self.assertEqual(len(docs), 12) - from archaeological_operations.models import Operation, \ - ArchaeologicalSite - result_queryset = Operation.objects.filter( - code_patriarche="66666") + from archaeological_operations.models import Operation, ArchaeologicalSite + + result_queryset = Operation.objects.filter(code_patriarche="66666") res = self.generic_serialization_test( - document_serialization, no_test=True, - kwargs={"operation_queryset": result_queryset} - ) - docs = json.loads( - res[('documents', 'ishtar_common__Document')] + document_serialization, + no_test=True, + kwargs={"operation_queryset": result_queryset}, ) + docs = json.loads(res[("documents", "ishtar_common__Document")]) self.assertEqual(len(docs), 6) result_queryset = ArchaeologicalSite.objects.filter( - id=ArchaeologicalSite.objects.all()[0].id) - res = self.generic_serialization_test( - document_serialization, no_test=True, - kwargs={"site_queryset": result_queryset} + id=ArchaeologicalSite.objects.all()[0].id ) - docs = json.loads( - res[('documents', 'ishtar_common__Document')] + res = self.generic_serialization_test( + document_serialization, + no_test=True, + kwargs={"site_queryset": result_queryset}, ) + docs = json.loads(res[("documents", "ishtar_common__Document")]) self.assertEqual(len(docs), 6) from archaeological_context_records.models import ContextRecord + result_queryset = ContextRecord.objects.filter( - id=ContextRecord.objects.all()[0].id) - res = self.generic_serialization_test( - document_serialization, no_test=True, - kwargs={"cr_queryset": result_queryset} + id=ContextRecord.objects.all()[0].id ) - docs = json.loads( - res[('documents', 'ishtar_common__Document')] + res = self.generic_serialization_test( + document_serialization, + no_test=True, + kwargs={"cr_queryset": result_queryset}, ) + docs = json.loads(res[("documents", "ishtar_common__Document")]) self.assertEqual(len(docs), 6) from archaeological_finds.models import Find - result_queryset = Find.objects.filter( - id=Find.objects.all()[0].id) + + result_queryset = Find.objects.filter(id=Find.objects.all()[0].id) res = self.generic_serialization_test( - document_serialization, no_test=True, - kwargs={"find_queryset": result_queryset} - ) - docs = json.loads( - res[('documents', 'ishtar_common__Document')] + document_serialization, + no_test=True, + kwargs={"find_queryset": result_queryset}, ) + docs = json.loads(res[("documents", "ishtar_common__Document")]) self.assertEqual(len(docs), 6) from archaeological_warehouse.models import Warehouse - result_queryset = Warehouse.objects.filter( - id=Warehouse.objects.all()[0].id) + + result_queryset = Warehouse.objects.filter(id=Warehouse.objects.all()[0].id) res = self.generic_serialization_test( - document_serialization, no_test=True, - kwargs={"warehouse_queryset": result_queryset} - ) - docs = json.loads( - res[('documents', 'ishtar_common__Document')] + document_serialization, + no_test=True, + kwargs={"warehouse_queryset": result_queryset}, ) + docs = json.loads(res[("documents", "ishtar_common__Document")]) self.assertEqual(len(docs), 6) def test_serialization_zip(self): @@ -1067,6 +1097,7 @@ class SerializationTest(GenericSerializationTest, TestCase): def test_type_restore(self): from archaeological_context_records.models import RelationType as CRRT from archaeological_operations.models import RelationType as OperationRT + cr_rel_type_nb = CRRT.objects.count() ope_rel_type_nb = OperationRT.objects.count() self.create_types() @@ -1075,75 +1106,76 @@ class SerializationTest(GenericSerializationTest, TestCase): zip_filename = type_serialization(archive=True) models.AuthorType.objects.create( - label="Am I still here", txt_idx="am-i-still-here") + label="Am I still here", txt_idx="am-i-still-here" + ) models.AuthorType.objects.filter(txt_idx="test").delete() restore_serialized(zip_filename) + self.assertEqual(models.AuthorType.objects.filter(txt_idx="test").count(), 1) self.assertEqual( - models.AuthorType.objects.filter(txt_idx="test").count(), 1) - self.assertEqual( - models.AuthorType.objects.filter(txt_idx="am-i-still-here").count(), - 1) + models.AuthorType.objects.filter(txt_idx="am-i-still-here").count(), 1 + ) self.assertEqual(cr_rel_type_nb, CRRT.objects.count()) self.assertEqual(ope_rel_type_nb, OperationRT.objects.count()) - self.assertTrue(OperationRT.objects.filter( - inverse_relation__isnull=False).count()) + self.assertTrue( + OperationRT.objects.filter(inverse_relation__isnull=False).count() + ) models.AuthorType.objects.filter(txt_idx="am-i-still-here").delete() zip_filename = type_serialization(archive=True) models.AuthorType.objects.create( - label="Am I still here", txt_idx="am-i-still-here") + label="Am I still here", txt_idx="am-i-still-here" + ) models.AuthorType.objects.filter(txt_idx="test").delete() restore_serialized(zip_filename, delete_existing=True) + self.assertEqual(models.AuthorType.objects.filter(txt_idx="test").count(), 1) self.assertEqual( - models.AuthorType.objects.filter(txt_idx="test").count(), 1) - self.assertEqual( - models.AuthorType.objects.filter(txt_idx="am-i-still-here").count(), - 0) + models.AuthorType.objects.filter(txt_idx="am-i-still-here").count(), 0 + ) self.assertEqual(cr_rel_type_nb, CRRT.objects.count()) self.assertEqual(ope_rel_type_nb, OperationRT.objects.count()) - self.assertTrue(OperationRT.objects.filter( - inverse_relation__isnull=False).count()) + self.assertTrue( + OperationRT.objects.filter(inverse_relation__isnull=False).count() + ) def test_conf_restore(self): values = self.create_default_conf() current_number, zip_filename = self.generic_restore_test_genzip( - CONF_MODEL_LIST, conf_serialization) + CONF_MODEL_LIST, conf_serialization + ) os.remove(values["document_template"].template.path) self.generic_restore_test(zip_filename, current_number, CONF_MODEL_LIST) - self.assertTrue( - os.path.isfile(values["document_template"].template.path) - ) + self.assertTrue(os.path.isfile(values["document_template"].template.path)) def test_importer_restore(self): self.create_default_importer() current_number, zip_filename = self.generic_restore_test_genzip( - IMPORT_MODEL_LIST, importer_serialization) - self.generic_restore_test(zip_filename, current_number, - IMPORT_MODEL_LIST) + IMPORT_MODEL_LIST, importer_serialization + ) + self.generic_restore_test(zip_filename, current_number, IMPORT_MODEL_LIST) def test_geo_restore(self): self.create_geo_default() self.assertTrue(models.Town.objects.get(numero_insee="12345").center) current_number, zip_filename = self.generic_restore_test_genzip( - GEO_MODEL_LIST, geo_serialization) - self.generic_restore_test(zip_filename, current_number, - GEO_MODEL_LIST) + GEO_MODEL_LIST, geo_serialization + ) + self.generic_restore_test(zip_filename, current_number, GEO_MODEL_LIST) # no geo restore self.assertFalse(models.Town.objects.get(numero_insee="12345").center) def test_directory_restore(self): self.create_directory_default() current_number, zip_filename = self.generic_restore_test_genzip( - DIRECTORY_MODEL_LIST, directory_serialization) - self.generic_restore_test(zip_filename, current_number, - DIRECTORY_MODEL_LIST) + DIRECTORY_MODEL_LIST, directory_serialization + ) + self.generic_restore_test(zip_filename, current_number, DIRECTORY_MODEL_LIST) def test_document_restore(self): self.create_document_default() current_number, zip_filename = self.generic_restore_test_genzip( - [models.Document], document_serialization) - self.generic_restore_test(zip_filename, current_number, - [models.Document]) + [models.Document], document_serialization + ) + self.generic_restore_test(zip_filename, current_number, [models.Document]) def full_create(self): self.create_types() @@ -1155,31 +1187,46 @@ class SerializationTest(GenericSerializationTest, TestCase): def test_full_restore(self): self.full_create() - model_list = get_type_models() + CONF_MODEL_LIST + IMPORT_MODEL_LIST + \ - GEO_MODEL_LIST + DIRECTORY_MODEL_LIST + OPERATION_MODEL_LIST + \ - CR_MODEL_LIST + FIND_MODEL_LIST + WAREHOUSE_MODEL_LIST + model_list = ( + get_type_models() + + CONF_MODEL_LIST + + IMPORT_MODEL_LIST + + GEO_MODEL_LIST + + DIRECTORY_MODEL_LIST + + OPERATION_MODEL_LIST + + CR_MODEL_LIST + + FIND_MODEL_LIST + + WAREHOUSE_MODEL_LIST + ) current_number, zip_filename = self.generic_restore_test_genzip( - model_list, full_serialization) - self.generic_restore_test(zip_filename, current_number, - model_list) + model_list, full_serialization + ) + self.generic_restore_test(zip_filename, current_number, model_list) def test_export_action(self): self.full_create() - model_list = get_type_models() + CONF_MODEL_LIST + IMPORT_MODEL_LIST + \ - GEO_MODEL_LIST + DIRECTORY_MODEL_LIST + OPERATION_MODEL_LIST + \ - CR_MODEL_LIST + FIND_MODEL_LIST + WAREHOUSE_MODEL_LIST + model_list = ( + get_type_models() + + CONF_MODEL_LIST + + IMPORT_MODEL_LIST + + GEO_MODEL_LIST + + DIRECTORY_MODEL_LIST + + OPERATION_MODEL_LIST + + CR_MODEL_LIST + + FIND_MODEL_LIST + + WAREHOUSE_MODEL_LIST + ) task = models.ExportTask.objects.create(state="S") launch_export(task.pk) task = models.ExportTask.objects.get(pk=task.pk) current_number = {} for model in model_list: - current_number[(model.__module__, model.__name__)] = \ - model.objects.count() - self.generic_restore_test(task.result.path, current_number, - model_list) + current_number[(model.__module__, model.__name__)] = model.objects.count() + self.generic_restore_test(task.result.path, current_number, model_list) task = models.ExportTask.objects.create( - filter_type="O", filter_text="66666", state="S") + filter_type="O", filter_text="66666", state="S" + ) current_number.update( { ("archaeological_operations.models", "ArchaeologicalSite"): 1, @@ -1195,39 +1242,38 @@ class SerializationTest(GenericSerializationTest, TestCase): ) launch_export(task.pk) task = models.ExportTask.objects.get(pk=task.pk) - self.generic_restore_test(task.result.path, current_number, - model_list) + self.generic_restore_test(task.result.path, current_number, model_list) class AccessControlTest(TestCase): def test_administrator(self): admin, created = models.PersonType.objects.get_or_create( - txt_idx='administrator', defaults={'label': 'Admin'}) - user, created = User.objects.get_or_create(username='myusername') + txt_idx="administrator", defaults={"label": "Admin"} + ) + user, created = User.objects.get_or_create(username="myusername") user.is_superuser = True user.save() - ishtar_user = models.IshtarUser.objects.get( - user_ptr__username='myusername') + ishtar_user = models.IshtarUser.objects.get(user_ptr__username="myusername") self.assertEqual( - models.UserProfile.objects.filter( - person__ishtaruser=ishtar_user, - profile_type__txt_idx='administrator' - ).count(), 1 + models.UserProfile.objects.filter( + person__ishtaruser=ishtar_user, profile_type__txt_idx="administrator" + ).count(), + 1, ) user = ishtar_user.user_ptr user.is_superuser = False user.save() self.assertEqual( models.UserProfile.objects.filter( - person__ishtaruser=ishtar_user, - profile_type__txt_idx='administrator' - ).count(), 1 + person__ishtaruser=ishtar_user, profile_type__txt_idx="administrator" + ).count(), + 1, ) # no more automatic deletion of profile for admin self.assertEqual( models.UserProfile.objects.filter( - person__ishtaruser=ishtar_user, - profile_type__txt_idx='administrator' - ).count(), 1 + person__ishtaruser=ishtar_user, profile_type__txt_idx="administrator" + ).count(), + 1, ) def test_django_admin(self): @@ -1243,7 +1289,7 @@ class AccessControlTest(TestCase): response = client.get(url) self.assertRedirects(response, "/admin/login/?next={}".format(url)) - User.objects.filter(username='myusername').update(is_staff=True) + User.objects.filter(username="myusername").update(is_staff=True) client.logout() client.login(username=username, password=password) response = client.get(url) @@ -1253,8 +1299,7 @@ class AccessControlTest(TestCase): response = client.get(url) self.assertEqual(response.status_code, 403) - user.user_permissions.add(Permission.objects.get( - codename='change_persontype')) + user.user_permissions.add(Permission.objects.get(codename="change_persontype")) client.logout() client.login(username=username, password=password) response = client.get(url) @@ -1265,76 +1310,84 @@ class UserProfileTest(TestCase): fixtures = OPERATION_FIXTURES def setUp(self): - self.password = 'mypassword' + self.password = "mypassword" self.username = "myuser" self.user = User.objects.create_superuser( - self.username, 'myemail@test.com', self.password) + self.username, "myemail@test.com", self.password + ) self.user.set_password(self.password) self.user.save() self.client = Client() self.client.login(username=self.username, password=self.password) def test_profile_edit(self): - base_url = '/profile/' + base_url = "/profile/" base_profile = self.user.ishtaruser.current_profile response = self.client.get(base_url) self.assertEqual(response.status_code, 200) response = self.client.post( - base_url, {'name': "New name", "current_profile": base_profile.pk}) + base_url, {"name": "New name", "current_profile": base_profile.pk} + ) self.assertEqual(response.status_code, 302) base_profile = models.UserProfile.objects.get(pk=base_profile.pk) - self.assertEqual( - base_profile.name, - "New name" - ) + self.assertEqual(base_profile.name, "New name") self.client.post( - base_url, {'delete_profile': True, 'name': "New name", - "current_profile": base_profile.pk}) + base_url, + { + "delete_profile": True, + "name": "New name", + "current_profile": base_profile.pk, + }, + ) self.assertEqual(response.status_code, 302) # cannot delete a profile it is the last of his kind - self.assertEqual( - self.user.ishtaruser.person.profiles.count(), - 1 - ) + self.assertEqual(self.user.ishtaruser.person.profiles.count(), 1) self.client.post( - base_url, {'name': "New name", 'duplicate_profile': True, - "current_profile": base_profile.pk}) + base_url, + { + "name": "New name", + "duplicate_profile": True, + "current_profile": base_profile.pk, + }, + ) self.assertEqual(response.status_code, 302) # duplicate - self.assertEqual( - self.user.ishtaruser.person.profiles.count(), - 2 - ) + self.assertEqual(self.user.ishtaruser.person.profiles.count(), 2) # new current profile is the duplicated new_profile = self.user.ishtaruser.current_profile base_profile = models.UserProfile.objects.get(pk=base_profile.pk) - self.assertNotEqual(base_profile.pk, - new_profile.pk) - self.assertNotEqual(base_profile.name, - new_profile.name) + self.assertNotEqual(base_profile.pk, new_profile.pk) + self.assertNotEqual(base_profile.name, new_profile.name) response = self.client.post( - base_url, {'name': "New name", "current_profile": new_profile.pk}) + base_url, {"name": "New name", "current_profile": new_profile.pk} + ) self.assertIn( - b"errorlist nonfield", response.content, - msg="An error should be isplayed as this name is already taken" + b"errorlist nonfield", + response.content, + msg="An error should be isplayed as this name is already taken", ) # the deletion can now occurs self.client.post( - base_url, {'delete_profile': True, - "current_profile": base_profile.pk}) - self.assertEqual( - self.user.ishtaruser.person.profiles.count(), - 1 + base_url, {"delete_profile": True, "current_profile": base_profile.pk} ) + self.assertEqual(self.user.ishtaruser.person.profiles.count(), 1) class AcItem: - def __init__(self, model, url, lbl_key=None, prepare_func=None, - id_key="pk", one_word_search=False, default_values=None): + def __init__( + self, + model, + url, + lbl_key=None, + prepare_func=None, + id_key="pk", + one_word_search=False, + default_values=None, + ): self.model, self.url, self.lbl_key = model, url, lbl_key self.prepare_func, self.id_key = prepare_func, id_key self.one_word_search = one_word_search @@ -1345,10 +1398,11 @@ class AcItem: class AutocompleteTestBase: def setUp(self): - self.password = 'mypassword' + self.password = "mypassword" self.username = "myuser" user = User.objects.create_superuser( - self.username, 'myemail@test.com', self.password) + self.username, "myemail@test.com", self.password + ) user.set_password(self.password) user.save() self.user = user @@ -1373,57 +1427,75 @@ class AutocompleteTestBase: item, __ = model.objects.get_or_create(**create_dict) response = self.client.get(url, {"term": search_term}) self.assertEqual( - response.status_code, 200, - msg="Status code != 200 - {}".format(url)) + response.status_code, 200, msg="Status code != 200 - {}".format(url) + ) data = json.loads(response.content.decode()) self.assertEqual( - len(data), 1, + len(data), + 1, msg="{} result for '{}' expected 1 - {}".format( - len(data), search_term, url)) + len(data), search_term, url + ), + ) self.assertEqual( - data[0]['id'], getattr(item, mdl.id_key), + data[0]["id"], + getattr(item, mdl.id_key), msg="id: {} expected {} for '{}' - {}".format( - data[0]['id'], item.pk, search_term, url)) + data[0]["id"], item.pk, search_term, url + ), + ) if mdl.one_word_search: continue search_term = "ler " + search_term response = self.client.get(url, {"term": search_term}) self.assertEqual( - response.status_code, 200, - msg="Status code != 200 when reaching {}".format(url)) + response.status_code, + 200, + msg="Status code != 200 when reaching {}".format(url), + ) data = json.loads(response.content.decode()) self.assertEqual( - len(data), 1, + len(data), + 1, msg="{} result for '{}' expected 1 - {}".format( - len(data), search_term, url)) + len(data), search_term, url + ), + ) self.assertEqual( - data[0]['id'], getattr(item, mdl.id_key), + data[0]["id"], + getattr(item, mdl.id_key), msg="id: {} expected {} for '{}' - {}".format( - data[0]['id'], item.pk, search_term, url)) + data[0]["id"], item.pk, search_term, url + ), + ) class AutocompleteTest(AutocompleteTestBase, TestCase): fixtures = OPERATION_FIXTURES models = [ - AcItem(models.User, 'autocomplete-user', "username"), - AcItem(models.User, 'autocomplete-ishtaruser', "username"), - AcItem(models.Person, 'autocomplete-person', "name"), - AcItem(models.Person, 'autocomplete-person-permissive', "name"), - AcItem(models.Town, 'autocomplete-town', "name"), - AcItem(models.Town, 'autocomplete-advanced-town', - prepare_func="create_advanced_town"), + AcItem(models.User, "autocomplete-user", "username"), + AcItem(models.User, "autocomplete-ishtaruser", "username"), + AcItem(models.Person, "autocomplete-person", "name"), + AcItem(models.Person, "autocomplete-person-permissive", "name"), + AcItem(models.Town, "autocomplete-town", "name"), + AcItem( + models.Town, + "autocomplete-advanced-town", + prepare_func="create_advanced_town", + ), AcItem(models.Department, "autocomplete-department", "label"), - AcItem(models.Author, "autocomplete-author", - prepare_func="create_author"), - AcItem(models.Organization, 'autocomplete-organization', - prepare_func="create_orga"), + AcItem(models.Author, "autocomplete-author", prepare_func="create_author"), + AcItem( + models.Organization, "autocomplete-organization", prepare_func="create_orga" + ), ] def create_advanced_town(self, base_name): town, __ = models.Town.objects.get_or_create(name=base_name) - dep, __ = models.Department.objects.get_or_create(label="Mydepartment", - number=999) + dep, __ = models.Department.objects.get_or_create( + label="Mydepartment", number=999 + ) town.departement = dep town.save() return town, "Mydepart" @@ -1431,40 +1503,53 @@ class AutocompleteTest(AutocompleteTestBase, TestCase): def create_author(self, base_name): person, __ = models.Person.objects.get_or_create(name=base_name) author, __ = models.Author.objects.get_or_create( - person=person, author_type=models.AuthorType.objects.all()[0]) + person=person, author_type=models.AuthorType.objects.all()[0] + ) return author, None def create_orga(self, base_name): orga, __ = models.Organization.objects.get_or_create( - name=base_name, - organization_type=models.OrganizationType.objects.all()[0]) + name=base_name, organization_type=models.OrganizationType.objects.all()[0] + ) return orga, None class AdminGenTypeTest(TestCase): fixtures = OPERATION_FIXTURES gen_models = [ - models.OrganizationType, models.PersonType, models.TitleType, - models.AuthorType, models.SourceType, models.OperationType, - models.SpatialReferenceSystem, models.Format, models.SupportType, + models.OrganizationType, + models.PersonType, + models.TitleType, + models.AuthorType, + models.SourceType, + models.OperationType, + models.SpatialReferenceSystem, + models.Format, + models.SupportType, ] models_with_data = gen_models + [models.ImporterModel] models = models_with_data - module_name = 'ishtar_common' + module_name = "ishtar_common" ishtar_apps = [ - 'ishtar_common', 'archaeological_files', 'archaeological_operations', - 'archaeological_context_records', 'archaeological_warehouse', - 'archaeological_finds' + "ishtar_common", + "archaeological_files", + "archaeological_operations", + "archaeological_context_records", + "archaeological_warehouse", + "archaeological_finds", + ] + readonly_models = [ + "archaeological_finds.Property", + "archaeological_finds.Treatment", + "ishtar_common.ProfileTypeSummary", ] - readonly_models = ['archaeological_finds.Property', - 'archaeological_finds.Treatment', - 'ishtar_common.ProfileTypeSummary'] def setUp(self): - self.password = 'mypassword' + self.password = "mypassword" self.username = "myuser" user = User.objects.create_superuser( - self.username, 'myemail@test.com', self.password) + self.username, "myemail@test.com", self.password + ) user.set_password(self.password) user.save() self.client = Client() @@ -1479,113 +1564,134 @@ class AdminGenTypeTest(TestCase): models.append((app, model)) for app, model in models: # quick test to verify basic access to listing - base_url = '/admin/{}/{}/'.format(app, model.__name__.lower()) + base_url = "/admin/{}/{}/".format(app, model.__name__.lower()) url = base_url response = self.client.get(url) self.assertEqual( - response.status_code, 200, - msg="Can not access admin list for {}.".format(model)) + response.status_code, + 200, + msg="Can not access admin list for {}.".format(model), + ) nb = model.objects.count() url = base_url + "add/" response = self.client.get(url) if app + "." + model.__name__ in self.readonly_models: continue self.assertEqual( - response.status_code, 200, - msg="Can not access admin add page for {}.".format(model)) + response.status_code, + 200, + msg="Can not access admin add page for {}.".format(model), + ) self.assertEqual( - nb, model.objects.count(), + nb, + model.objects.count(), msg="A ghost object have been created on access to add page " - "for {}.".format(model)) + "for {}.".format(model), + ) if nb: url = base_url + "{}/change/".format(model.objects.all()[0].pk) response = self.client.get(url) self.assertEqual( - response.status_code, 200, - msg="Can not access admin detail for {}.".format(model)) + response.status_code, + 200, + msg="Can not access admin detail for {}.".format(model), + ) def test_csv_export(self): for model in self.gen_models: - url = '/admin/{}/{}/'.format(self.module_name, - model.__name__.lower()) + url = "/admin/{}/{}/".format(self.module_name, model.__name__.lower()) q = model.objects if not q.count(): continue response = self.client.post( - url, {'action': 'export_as_csv', - '_selected_action': [str(o.pk) for o in q.all()]}) + url, + { + "action": "export_as_csv", + "_selected_action": [str(o.pk) for o in q.all()], + }, + ) self.assertEqual( - response.status_code, 200, - msg="Can not export as CSV for {}.".format(model)) + response.status_code, + 200, + msg="Can not export as CSV for {}.".format(model), + ) try: f = io.StringIO(response.content.decode()) reader = csv.DictReader(f) for row in reader: - if 'txt_idx' in row: - slug_name = 'txt_idx' - elif 'slug' in row: - slug_name = 'slug' + if "txt_idx" in row: + slug_name = "txt_idx" + elif "slug" in row: + slug_name = "slug" else: continue obj = model.objects.get(**{slug_name: row[slug_name]}) for k in row: current_value = getattr(obj, k) if not row[k]: - self.assertIn(current_value, [None, ''], + self.assertIn( + current_value, + [None, ""], msg="Export CSV for {} - {}: CSV value is " - "null whereas value for object is {}" - ".".format(model, k, current_value)) + "null whereas value for object is {}" + ".".format(model, k, current_value), + ) continue field = model._meta.get_field(k) if isinstance(field, BooleanField): if current_value: self.assertEqual( - row[k], 'True', + row[k], + "True", msg="Export CSV for {} - {}: CSV value is " - "{} whereas value for " - "object is True.".format(model, k, - row[k])) + "{} whereas value for " + "object is True.".format(model, k, row[k]), + ) continue else: self.assertEqual( - row[k], 'False', + row[k], + "False", msg="Export CSV for {} - {}: CSV value is " - "{} whereas value for " - "object is False.".format( - model, k, row[k])) + "{} whereas value for " + "object is False.".format(model, k, row[k]), + ) continue elif isinstance(field, ForeignKey): fmodel = field.rel.to try: - value = fmodel.objects.get( - **{slug_name: row[k]} - ) + value = fmodel.objects.get(**{slug_name: row[k]}) except fmodel.DoesNotExist: - msg = "Export CSV for {} - {}: CSV value is "\ - "{} but it is not a vaild slug for {}" \ - ".".format(model, k, row[k], fmodel) + msg = ( + "Export CSV for {} - {}: CSV value is " + "{} but it is not a vaild slug for {}" + ".".format(model, k, row[k], fmodel) + ) raise ValidationError(msg) self.assertEqual( - value, current_value, + value, + current_value, msg="Export CSV for {} - {}: CSV value is " - "{} whereas value for " - "object is {}.".format( - model, k, value, current_value)) + "{} whereas value for " + "object is {}.".format(model, k, value, current_value), + ) elif type(current_value) == float: self.assertEqual( - float(row[k]), current_value, + float(row[k]), + current_value, msg="Export CSV for {} - {}: CSV value is " - "{} whereas value for " - "object is {}.".format( - model, k, row[k], current_value)) + "{} whereas value for " + "object is {}.".format(model, k, row[k], current_value), + ) elif type(current_value) == int: self.assertEqual( - int(row[k]), current_value, + int(row[k]), + current_value, msg="Export CSV for {} - {}: CSV value is " - "{} whereas value for " - "object is {}.".format( - model, k, row[k], current_value)) + "{} whereas value for " + "object is {}.".format(model, k, row[k], current_value), + ) finally: f.close() @@ -1596,22 +1702,27 @@ class AdminGenTypeTest(TestCase): nb = q.count() if not nb: continue - base_url = '/admin/{}/{}/'.format(self.module_name, - model.__name__.lower()) + base_url = "/admin/{}/{}/".format(self.module_name, model.__name__.lower()) response = self.client.post( - base_url, {'action': 'export_as_csv', - '_selected_action': [str(o.pk) for o in q.all()]}) + base_url, + { + "action": "export_as_csv", + "_selected_action": [str(o.pk) for o in q.all()], + }, + ) self.assertEqual( - response.status_code, 200, - msg="Can not export as CSV for {}.".format(model)) + response.status_code, + 200, + msg="Can not export as CSV for {}.".format(model), + ) for obj in q.all(): obj.delete() - url = base_url + 'import-from-csv/' + url = base_url + "import-from-csv/" try: f = io.BytesIO(response.content) - response = self.client.post(url, {'csv_file': f, 'apply': True}) + response = self.client.post(url, {"csv_file": f, "apply": True}) self.assertEqual(response.status_code, 302) self.assertRedirects(response, base_url) self.assertEqual(nb, model.objects.count()) @@ -1620,17 +1731,22 @@ class AdminGenTypeTest(TestCase): def test_json_export(self): for model in self.gen_models: - url = '/admin/{}/{}/'.format(self.module_name, - model.__name__.lower()) + url = "/admin/{}/{}/".format(self.module_name, model.__name__.lower()) q = model.objects if not q.count(): continue response = self.client.post( - url, {'action': '_serialize_action', - '_selected_action': [str(o.pk) for o in q.all()]}) + url, + { + "action": "_serialize_action", + "_selected_action": [str(o.pk) for o in q.all()], + }, + ) self.assertEqual( - response.status_code, 200, - msg="Can not export as JSON for {}.".format(model)) + response.status_code, + 200, + msg="Can not export as JSON for {}.".format(model), + ) # json content already tested on full export def test_json_import(self): @@ -1639,23 +1755,27 @@ class AdminGenTypeTest(TestCase): nb = q.count() if not nb: continue - base_url = '/admin/{}/{}/'.format(self.module_name, - model.__name__.lower()) + base_url = "/admin/{}/{}/".format(self.module_name, model.__name__.lower()) response = self.client.post( - base_url, {'action': '_serialize_action', - '_selected_action': [str(o.pk) for o in q.all()]}) + base_url, + { + "action": "_serialize_action", + "_selected_action": [str(o.pk) for o in q.all()], + }, + ) self.assertEqual( - response.status_code, 200, - msg="Can not export as CSV for {}.".format(model)) + response.status_code, + 200, + msg="Can not export as CSV for {}.".format(model), + ) for obj in q.all(): obj.delete() - url = base_url + 'import-from-json/' + url = base_url + "import-from-json/" try: f = io.BytesIO(response.content) - response = self.client.post(url, {'json_file': f, - 'apply': True}) + response = self.client.post(url, {"json_file": f, "apply": True}) self.assertEqual(response.status_code, 302) self.assertRedirects(response, base_url) self.assertEqual(nb, model.objects.count()) @@ -1664,71 +1784,67 @@ class AdminGenTypeTest(TestCase): def test_importer_type_duplicate(self): model = models.ImporterType - base_url = '/admin/{}/{}/'.format(self.module_name, - model.__name__.lower()) + base_url = "/admin/{}/{}/".format(self.module_name, model.__name__.lower()) ref = model.objects.all()[0] nb = model.objects.count() response = self.client.post( - base_url, {'action': 'duplicate_importertype', - '_selected_action': [str(ref.pk)]}) + base_url, + {"action": "duplicate_importertype", "_selected_action": [str(ref.pk)]}, + ) self.assertEqual(response.status_code, 302) self.assertEqual(nb + 1, model.objects.count()) - duplicate = model.objects.order_by('-pk').all()[0] - self.assertEqual(duplicate.columns.count(), - ref.columns.count()) + duplicate = model.objects.order_by("-pk").all()[0] + self.assertEqual(duplicate.columns.count(), ref.columns.count()) def test_importer_column_duplicate(self): model = models.ImporterColumn - base_url = '/admin/{}/{}/'.format(self.module_name, - model.__name__.lower()) + base_url = "/admin/{}/{}/".format(self.module_name, model.__name__.lower()) ref = model.objects.all()[0] nb = model.objects.count() response = self.client.post( - base_url, {'action': 'duplicate_importercolumn', - '_selected_action': [str(ref.pk)]}) + base_url, + {"action": "duplicate_importercolumn", "_selected_action": [str(ref.pk)]}, + ) self.assertEqual(response.status_code, 302) self.assertEqual(nb + 1, model.objects.count()) - duplicate = model.objects.order_by('-pk').all()[0] - self.assertEqual(duplicate.duplicate_fields.count(), - ref.duplicate_fields.count()) - self.assertEqual(duplicate.targets.count(), - ref.targets.count()) + duplicate = model.objects.order_by("-pk").all()[0] + self.assertEqual( + duplicate.duplicate_fields.count(), ref.duplicate_fields.count() + ) + self.assertEqual(duplicate.targets.count(), ref.targets.count()) def test_importer_column_shift(self): model = models.ImporterColumn - importer_type = models.ImporterType.objects.get( - slug='ishtar-operations') + importer_type = models.ImporterType.objects.get(slug="ishtar-operations") # col in fixture should be well ordered - for idx, col in enumerate( - importer_type.columns.order_by('col_number').all()): + for idx, col in enumerate(importer_type.columns.order_by("col_number").all()): self.assertEqual(col.col_number, idx + 1) - base_url = '/admin/{}/{}/'.format(self.module_name, - model.__name__.lower()) + base_url = "/admin/{}/{}/".format(self.module_name, model.__name__.lower()) response = self.client.post( - base_url, { - 'action': 'shift_right', - '_selected_action': [ - str(c.pk) for c in importer_type.columns.all() - ]}) + base_url, + { + "action": "shift_right", + "_selected_action": [str(c.pk) for c in importer_type.columns.all()], + }, + ) self.assertEqual(response.status_code, 302) # col shifted to the right - for idx, col in enumerate( - importer_type.columns.order_by('col_number').all()): + for idx, col in enumerate(importer_type.columns.order_by("col_number").all()): self.assertEqual(col.col_number, idx + 2) response = self.client.post( - base_url, { - 'action': 'shift_left', - '_selected_action': [ - str(c.pk) for c in importer_type.columns.all() - ]}) + base_url, + { + "action": "shift_left", + "_selected_action": [str(c.pk) for c in importer_type.columns.all()], + }, + ) self.assertEqual(response.status_code, 302) # col shifted back to the left - for idx, col in enumerate( - importer_type.columns.order_by('col_number').all()): + for idx, col in enumerate(importer_type.columns.order_by("col_number").all()): self.assertEqual(col.col_number, idx + 1) def test_str(self): @@ -1737,88 +1853,102 @@ class AdminGenTypeTest(TestCase): self.assertTrue(str(model.objects.all()[0])) def test_user_creation(self): - url = '/admin/auth/user/add/' - password = 'ishtar is the queen' + url = "/admin/auth/user/add/" + password = "ishtar is the queen" response = self.client.post( - url, {'username': 'test', 'password1': password, - 'password2': password}) + url, {"username": "test", "password1": password, "password2": password} + ) self.assertEqual(response.status_code, 302) - self.assertTrue(self.client.login(username='test', password=password)) + self.assertTrue(self.client.login(username="test", password=password)) class MergeTest(TestCase): def setUp(self): - self.user, created = User.objects.get_or_create(username='username') - self.organisation_types = \ - models.OrganizationType.create_default_for_test() + self.user, created = User.objects.get_or_create(username="username") + self.organisation_types = models.OrganizationType.create_default_for_test() - self.person_types = [models.PersonType.objects.create(label='Admin'), - models.PersonType.objects.create(label='User')] - self.author_types = [models.AuthorType.objects.create(label='1'), - models.AuthorType.objects.create(label='2')] + self.person_types = [ + models.PersonType.objects.create(label="Admin"), + models.PersonType.objects.create(label="User"), + ] + self.author_types = [ + models.AuthorType.objects.create(label="1"), + models.AuthorType.objects.create(label="2"), + ] self.company_1 = models.Organization.objects.create( - history_modifier=self.user, name='Franquin Comp.', - organization_type=self.organisation_types[0]) + history_modifier=self.user, + name="Franquin Comp.", + organization_type=self.organisation_types[0], + ) self.person_1 = models.Person.objects.create( - name='Boule', surname=' ', history_modifier=self.user, - attached_to=self.company_1) + name="Boule", + surname=" ", + history_modifier=self.user, + attached_to=self.company_1, + ) self.person_1.person_types.add(self.person_types[0]) self.author_1_pk = models.Author.objects.create( - person=self.person_1, author_type=self.author_types[0]).pk + person=self.person_1, author_type=self.author_types[0] + ).pk - self.title = models.TitleType.objects.create(label='Test') + self.title = models.TitleType.objects.create(label="Test") self.company_2 = models.Organization.objects.create( - history_modifier=self.user, name='Goscinny Corp.', - organization_type=self.organisation_types[1]) + history_modifier=self.user, + name="Goscinny Corp.", + organization_type=self.organisation_types[1], + ) self.person_2 = models.Person.objects.create( - name='Bill', history_modifier=self.user, surname='Peyo', - title=self.title, attached_to=self.company_2) + name="Bill", + history_modifier=self.user, + surname="Peyo", + title=self.title, + attached_to=self.company_2, + ) self.user.ishtaruser.person = self.person_2 self.user.ishtaruser.save() models.UserProfile.objects.create( - profile_type=models.ProfileType.objects.all()[0], - person=self.person_2 + profile_type=models.ProfileType.objects.all()[0], person=self.person_2 ) self.person_2.person_types.add(self.person_types[1]) self.author_2_pk = models.Author.objects.create( - person=self.person_2, author_type=self.author_types[1]).pk + person=self.person_2, author_type=self.author_types[1] + ).pk self.person_3 = models.Person.objects.create( - name='George', history_modifier=self.user, - attached_to=self.company_1) + name="George", history_modifier=self.user, attached_to=self.company_1 + ) def test_person_merge(self): self.person_1.merge(self.person_2) # preserve existing fields - self.assertEqual(self.person_1.name, 'Boule') + self.assertEqual(self.person_1.name, "Boule") # fill missing fields self.assertEqual(self.person_1.title, self.title) # string field with only spaces is an empty field - self.assertEqual(self.person_1.surname, 'Peyo') + self.assertEqual(self.person_1.surname, "Peyo") # preserve one to one field - user = User.objects.get(username='username') + user = User.objects.get(username="username") self.assertEqual(self.person_1, user.ishtaruser.person) # preserve existing foreign key self.assertEqual(self.person_1.attached_to, self.company_1) # preserve existing many to many - self.assertTrue(self.person_types[0] - in self.person_1.person_types.all()) + self.assertTrue(self.person_types[0] in self.person_1.person_types.all()) # add new many to many - self.assertTrue(self.person_types[1] - in self.person_1.person_types.all()) + self.assertTrue(self.person_types[1] in self.person_1.person_types.all()) # update reverse foreign key association and do not break the existing - self.assertEqual(models.Author.objects.get(pk=self.author_1_pk).person, - self.person_1) - self.assertEqual(models.Author.objects.get(pk=self.author_2_pk).person, - self.person_1) + self.assertEqual( + models.Author.objects.get(pk=self.author_1_pk).person, self.person_1 + ) + self.assertEqual( + models.Author.objects.get(pk=self.author_2_pk).person, self.person_1 + ) self.person_3.merge(self.person_1) # manage well empty many to many fields - self.assertTrue(self.person_types[1] - in self.person_3.person_types.all()) + self.assertTrue(self.person_types[1] in self.person_3.person_types.all()) def test_person_with_use_account_merge(self): # bug: merge when the target is not the one having a Ishtar user account @@ -1828,23 +1958,25 @@ class MergeTest(TestCase): init_mc = self.person_1.merge_candidate.count() person = models.Person.objects.create( name=self.person_1.name, - surname=self.person_1.surname, history_modifier=self.user, - attached_to=self.person_1.attached_to) - self.assertEqual(self.person_1.merge_candidate.count(), - init_mc + 1) + surname=self.person_1.surname, + history_modifier=self.user, + attached_to=self.person_1.attached_to, + ) + self.assertEqual(self.person_1.merge_candidate.count(), init_mc + 1) person.archive() - self.assertEqual(self.person_1.merge_candidate.count(), - init_mc) + self.assertEqual(self.person_1.merge_candidate.count(), init_mc) class ShortMenuTest(TestCase): def setUp(self): - self.username = 'username666' - self.password = 'dcbqj7xnjkxnjsknx!@%' + self.username = "username666" + self.password = "dcbqj7xnjkxnjsknx!@%" self.user = User.objects.create_superuser( - self.username, "nomail@nomail.com", self.password) + self.username, "nomail@nomail.com", self.password + ) self.other_user = User.objects.create_superuser( - 'John', "nomail@nomail.com", self.password) + "John", "nomail@nomail.com", self.password + ) profile = models.get_current_profile() profile.files = True profile.context_record = True @@ -1856,6 +1988,7 @@ class ShortMenuTest(TestCase): if not user: user = self.other_user from archaeological_operations.models import Operation, OperationType + ope_type, created = OperationType.objects.get_or_create(label="test") idx = 1 while Operation.objects.filter(code_patriarche=str(idx)).count(): @@ -1863,23 +1996,24 @@ class ShortMenuTest(TestCase): return Operation.objects.create( operation_type=ope_type, history_modifier=user, - year=2042, operation_code=54, - code_patriarche=str(idx) + year=2042, + operation_code=54, + code_patriarche=str(idx), ) def test_not_connected(self): c = Client() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) # no content if not logged self.assertFalse(b"shortcut-menu" in response.content) c = Client() c.login(username=self.username, password=self.password) # no content because the user owns no object - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertFalse(b"shortcut-menu" in response.content) self._create_ope(user=self.user) # content is here - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertTrue(b"shortcut-menu" in response.content) def test_operation(self): @@ -1887,14 +2021,14 @@ class ShortMenuTest(TestCase): c.login(username=self.username, password=self.password) ope = self._create_ope() # not available at first - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertFalse(str(ope.cached_label) in response.content.decode()) # available because is the creator ope.history_creator = self.user ope.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(ope.cached_label) in response.content.decode()) @@ -1902,7 +2036,7 @@ class ShortMenuTest(TestCase): ope.history_creator = self.other_user ope.in_charge = self.user.ishtaruser.person ope.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(ope.cached_label) in response.content.decode()) @@ -1911,14 +2045,14 @@ class ShortMenuTest(TestCase): ope.in_charge = None ope.scientist = self.user.ishtaruser.person ope.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(ope.cached_label) in response.content.decode()) # end date is reached - no more available ope.end_date = datetime.date(1900, 1, 1) ope.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertFalse(str(ope.cached_label) in response.content.decode()) @@ -1931,12 +2065,13 @@ class ShortMenuTest(TestCase): session = c.session session[ope.SLUG] = ope.pk session.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(ope.cached_label) in response.content.decode()) def testFile(self): from archaeological_files.models import File, FileType + c = Client() c.login(username=self.username, password=self.password) file_type = FileType.objects.create() @@ -1946,14 +2081,14 @@ class ShortMenuTest(TestCase): year=2043, ) # not available at first - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertFalse(str(fle.cached_label) in response.content.decode()) # available because is the creator fle.history_creator = self.user fle.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(fle.cached_label) in response.content.decode()) @@ -1961,27 +2096,25 @@ class ShortMenuTest(TestCase): fle.history_creator = self.other_user fle.in_charge = self.user.ishtaruser.person fle.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(fle.cached_label) in response.content.decode()) # end date is reached - no more available fle.end_date = datetime.date(1900, 1, 1) fle.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertFalse(str(fle.cached_label) in response.content.decode()) def _create_cr(self): from archaeological_context_records.models import ContextRecord from archaeological_operations.models import Parcel + ope = self._create_ope() town = models.Town.objects.create() parcel = Parcel.objects.create( - operation=ope, - town=town, - section="AA", - parcel_number=42 + operation=ope, town=town, section="AA", parcel_number=42 ) return ContextRecord.objects.create( parcel=parcel, @@ -1995,14 +2128,14 @@ class ShortMenuTest(TestCase): cr = self._create_cr() # not available at first - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertFalse(str(cr.cached_label) in response.content.decode()) # available because is the creator cr.history_creator = self.user cr.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(cr.cached_label) in response.content.decode()) @@ -2011,7 +2144,7 @@ class ShortMenuTest(TestCase): cr.save() cr.operation.in_charge = self.user.ishtaruser.person cr.operation.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(cr.cached_label) in response.content.decode()) @@ -2021,19 +2154,16 @@ class ShortMenuTest(TestCase): cr.operation.in_charge = None cr.operation.scientist = self.user.ishtaruser.person cr.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(cr.cached_label) in response.content.decode()) def _create_find(self): from archaeological_finds.models import BaseFind, Find + cr = self._create_cr() - base_find = BaseFind.objects.create( - context_record=cr - ) - find = Find.objects.create( - label="Where is my find?" - ) + base_find = BaseFind.objects.create(context_record=cr) + find = Find.objects.create(label="Where is my find?") find.base_finds.add(base_find) return base_find, find @@ -2043,24 +2173,23 @@ class ShortMenuTest(TestCase): base_find, find = self._create_find() # not available at first - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertFalse(str(find.cached_label) in response.content.decode()) # available because is the creator find.history_creator = self.user find.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(find.cached_label) in response.content.decode()) # available because is in charge find.history_creator = self.other_user find.save() - base_find.context_record.operation.in_charge = \ - self.user.ishtaruser.person + base_find.context_record.operation.in_charge = self.user.ishtaruser.person base_find.context_record.operation.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(find.cached_label) in response.content.decode()) @@ -2068,10 +2197,9 @@ class ShortMenuTest(TestCase): find.history_creator = self.other_user find.save() base_find.context_record.operation.in_charge = None - base_find.context_record.operation.scientist = \ - self.user.ishtaruser.person + base_find.context_record.operation.scientist = self.user.ishtaruser.person base_find.context_record.operation.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(find.cached_label) in response.content.decode()) @@ -2079,34 +2207,34 @@ class ShortMenuTest(TestCase): c = Client() c.login(username=self.username, password=self.password) from archaeological_finds.models import FindBasket + basket = FindBasket.objects.create( label="My basket", ) # not available at first - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertFalse(str(basket.label) in response.content.decode()) # available because is the owner basket.user = self.user.ishtaruser basket.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(basket.label) in response.content.decode()) def test_treatment_file(self): c = Client() c.login(username=self.username, password=self.password) - from archaeological_finds.models import TreatmentFile, \ - TreatmentFileType + from archaeological_finds.models import TreatmentFile, TreatmentFileType + tf = TreatmentFile.objects.create( - type=TreatmentFileType.objects.create(), - year=2050 + type=TreatmentFileType.objects.create(), year=2050 ) # not available at first - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertFalse(str(tf.cached_label) in response.content.decode()) @@ -2114,7 +2242,7 @@ class ShortMenuTest(TestCase): tf.history_creator = self.user tf.save() tf = TreatmentFile.objects.get(pk=tf.pk) - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(tf.cached_label) in response.content.decode()) @@ -2122,26 +2250,25 @@ class ShortMenuTest(TestCase): tf.history_creator = self.other_user tf.in_charge = self.user.ishtaruser.person tf.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(tf.cached_label) in response.content.decode()) # end date is reached - no more available tf.end_date = datetime.date(1900, 1, 1) tf.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertFalse(str(tf.cached_label) in response.content.decode()) def _create_treatment(self): from archaeological_finds.models import Treatment, TreatmentState + completed, created = TreatmentState.objects.get_or_create( - txt_idx='completed', defaults={"executed": True, "label": "Done"} + txt_idx="completed", defaults={"executed": True, "label": "Done"} ) return Treatment.objects.create( - label="My treatment", - year=2052, - treatment_state=completed + label="My treatment", year=2052, treatment_state=completed ) def test_treatment(self): @@ -2150,14 +2277,14 @@ class ShortMenuTest(TestCase): treat = self._create_treatment() # not available at first - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertFalse(str(treat.cached_label) in response.content.decode()) # available because is the creator treat.history_creator = self.user treat.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(treat.cached_label) in response.content.decode()) @@ -2165,14 +2292,14 @@ class ShortMenuTest(TestCase): treat.history_creator = self.other_user treat.person = self.user.ishtaruser.person treat.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertTrue(str(treat.cached_label) in response.content.decode()) # end date is reached - no more available treat.end_date = datetime.date(1900, 1, 1) treat.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self.assertFalse(str(treat.cached_label) in response.content.decode()) @@ -2182,55 +2309,52 @@ class ShortMenuTest(TestCase): base_find, find = self._create_find() response = c.post( - reverse('pin-search', args=['find']), - {'value': 'Where is my find'}, - **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} + reverse("pin-search", args=["find"]), + {"value": "Where is my find"}, + **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"} ) self.assertEqual(response.status_code, 200) # the selected find search is pined - self.assertEqual(c.session['pin-search-find'], 'Where is my find') + self.assertEqual(c.session["pin-search-find"], "Where is my find") # empty search save means empty dependant search - c.get( - reverse('pin', args=['contextrecord', - str(base_find.context_record.pk)]) - ) + c.get(reverse("pin", args=["contextrecord", str(base_find.context_record.pk)])) response = c.post( - reverse('pin-search', args=['find']), - {'value': ''}, - **{'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'} + reverse("pin-search", args=["find"]), + {"value": ""}, + **{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"} ) self.assertEqual(response.status_code, 200) - self.assertEqual(c.session['pin-search-find'], '') - self.assertEqual(c.session['contextrecord'], '') + self.assertEqual(c.session["pin-search-find"], "") + self.assertEqual(c.session["contextrecord"], "") def test_update_current_item(self): c = Client() c.login(username=self.username, password=self.password) base_find, find = self._create_find() - response = c.get(reverse('pin', args=['find', find.pk])) + response = c.get(reverse("pin", args=["find", find.pk])) self.assertEqual(response.status_code, 200) # the selected find is pined - self.assertEqual(c.session['find'], str(find.pk)) + self.assertEqual(c.session["find"], str(find.pk)) # dependant items are also pined - self.assertEqual(c.session['contextrecord'], - str(base_find.context_record.pk)) - self.assertEqual(c.session['operation'], - str(base_find.context_record.operation.pk)) + self.assertEqual(c.session["contextrecord"], str(base_find.context_record.pk)) + self.assertEqual( + c.session["operation"], str(base_find.context_record.operation.pk) + ) # pin another operation - dependant items are nullify ope = self._create_ope() - response = c.get(reverse('pin', args=['operation', ope.pk])) + response = c.get(reverse("pin", args=["operation", ope.pk])) self.assertEqual(response.status_code, 200) - self.assertFalse(c.session['find']) - self.assertFalse(c.session['contextrecord']) + self.assertFalse(c.session["find"]) + self.assertFalse(c.session["contextrecord"]) # current find is set as an integer session = c.session - session['find'] = find.id + session["find"] = find.id session.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) self._create_treatment() @@ -2239,16 +2363,14 @@ class ShortMenuTest(TestCase): c = Client() c.login(username=self.username, password=self.password) from archaeological_finds.models import FindBasket - basket = FindBasket.objects.create( - label="My basket", - user=self.user.ishtaruser - ) + + basket = FindBasket.objects.create(label="My basket", user=self.user.ishtaruser) session = c.session - session['find'] = 'basket-{}'.format(basket.pk) + session["find"] = "basket-{}".format(basket.pk) session.save() - response = c.get(reverse('shortcut-menu')) + response = c.get(reverse("shortcut-menu")) self.assertEqual(response.status_code, 200) - response = c.get(reverse('get-document')) + response = c.get(reverse("get-document")) self.assertEqual(response.status_code, 200) @@ -2256,22 +2378,23 @@ class ImportTest(TestCase): def create_import(self): create_user() imp_model = models.ImporterModel.objects.create( - klass='ishtar_common.models.Person', name='Person') - importer_type = models.ImporterType.objects.create( - associated_models=imp_model) + klass="ishtar_common.models.Person", name="Person" + ) + importer_type = models.ImporterType.objects.create(associated_models=imp_model) dest = os.path.join(settings.MEDIA_ROOT, "MCC-operations-example.csv") shutil.copy( - settings.ROOT_PATH + - '../archaeological_operations/tests/MCC-operations-example.csv', - dest + settings.ROOT_PATH + + "../archaeological_operations/tests/MCC-operations-example.csv", + dest, ) - with open(dest, 'rb') as f: + with open(dest, "rb") as f: mcc_operation_file = DjangoFile(f) imprt = models.Import.objects.create( user=models.IshtarUser.objects.all()[0], importer_type=importer_type, - imported_file=mcc_operation_file) + imported_file=mcc_operation_file, + ) return imprt def test_archive_import(self): @@ -2279,8 +2402,7 @@ class ImportTest(TestCase): with open(imprt.imported_file.path, "r") as f: csv_content = f.read() with tempfile.TemporaryDirectory() as tmpdir: - for k in ("error_file", "result_file", "match_file", - "imported_images"): + for k in ("error_file", "result_file", "match_file", "imported_images"): sample_file = os.path.join(tmpdir, "media_{}.zip".format(k)) with open(sample_file, "w") as m: m.write("test" + k) @@ -2299,7 +2421,7 @@ class ImportTest(TestCase): self.assertTrue(imprt.archive_file) self.assertTrue(zipfile.is_zipfile(imprt.archive_file)) with tempfile.TemporaryDirectory() as tmpdir: - current_zip = zipfile.ZipFile(imprt.archive_file.path, 'r') + current_zip = zipfile.ZipFile(imprt.archive_file.path, "r") name_list = current_zip.namelist() self.assertIn("content.json", name_list) current_zip.extract("content.json", tmpdir) @@ -2324,7 +2446,7 @@ class ImportTest(TestCase): with open(os.path.join(tmpdir, name), "r") as f: self.assertEqual(f.read(), csv_content) - imprt.unarchive('FE') + imprt.unarchive("FE") imprt = models.Import.objects.get(pk=imprt.pk) self.assertEqual(imprt.state, "FE") for k in ("error_file", "result_file", "match_file", "imported_images"): @@ -2353,43 +2475,53 @@ class ImportTest(TestCase): self.assertFalse(imprt.imported_images) def test_delete_related(self): - town = models.Town.objects.create(name='my-test') - self.assertEqual(models.Town.objects.filter(name='my-test').count(), 1) + town = models.Town.objects.create(name="my-test") + self.assertEqual(models.Town.objects.filter(name="my-test").count(), 1) imprt = self.create_import() town.imports.add(imprt) imprt.delete() # town should be deleted - self.assertEqual(models.Town.objects.filter(name='my-test').count(), - 0) + self.assertEqual(models.Town.objects.filter(name="my-test").count(), 0) def test_keys(self): - content_type = ContentType.objects.get_for_model( - models.OrganizationType) + content_type = ContentType.objects.get_for_model(models.OrganizationType) # creation label = "Ploufé" ot = models.OrganizationType.objects.create(label=label) - self.assertEqual(models.ItemKey.objects.filter( - object_id=ot.pk, key=slugify(label), - content_type=content_type).count(), 1) + self.assertEqual( + models.ItemKey.objects.filter( + object_id=ot.pk, key=slugify(label), content_type=content_type + ).count(), + 1, + ) label_2 = "Plif" ot_2 = models.OrganizationType.objects.create(label=label_2) - self.assertEqual(models.ItemKey.objects.filter( - object_id=ot_2.pk, key=slugify(label_2), - content_type=content_type).count(), 1) + self.assertEqual( + models.ItemKey.objects.filter( + object_id=ot_2.pk, key=slugify(label_2), content_type=content_type + ).count(), + 1, + ) # replace key ot_2.add_key(slugify(label), force=True) # one key point to only one item - self.assertEqual(models.ItemKey.objects.filter( - key=slugify(label), - content_type=content_type).count(), 1) + self.assertEqual( + models.ItemKey.objects.filter( + key=slugify(label), content_type=content_type + ).count(), + 1, + ) # this key point to the right item - self.assertEqual(models.ItemKey.objects.filter( - object_id=ot_2.pk, key=slugify(label), - content_type=content_type).count(), 1) + self.assertEqual( + models.ItemKey.objects.filter( + object_id=ot_2.pk, key=slugify(label), content_type=content_type + ).count(), + 1, + ) # modification label_3 = "Yop" @@ -2397,32 +2529,43 @@ class ImportTest(TestCase): ot_2.txt_idx = slugify(label_3) ot_2.save() # old label not referenced anymore - self.assertEqual(models.ItemKey.objects.filter( - object_id=ot_2.pk, key=slugify(label_2), - content_type=content_type).count(), 0) + self.assertEqual( + models.ItemKey.objects.filter( + object_id=ot_2.pk, key=slugify(label_2), content_type=content_type + ).count(), + 0, + ) # # forced key association is always here # new key is here - self.assertEqual(models.ItemKey.objects.filter( - object_id=ot_2.pk, key=slugify(label), - content_type=content_type).count(), 1) - self.assertEqual(models.ItemKey.objects.filter( - object_id=ot_2.pk, key=slugify(label_3), - content_type=content_type).count(), 1) + self.assertEqual( + models.ItemKey.objects.filter( + object_id=ot_2.pk, key=slugify(label), content_type=content_type + ).count(), + 1, + ) + self.assertEqual( + models.ItemKey.objects.filter( + object_id=ot_2.pk, key=slugify(label_3), content_type=content_type + ).count(), + 1, + ) class IshtarSiteProfileTest(TestCase): - fixtures = [settings.ROOT_PATH + - '../fixtures/initial_data-auth-fr.json', - settings.ROOT_PATH + - '../ishtar_common/fixtures/initial_data-fr.json',] + fixtures = [ + settings.ROOT_PATH + "../fixtures/initial_data-auth-fr.json", + settings.ROOT_PATH + "../ishtar_common/fixtures/initial_data-fr.json", + ] def testRelevance(self): - cache.set('default-ishtarsiteprofile-is-current-profile', None, - settings.CACHE_TIMEOUT) + cache.set( + "default-ishtarsiteprofile-is-current-profile", None, settings.CACHE_TIMEOUT + ) profile = models.get_current_profile() default_slug = profile.slug profile2 = models.IshtarSiteProfile.objects.create( - label="Test profile 2", slug='test-profile-2') + label="Test profile 2", slug="test-profile-2" + ) profile2.save() # when no profile is the current, activate by default the first created self.assertTrue(profile.active and not profile2.active) @@ -2443,52 +2586,53 @@ class IshtarSiteProfileTest(TestCase): self.assertTrue(profile2.context_record and profile2.find) def testDefaultProfile(self): - cache.set('default-ishtar_common-IshtarSiteProfile', None, - settings.CACHE_TIMEOUT) + cache.set( + "default-ishtar_common-IshtarSiteProfile", None, settings.CACHE_TIMEOUT + ) self.assertFalse(models.IshtarSiteProfile.objects.count()) profile = models.get_current_profile(force=True) self.assertTrue(profile) self.assertEqual(models.IshtarSiteProfile.objects.count(), 1) def test_menu_filtering(self): - cache.set('default-ishtarsiteprofile-is-current-profile', None, - settings.CACHE_TIMEOUT) - username = 'username4277' - password = 'dcbqj756456!@%' - User.objects.create_superuser(username, "nomail@nomail.com", - password) + cache.set( + "default-ishtarsiteprofile-is-current-profile", None, settings.CACHE_TIMEOUT + ) + username = "username4277" + password = "dcbqj756456!@%" + User.objects.create_superuser(username, "nomail@nomail.com", password) c = Client() c.login(username=username, password=password) - response = c.get(reverse('start')) + response = c.get(reverse("start")) self.assertNotIn(b'href="/file_search/"', response.content) profile = models.get_current_profile() profile.files = True profile.save() - response = c.get(reverse('start')) + response = c.get(reverse("start")) self.assertIn(b'href="/file_search/"', response.content) def testExternalKey(self): profile = models.get_current_profile() - p = models.Person.objects.create(name='plouf', surname='Tégada') + p = models.Person.objects.create(name="plouf", surname="Tégada") self.assertEqual(p.raw_name, "PLOUF Tégada") - profile.person_raw_name = '{surname|slug} {name}' + profile.person_raw_name = "{surname|slug} {name}" profile.save() - p.raw_name = '' + p.raw_name = "" p.save() self.assertEqual(p.raw_name, "tegada plouf") class IshtarBasicTest(TestCase): def setUp(self): - self.password = 'mypassword' + self.password = "mypassword" self.my_admin = User.objects.create_superuser( - 'myuser', 'myemail@test.com', self.password) + "myuser", "myemail@test.com", self.password + ) self.client = Client() - self.client.login(username=self.my_admin.username, - password=self.password) + self.client.login(username=self.my_admin.username, password=self.password) def test_status(self): - response = self.client.get(reverse('status')) + response = self.client.get(reverse("status")) self.assertEqual(response.status_code, 200) def test_person_rawname(self): @@ -2501,28 +2645,29 @@ class IshtarBasicTest(TestCase): def test_show(self): person = models.Person.objects.create(name="Weasley", surname="Bill") orga_type = models.OrganizationType.objects.create( - txt_idx='test', label='testy') + txt_idx="test", label="testy" + ) company = models.Organization.objects.create( - history_modifier=self.my_admin, name='Franquin Comp.', - organization_type=orga_type) + history_modifier=self.my_admin, + name="Franquin Comp.", + organization_type=orga_type, + ) c = Client() - response = c.get(reverse('show-person', kwargs={'pk': person.pk})) + response = c.get(reverse("show-person", kwargs={"pk": person.pk})) self.assertEqual(response.status_code, 200) # empty content when not allowed self.assertEqual(response.content, b"") - response = c.get(reverse('show-organization', - kwargs={'pk': company.pk})) + response = c.get(reverse("show-organization", kwargs={"pk": company.pk})) self.assertEqual(response.status_code, 200) # empty content when not allowed self.assertEqual(response.content, b"") c.login(username=self.my_admin.username, password=self.password) - response = c.get(reverse('show-person', kwargs={'pk': person.pk})) + response = c.get(reverse("show-person", kwargs={"pk": person.pk})) self.assertEqual(response.status_code, 200) self.assertIn(b'class="card sheet"', response.content) - response = c.get(reverse('show-organization', - kwargs={'pk': company.pk})) + response = c.get(reverse("show-organization", kwargs={"pk": company.pk})) self.assertEqual(response.status_code, 200) self.assertIn(b'class="card sheet"', response.content) @@ -2535,8 +2680,7 @@ class IshtarBasicTest(TestCase): town = models.Town.objects.get(numero_insee="99999") self.assertEqual(town.cached_label, "Sin City - 99") - models.Town.objects.create(name="Mega City", numero_insee="99999", - year=2051) + models.Town.objects.create(name="Mega City", numero_insee="99999", year=2051) mega_city = models.Town.objects.get(numero_insee="99999", year=2051) town.children.add(mega_city) town = models.Town.objects.get(numero_insee="99999-2050") @@ -2548,13 +2692,14 @@ class GeomaticTest(TestCase): class FakeGeomaticObject(object): _meta = models.GeoItem._meta - def __init__(self, x, y, z, spatial_reference_system, point=None, - point_2d=None): + def __init__( + self, x, y, z, spatial_reference_system, point=None, point_2d=None + ): self.x = x self.y = y self.z = z self.spatial_reference_system = spatial_reference_system - self.point_source = 'P' + self.point_source = "P" self.point_source_item = "" self.point = point self.point_2d = point_2d @@ -2562,16 +2707,15 @@ class GeomaticTest(TestCase): def save(self, *args, **kwargs): pass + profile = models.get_current_profile() profile.mapping = True profile.save() srs = models.SpatialReferenceSystem.objects.create( - label='WGS84', txt_idx='wgs84', srid=4326 + label="WGS84", txt_idx="wgs84", srid=4326 ) - obj = FakeGeomaticObject( - x=2, y=3, z=4, - spatial_reference_system=srs) + obj = FakeGeomaticObject(x=2, y=3, z=4, spatial_reference_system=srs) self.assertIsNone(obj.point_2d) post_save_geo(FakeGeomaticObject, instance=obj) self.assertIsNotNone(obj.point_2d) @@ -2585,10 +2729,8 @@ class NewItems(TestCase): self.username, self.password, self.user = create_superuser() def test_new_author(self): - url = 'new-author' - person = models.Person.objects.create( - name="Hop", surname="Oups" - ) + url = "new-author" + person = models.Person.objects.create(name="Hop", surname="Oups") c = Client() # TODO @@ -2600,30 +2742,29 @@ class NewItems(TestCase): response = c.post( reverse(url), - {"person": person.id, - "author_type": models.AuthorType.objects.all()[0].pk} + {"person": person.id, "author_type": models.AuthorType.objects.all()[0].pk}, ) self.assertEqual(response.status_code, 200) self.assertEqual(person.author.count(), 1) class AccountWizardTest(WizardTest, TestCase): - fixtures = [settings.ROOT_PATH + - '../fixtures/initial_data-auth-fr.json', - settings.ROOT_PATH + - '../ishtar_common/fixtures/initial_data-fr.json',] - url_name = 'account_management' - wizard_name = 'account_wizard' + fixtures = [ + settings.ROOT_PATH + "../fixtures/initial_data-auth-fr.json", + settings.ROOT_PATH + "../ishtar_common/fixtures/initial_data-fr.json", + ] + url_name = "account_management" + wizard_name = "account_wizard" steps = views.account_wizard_steps form_datas = [ WizardTestFormData( "Add an account", form_datas={ - 'account': { - 'username': "My username", - 'email': "test@example.com", - 'hidden_password': "my_pass", - 'hidden_password_confirm': "my_pass", + "account": { + "username": "My username", + "email": "test@example.com", + "hidden_password": "my_pass", + "hidden_password_confirm": "my_pass", } }, ), @@ -2631,10 +2772,11 @@ class AccountWizardTest(WizardTest, TestCase): def pre_wizard(self): self.person = models.Person.objects.create( - name='Boule', surname=' ', + name="Boule", + surname=" ", ) - self.form_datas[0].set('selec', 'pk', self.person.pk) - self.form_datas[0].set('account', 'pk', self.person.pk) + self.form_datas[0].set("selec", "pk", self.person.pk) + self.form_datas[0].set("account", "pk", self.person.pk) self.account_number = models.IshtarUser.objects.count() super(AccountWizardTest, self).pre_wizard() @@ -2644,15 +2786,15 @@ class AccountWizardTest(WizardTest, TestCase): user = person.ishtaruser.user_ptr self.assertEqual(user.username, "My username") self.assertEqual(user.email, "test@example.com") - self.assertEqual(models.IshtarUser.objects.count(), - self.account_number + 1) + self.assertEqual(models.IshtarUser.objects.count(), self.account_number + 1) class DashboardTest(TestCase): def setUp(self): self.username, self.password, self.user = create_superuser() profile, created = models.IshtarSiteProfile.objects.get_or_create( - slug='default', active=True) + slug="default", active=True + ) profile.files = True profile.context_record = True profile.find = True @@ -2663,33 +2805,46 @@ class DashboardTest(TestCase): c = Client() c.login(username=self.username, password=self.password) - url = 'dashboard-main-detail' - - response = c.get(reverse(url, kwargs={'item_name': "zorglub"})) - self.assertEqual( - response.status_code, 404) - - for item in ['users', 'files', 'treatmentfiles', 'treatments', - 'operations', 'contextrecords', 'finds']: - response = c.get(reverse(url, kwargs={'item_name': item})) + url = "dashboard-main-detail" + + response = c.get(reverse(url, kwargs={"item_name": "zorglub"})) + self.assertEqual(response.status_code, 404) + + for item in [ + "users", + "files", + "treatmentfiles", + "treatments", + "operations", + "contextrecords", + "finds", + ]: + response = c.get(reverse(url, kwargs={"item_name": item})) self.assertEqual( - response.status_code, 200, - "Reaching dashboard for item: {} return an error.".format(url)) + response.status_code, + 200, + "Reaching dashboard for item: {} return an error.".format(url), + ) class CleanMedia(TestCase): def test_rename(self): test_names = [ - ("éofficier2-12-02-04.93_gvK3hAr-1_2m7zZPn-1_nKhh2S2-1_"\ - "ONmUhfD-1_ymA3gGJ-1_XzJyRx3-1_PhvRcO8-1-thumb_ZwWMKBd.jpg", - "éofficier2-12-02-04.93-thumb.jpg"), + ( + "éofficier2-12-02-04.93_gvK3hAr-1_2m7zZPn-1_nKhh2S2-1_" + "ONmUhfD-1_ymA3gGJ-1_XzJyRx3-1_PhvRcO8-1-thumb_ZwWMKBd.jpg", + "éofficier2-12-02-04.93-thumb.jpg", + ), ("a_ZwWMKBd.jpg", False), # no rename because too short - ("hoplala_gvK3hAr_2m7zZPn_nKhh2S2_ZwWMKBd.jpg", - "hoplala_gvK3hAr_2m7zZPn_nKhh2S2.jpg",), # stop before because + ( + "hoplala_gvK3hAr_2m7zZPn_nKhh2S2_ZwWMKBd.jpg", + "hoplala_gvK3hAr_2m7zZPn_nKhh2S2.jpg", + ), # stop before because # another file exists ] - base_dir = os.sep.join([settings.ROOT_PATH, "..", "ishtar_common", - "tests", "rename"]) + base_dir = os.sep.join( + [settings.ROOT_PATH, "..", "ishtar_common", "tests", "rename"] + ) for name, expected in test_names: name = os.sep.join([base_dir, name]) new_name, modif = rename_and_simplify_media_name(name, rename=False) @@ -2701,12 +2856,15 @@ class CleanMedia(TestCase): def test_try_fix(self): test_names = [ - ("hoplala_gvK3hAr_2m7zZPn_nKhh2S2_ZwWMKBd_ZwWMKBd.jpg", - # non existing file - "hoplala_gvK3hAr_2m7zZPn.jpg",), + ( + "hoplala_gvK3hAr_2m7zZPn_nKhh2S2_ZwWMKBd_ZwWMKBd.jpg", + # non existing file + "hoplala_gvK3hAr_2m7zZPn.jpg", + ), ] - base_dir = os.sep.join([settings.ROOT_PATH, "..", "ishtar_common", - "tests", "rename"]) + base_dir = os.sep.join( + [settings.ROOT_PATH, "..", "ishtar_common", "tests", "rename"] + ) for name, expected in test_names: name = os.sep.join([base_dir, name]) @@ -2719,8 +2877,7 @@ class PersonQATest(TestCase): def setUp(self): self.username, self.password, self.user = create_superuser() - self.user.user_permissions.add(Permission.objects.get( - codename='change_person')) + self.user.user_permissions.add(Permission.objects.get(codename="change_person")) self.title_1 = models.TitleType.objects.create(label="T1", txt_idx="t1") self.title_2 = models.TitleType.objects.create(label="T2", txt_idx="t2") self.person_1 = models.Person.objects.create(title=self.title_1) @@ -2729,51 +2886,52 @@ class PersonQATest(TestCase): def test_bulk_update(self): c = Client() pks = "{}-{}".format(self.person_1.pk, self.person_2.pk) - response = c.get(reverse('person-qa-bulk-update', args=[pks])) - self.assertRedirects(response, '/') + response = c.get(reverse("person-qa-bulk-update", args=[pks])) + self.assertRedirects(response, "/") c = Client() c.login(username=self.username, password=self.password) - response = c.get(reverse('person-qa-bulk-update', args=[pks])) + response = c.get(reverse("person-qa-bulk-update", args=[pks])) self.assertEqual(response.status_code, 200) self.assertNotEqual(self.person_1.title, self.title_2) self.assertNotEqual(self.person_2.title, self.title_2) response = c.post( - reverse('person-qa-bulk-update-confirm', args=[pks]), - {'qa_title': self.title_2.pk} + reverse("person-qa-bulk-update-confirm", args=[pks]), + {"qa_title": self.title_2.pk}, ) if response.status_code != 200: - self.assertRedirects(response, '/success/') + self.assertRedirects(response, "/success/") self.assertEqual( - models.Person.objects.get(pk=self.person_1.pk).title, - self.title_2 + models.Person.objects.get(pk=self.person_1.pk).title, self.title_2 ) self.assertEqual( - models.Person.objects.get(pk=self.person_2.pk).title, - self.title_2 + models.Person.objects.get(pk=self.person_2.pk).title, self.title_2 ) class DocumentTest(TestCase): def setUp(self): Operation = apps.get_model("archaeological_operations", "Operation") - ContextRecord = apps.get_model("archaeological_context_records", - "ContextRecord") + ContextRecord = apps.get_model( + "archaeological_context_records", "ContextRecord" + ) Unit = apps.get_model("archaeological_context_records", "Unit") BaseFind = apps.get_model("archaeological_finds", "BaseFind") Find = apps.get_model("archaeological_finds", "Find") operation_type, __ = models.OperationType.objects.get_or_create( - txt_idx="arch_diagnostic", label="Diagnostic") + txt_idx="arch_diagnostic", label="Diagnostic" + ) self.ope1 = Operation.objects.create( - code_patriarche="001", - operation_type_id=operation_type.pk) + code_patriarche="001", operation_type_id=operation_type.pk + ) self.ope2 = Operation.objects.create( - code_patriarche="002", - operation_type_id=operation_type.pk) + code_patriarche="002", operation_type_id=operation_type.pk + ) su, __ = Unit.objects.get_or_create( - txt_idx='stratigraphic-unit', label="Stratigraphic unit", order=1) + txt_idx="stratigraphic-unit", label="Stratigraphic unit", order=1 + ) self.cr1 = ContextRecord.objects.create(operation=self.ope1, unit=su) self.cr2 = ContextRecord.objects.create(operation=self.ope2, unit=su) bf1 = BaseFind.objects.create(context_record=self.cr1) @@ -2783,31 +2941,35 @@ class DocumentTest(TestCase): self.find2 = Find.objects.create() self.find2.base_finds.add(bf2) self.st1 = models.SourceType.objects.create(label="Report", code="REP") - self.st2 = models.SourceType.objects.create(label="Illustration", - code="ILL") + self.st2 = models.SourceType.objects.create(label="Illustration", code="ILL") def test_custom_index(self): profile, created = models.IshtarSiteProfile.objects.get_or_create( - slug='default', active=True) - profile.document_complete_identifier = \ + slug="default", active=True + ) + profile.document_complete_identifier = ( "{operation_codes}-{source_type__code}-{custom_index}" + ) profile.document_custom_index = "operation" profile.save() - doc = models.Document.objects.create(source_type=self.st1, - title="Operation report") + doc = models.Document.objects.create( + source_type=self.st1, title="Operation report" + ) doc.operations.add(self.ope1) doc = models.Document.objects.get(pk=doc.pk) self.assertEqual(doc.complete_identifier, "001-REP-1") - doc2 = models.Document.objects.create(source_type=self.st2, - title="Illustration CR") + doc2 = models.Document.objects.create( + source_type=self.st2, title="Illustration CR" + ) doc2.context_records.add(self.cr1) doc2 = models.Document.objects.get(pk=doc2.pk) self.assertEqual(doc2.complete_identifier, "001-ILL-2") - doc3 = models.Document.objects.create(source_type=self.st1, - title="Operation report 2") + doc3 = models.Document.objects.create( + source_type=self.st1, title="Operation report 2" + ) doc3.operations.add(self.ope2) doc3 = models.Document.objects.get(pk=doc3.pk) self.assertEqual(doc3.complete_identifier, "002-REP-1") @@ -2817,12 +2979,13 @@ class DocumentTest(TestCase): doc3.save() doc3 = models.Document.objects.get(pk=doc3.pk) self.assertEqual(doc3.custom_index, None) # 2 operations - no index - self.assertEqual(doc3.complete_identifier, '001|002-REP-') + self.assertEqual(doc3.complete_identifier, "001|002-REP-") # complex jinja identifier - profile.document_complete_identifier = \ - "{% if custom_index %}{{operation_codes}}-{{source_type__code}}-" \ - "{{ \"%03d\" % (custom_index|int)}}{% else %}no-code{% endif %}" + profile.document_complete_identifier = ( + "{% if custom_index %}{{operation_codes}}-{{source_type__code}}-" + '{{ "%03d" % (custom_index|int)}}{% else %}no-code{% endif %}' + ) profile.save() doc3.operations.remove(self.ope1) @@ -2830,18 +2993,19 @@ class DocumentTest(TestCase): doc3.complete_identifier = "" doc3.save() doc3 = models.Document.objects.get(pk=doc3.pk) - self.assertEqual(doc3.complete_identifier, '002-REP-001') + self.assertEqual(doc3.complete_identifier, "002-REP-001") doc3.operations.remove(self.ope2) doc3.custom_index = None doc3.complete_identifier = "" doc3.save() doc3 = models.Document.objects.get(pk=doc3.pk) - self.assertEqual(doc3.complete_identifier, 'no-code') + self.assertEqual(doc3.complete_identifier, "no-code") def test_clean_duplicate_association(self): - doc = models.Document.objects.create(source_type=self.st1, - title="Operation report") + doc = models.Document.objects.create( + source_type=self.st1, title="Operation report" + ) doc.operations.add(self.ope1) doc.context_records.add(self.cr1) doc = models.Document.objects.get(pk=doc.pk) @@ -2882,33 +3046,35 @@ class JinjaFilterTest(TestCase): self.assertEqual(utils_secretary.splitpart("1,2,3", 10), "") self.assertEqual(utils_secretary.splitpart("", 10), "") self.assertEqual(utils_secretary.splitpart("1;2;3", 2, ";"), "3") - self.assertEqual(utils_secretary.splitpart( - "1;2;3;4", 1, ";", True), "2;3;4") + self.assertEqual(utils_secretary.splitpart("1;2;3;4", 1, ";", True), "2;3;4") def test_human_date(self): self.assertEqual(utils_secretary.human_date_filter("NODATE"), "") self.assertEqual( - utils_secretary.human_date_filter("2020-01-01"), - "1 janvier 2020") + utils_secretary.human_date_filter("2020-01-01"), "1 janvier 2020" + ) def test_capfirst(self): self.assertEqual( utils_secretary.capfirst_filter("saint georges d'oléron"), - "Saint georges d'oléron") + "Saint georges d'oléron", + ) self.assertEqual(utils_secretary.capfirst_filter("s"), "S") self.assertEqual(utils_secretary.capfirst_filter(""), "") def test_capitalize(self): self.assertEqual( utils_secretary.capitalize_filter("SAINT-GEORGES D'OLÉRON"), - "Saint-Georges d'Oléron") + "Saint-Georges d'Oléron", + ) self.assertEqual(utils_secretary.capitalize_filter("s"), "S") self.assertEqual(utils_secretary.capitalize_filter(""), "") def test_lowerfirst(self): self.assertEqual( utils_secretary.lowerfirst_filter("SAINT GEORGES D'OLÉRON"), - "sAINT GEORGES D'OLÉRON") + "sAINT GEORGES D'OLÉRON", + ) self.assertEqual(utils_secretary.lowerfirst_filter("S"), "s") self.assertEqual(utils_secretary.lowerfirst_filter(""), "") @@ -2916,49 +3082,45 @@ class JinjaFilterTest(TestCase): class TemplateGenerationTest(TestCase): def test_filters(self): model, __ = models.ImporterModel.objects.get_or_create( - klass='archaeological_finds.models.Find' + klass="archaeological_finds.models.Find" ) q = models.DocumentTemplate.objects.filter(slug="test") if q.count(): q.all()[0].delete() doc = models.DocumentTemplate.objects.create( - name="Test", - slug="test", - associated_model=model, - available=True) + name="Test", slug="test", associated_model=model, available=True + ) LABEL_EXPECTED_KEYS = [ - 'container_index', - 'context_record_operation_common_name', - 'context_record_town_name', - 'container_location_name', - 'context_record_operation_operation_type', - 'container_reference', - 'my_complete_id', - 'complete_idx', - 'complete_idxy', + "container_index", + "context_record_operation_common_name", + "context_record_town_name", + "container_location_name", + "context_record_operation_operation_type", + "container_reference", + "my_complete_id", + "complete_idx", + "complete_idxy", ] - tpl_label = settings.ROOT_PATH + \ - '../ishtar_common/tests/test-filters-label.odt' + tpl_label = settings.ROOT_PATH + "../ishtar_common/tests/test-filters-label.odt" BASE_EXPECTED_KEYS = [ - 'container_index', - 'context_record_operation_common_name', - 'context_record_town_name', - 'container_location_name', - 'context_record_operation_operation_type', - 'container_reference', - 'my_complete_id', - 'complete_idx', - 'complete_idxy', + "container_index", + "context_record_operation_common_name", + "context_record_town_name", + "container_location_name", + "context_record_operation_operation_type", + "container_reference", + "my_complete_id", + "complete_idx", + "complete_idxy", ] - tpl_base = settings.ROOT_PATH + \ - '../ishtar_common/tests/test-filters-base.odt' + tpl_base = settings.ROOT_PATH + "../ishtar_common/tests/test-filters-base.odt" tests = ( (LABEL_EXPECTED_KEYS, tpl_label, models.DocumentTemplate.LABEL_RE), (BASE_EXPECTED_KEYS, tpl_base, models.DocumentTemplate.BASE_RE), ) for expected_keys, tpl, filter_re in tests: - with open(tpl, 'rb') as tpl: + with open(tpl, "rb") as tpl: template = SimpleUploadedFile("template.odt", tpl.read()) filtr = doc.get_filter(template, filter_re) for key in expected_keys: diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py index 5ec8d4ee5..9266a7a9a 100644 --- a/ishtar_common/urls.py +++ b/ishtar_common/urls.py @@ -31,149 +31,240 @@ from ishtar_common.utils import check_rights, get_urls_for_model # forms urlpatterns = [ - url(r'^status/$', views.status, name='status'), - url(r'^raise-error/$', views.raise_error, name='raise-error'), - url(r'^raise-task-error/$', views.raise_task_error, - name='raise-task-error'), - url(r'^ty/(?P<url_id>[a-zA-Z0-9]+)$', views.tiny_redirect, - name='tiny-redirect'), - url(r'^robots\.txt$', TemplateView.as_view(template_name='robots.txt', - content_type='text/plain')), + url(r"^status/$", views.status, name="status"), + url(r"^raise-error/$", views.raise_error, name="raise-error"), + url(r"^raise-task-error/$", views.raise_task_error, name="raise-task-error"), + url(r"^ty/(?P<url_id>[a-zA-Z0-9]+)$", views.tiny_redirect, name="tiny-redirect"), + url( + r"^robots\.txt$", + TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), + ), # internationalization - url(r'^i18n/', include('django.conf.urls.i18n')), + url(r"^i18n/", include("django.conf.urls.i18n")), # General - url(r'shortcut_menu/', views.shortcut_menu, name='shortcut-menu'), - url(r'display/(?P<item_type>\w+)/(?P<pk>\d+)/', - views.DisplayItemView.as_view(), name='display-item'), - url(r'qrcode/search/', - views.QRCodeForSearchView.as_view(), name='search-qrcode'), - url(r'qrcode/(?P<app>[-a-z]+)/(?P<model_name>[-a-z]+)/(?P<pk>\d+)/', - views.QRCodeView.as_view(), name='qrcode-item'), - url(r'^generate-labels/(?P<template_slug>[-a-z0-9]+)/', - views.GenerateLabelView.as_view(), name='generate-labels'), - url(r'^generate-document/(?P<template_slug>[-a-z0-9]+)/(' - r'?P<item_pk>\d+)/', - views.GenerateView.as_view(), name='generate-document'), - url(r'person_search/(?P<step>.+)?$', - check_rights(['add_person'])( - views.person_search_wizard), name='person_search'), - url(r'person_creation/(?P<step>.+)?$', - check_rights(['add_person'])( - views.person_creation_wizard), name='person_creation'), - url(r'person_modification/(?P<step>.+)?$', - check_rights(['change_person', 'change_own_person'])( - views.person_modification_wizard), name='person_modification'), - url(r'person_modify/(?P<pk>.+)/$', views.person_modify, - name='person_modify'), - url(r'person_deletion/(?P<step>.+)?$', - check_rights(['change_person', 'change_own_person'])( - views.person_deletion_wizard), name='person_deletion'), - url(r'person_delete/(?P<pk>.+)/$', views.person_delete, - name='person_delete'), - url(r'^person-edit/$', - check_rights(['add_person'])( - views.PersonCreate.as_view()), name='person_create'), - url(r'^person-edit/(?P<pk>\d+)$', - check_rights(['change_person', 'change_own_person'])( - views.PersonEdit.as_view()), name='person_edit'), - - url(r'^person-qa-bulk-update/(?P<pks>[0-9-]+)?/$', - check_rights(['change_person', 'change_own_person'])( - views.QAPersonForm.as_view()), - name='person-qa-bulk-update'), - url(r'^person-qa-bulk-update/(?P<pks>[0-9-]+)?/confirm/$', - check_rights(['change_person', 'change_own_person'])( - views.QAPersonForm.as_view()), - name='person-qa-bulk-update-confirm', kwargs={"confirm": True}), - - url(r'organization_search/(?P<step>.+)?$', - check_rights(['add_organization'])( - views.organization_search_wizard), name='organization_search'), - url(r'organization_creation/(?P<step>.+)?$', - check_rights(['add_organization'])( - views.organization_creation_wizard), name='organization_creation'), - url(r'organization_modification/(?P<step>.+)?$', - check_rights(['change_organization', 'change_own_organization'])( - views.organization_modification_wizard), - name='organization_modification'), - url(r'organization_modify/(?P<pk>.+)/$', views.organization_modify, - name='organization_modify'), - url(r'organization_deletion/(?P<step>.+)?$', - check_rights(['change_organization', 'change_own_organization'])( - views.organization_deletion_wizard), name='organization_deletion'), - url(r'organization_delete/(?P<pk>.+)/$', views.organization_delete, - name='delete-organization'), - url(r'organization-edit/$', - check_rights(['add_organization'])( - views.OrganizationCreate.as_view()), name='organization_create'), - url(r'organization-edit/(?P<pk>\d+)$', - check_rights(['change_organization', 'change_own_organization'])( - views.OrganizationEdit.as_view()), name='organization_edit'), - url(r'organization-person-edit/$', - check_rights(['add_organization'])( - views.OrganizationPersonCreate.as_view()), - name='organization_person_create'), - url(r'organization-person-edit/(?P<pk>\d+)$', - check_rights(['change_organization', 'change_own_organization'])( - views.OrganizationPersonEdit.as_view()), - name='organization_person_edit'), - url(r'^organization-qa-bulk-update/(?P<pks>[0-9-]+)?/$', - check_rights(['change_organization', 'change_own_organization'])( - views.QAOrganizationForm.as_view()), - name='organization-qa-bulk-update'), - url(r'^organization-qa-bulk-update/(?P<pks>[0-9-]+)?/confirm/$', - check_rights(['change_organization', 'change_own_organization'])( - views.QAOrganizationForm.as_view()), - name='organization-qa-bulk-update-confirm', kwargs={"confirm": True}), - - url(r'get-ishtaruser/(?P<type>.+)?$', - views.get_ishtaruser, - name='get-ishtaruser'), - url(r'account_management/(?P<step>.+)?$', - check_rights(['add_ishtaruser'])( - views.account_management_wizard), name='account_management'), - url(r'account_deletion/(?P<step>.+)?$', - check_rights(['add_ishtaruser'])( - views.account_deletion_wizard), name='account_deletion'), - url(r'^import-new/$', - check_rights(['change_import'])( - views.NewImportView.as_view()), name='new_import'), - url(r'^import-list/$', - check_rights(['change_import'])( - views.ImportListView.as_view()), - name='current_imports'), - url(r'^import-list-table/$', - check_rights(['change_import'])( - views.ImportListTableView.as_view()), - name='current_imports_table'), - url(r'^import-list-old/$', - check_rights(['change_import'])( - views.ImportOldListView.as_view()), - name='old_imports'), - url(r'^import-delete/(?P<pk>[0-9]+)/$', - views.ImportDeleteView.as_view(), name='import_delete'), - url(r'^import-link-unmatched/(?P<pk>[0-9]+)/$', - views.ImportLinkView.as_view(), name='import_link_unmatched'), - url(r'^import-step-by-step/all/(?P<pk>[0-9]+)/(?P<line_number>[0-9]+)/$', - views.ImportStepByStepView.as_view(), name='import_step_by_step_all', - kwargs={'all_pages': True}), - url(r'^import-step-by-step/(?P<pk>[0-9]+)/(?P<line_number>[0-9]+)/$', - views.ImportStepByStepView.as_view(), name='import_step_by_step'), - url(r'^profile(?:/(?P<pk>[0-9]+))?/$', views.ProfileEdit.as_view(), - name='profile'), - url(r'^save-search/(?P<app_label>[a-z-]+)/(?P<model>[a-z-]+)/$', + url(r"shortcut_menu/", views.shortcut_menu, name="shortcut-menu"), + url( + r"display/(?P<item_type>\w+)/(?P<pk>\d+)/", + views.DisplayItemView.as_view(), + name="display-item", + ), + url(r"qrcode/search/", views.QRCodeForSearchView.as_view(), name="search-qrcode"), + url( + r"qrcode/(?P<app>[-a-z]+)/(?P<model_name>[-a-z]+)/(?P<pk>\d+)/", + views.QRCodeView.as_view(), + name="qrcode-item", + ), + url( + r"^generate-labels/(?P<template_slug>[-a-z0-9]+)/", + views.GenerateLabelView.as_view(), + name="generate-labels", + ), + url( + r"^generate-document/(?P<template_slug>[-a-z0-9]+)/(" r"?P<item_pk>\d+)/", + views.GenerateView.as_view(), + name="generate-document", + ), + url( + r"person_search/(?P<step>.+)?$", + check_rights(["add_person"])(views.person_search_wizard), + name="person_search", + ), + url( + r"person_creation/(?P<step>.+)?$", + check_rights(["add_person"])(views.person_creation_wizard), + name="person_creation", + ), + url( + r"person_modification/(?P<step>.+)?$", + check_rights(["change_person", "change_own_person"])( + views.person_modification_wizard + ), + name="person_modification", + ), + url(r"person_modify/(?P<pk>.+)/$", views.person_modify, name="person_modify"), + url( + r"person_deletion/(?P<step>.+)?$", + check_rights(["change_person", "change_own_person"])( + views.person_deletion_wizard + ), + name="person_deletion", + ), + url(r"person_delete/(?P<pk>.+)/$", views.person_delete, name="person_delete"), + url( + r"^person-edit/$", + check_rights(["add_person"])(views.PersonCreate.as_view()), + name="person_create", + ), + url( + r"^person-edit/(?P<pk>\d+)$", + check_rights(["change_person", "change_own_person"])( + views.PersonEdit.as_view() + ), + name="person_edit", + ), + url( + r"^person-qa-bulk-update/(?P<pks>[0-9-]+)?/$", + check_rights(["change_person", "change_own_person"])( + views.QAPersonForm.as_view() + ), + name="person-qa-bulk-update", + ), + url( + r"^person-qa-bulk-update/(?P<pks>[0-9-]+)?/confirm/$", + check_rights(["change_person", "change_own_person"])( + views.QAPersonForm.as_view() + ), + name="person-qa-bulk-update-confirm", + kwargs={"confirm": True}, + ), + url( + r"organization_search/(?P<step>.+)?$", + check_rights(["add_organization"])(views.organization_search_wizard), + name="organization_search", + ), + url( + r"organization_creation/(?P<step>.+)?$", + check_rights(["add_organization"])(views.organization_creation_wizard), + name="organization_creation", + ), + url( + r"organization_modification/(?P<step>.+)?$", + check_rights(["change_organization", "change_own_organization"])( + views.organization_modification_wizard + ), + name="organization_modification", + ), + url( + r"organization_modify/(?P<pk>.+)/$", + views.organization_modify, + name="organization_modify", + ), + url( + r"organization_deletion/(?P<step>.+)?$", + check_rights(["change_organization", "change_own_organization"])( + views.organization_deletion_wizard + ), + name="organization_deletion", + ), + url( + r"organization_delete/(?P<pk>.+)/$", + views.organization_delete, + name="delete-organization", + ), + url( + r"organization-edit/$", + check_rights(["add_organization"])(views.OrganizationCreate.as_view()), + name="organization_create", + ), + url( + r"organization-edit/(?P<pk>\d+)$", + check_rights(["change_organization", "change_own_organization"])( + views.OrganizationEdit.as_view() + ), + name="organization_edit", + ), + url( + r"organization-person-edit/$", + check_rights(["add_organization"])(views.OrganizationPersonCreate.as_view()), + name="organization_person_create", + ), + url( + r"organization-person-edit/(?P<pk>\d+)$", + check_rights(["change_organization", "change_own_organization"])( + views.OrganizationPersonEdit.as_view() + ), + name="organization_person_edit", + ), + url( + r"^organization-qa-bulk-update/(?P<pks>[0-9-]+)?/$", + check_rights(["change_organization", "change_own_organization"])( + views.QAOrganizationForm.as_view() + ), + name="organization-qa-bulk-update", + ), + url( + r"^organization-qa-bulk-update/(?P<pks>[0-9-]+)?/confirm/$", + check_rights(["change_organization", "change_own_organization"])( + views.QAOrganizationForm.as_view() + ), + name="organization-qa-bulk-update-confirm", + kwargs={"confirm": True}, + ), + url(r"get-ishtaruser/(?P<type>.+)?$", views.get_ishtaruser, name="get-ishtaruser"), + url( + r"account_management/(?P<step>.+)?$", + check_rights(["add_ishtaruser"])(views.account_management_wizard), + name="account_management", + ), + url( + r"account_deletion/(?P<step>.+)?$", + check_rights(["add_ishtaruser"])(views.account_deletion_wizard), + name="account_deletion", + ), + url( + r"^import-new/$", + check_rights(["change_import"])(views.NewImportView.as_view()), + name="new_import", + ), + url( + r"^import-list/$", + check_rights(["change_import"])(views.ImportListView.as_view()), + name="current_imports", + ), + url( + r"^import-list-table/$", + check_rights(["change_import"])(views.ImportListTableView.as_view()), + name="current_imports_table", + ), + url( + r"^import-list-old/$", + check_rights(["change_import"])(views.ImportOldListView.as_view()), + name="old_imports", + ), + url( + r"^import-delete/(?P<pk>[0-9]+)/$", + views.ImportDeleteView.as_view(), + name="import_delete", + ), + url( + r"^import-link-unmatched/(?P<pk>[0-9]+)/$", + views.ImportLinkView.as_view(), + name="import_link_unmatched", + ), + url( + r"^import-step-by-step/all/(?P<pk>[0-9]+)/(?P<line_number>[0-9]+)/$", + views.ImportStepByStepView.as_view(), + name="import_step_by_step_all", + kwargs={"all_pages": True}, + ), + url( + r"^import-step-by-step/(?P<pk>[0-9]+)/(?P<line_number>[0-9]+)/$", + views.ImportStepByStepView.as_view(), + name="import_step_by_step", + ), + url(r"^profile(?:/(?P<pk>[0-9]+))?/$", views.ProfileEdit.as_view(), name="profile"), + url( + r"^save-search/(?P<app_label>[a-z-]+)/(?P<model>[a-z-]+)/$", views.SearchQueryEdit.as_view(), - name='save-search-query'), - url(r'^bookmarks/(?P<app_label>[a-z-]+)/(?P<model>[a-z-]+)/$', - views.BookmarkList.as_view(), name='bookmark-list'), - url(r'^bookmark/(?P<pk>[0-9]+)/$', - views.get_bookmark, name='bookmark'), - url(r'^bookmark/delete/(?P<pk>[0-9]+)/$', - views.SearchQueryDelete.as_view(), name='bookmark-delete'), - url(r'^alerts/$', views.AlertList.as_view(), name='alert-list'), - url(r'^success(?:/(?P<context>[a-z-]+))?/$', + name="save-search-query", + ), + url( + r"^bookmarks/(?P<app_label>[a-z-]+)/(?P<model>[a-z-]+)/$", + views.BookmarkList.as_view(), + name="bookmark-list", + ), + url(r"^bookmark/(?P<pk>[0-9]+)/$", views.get_bookmark, name="bookmark"), + url( + r"^bookmark/delete/(?P<pk>[0-9]+)/$", + views.SearchQueryDelete.as_view(), + name="bookmark-delete", + ), + url(r"^alerts/$", views.AlertList.as_view(), name="alert-list"), + url( + r"^success(?:/(?P<context>[a-z-]+))?/$", TemplateView.as_view(template_name="ishtar/forms/success.html"), - name='success'), + name="success", + ), ] menu = Menu(None) @@ -181,7 +272,7 @@ menu.init() actions = [] for section in menu.ref_childs: for menu_item in section.childs: - if hasattr(menu_item, 'childs'): + if hasattr(menu_item, "childs"): for menu_subitem in menu_item.childs: actions.append(menu_subitem.idx) else: @@ -191,170 +282,291 @@ actions = r"|".join(actions) # other views urlpatterns += [ # General - url(r'dashboard-main/$', views.dashboard_main, - name='dashboard-main'), - url(r'dashboard-main/(?P<item_name>[a-z-]+)/$', views.dashboard_main_detail, - name='dashboard-main-detail'), - url(r'update-current-item/$', views.update_current_item, - name='update-current-item'), - url(r'pin/(?P<item_type>[a-z-]+)/(?P<pk>\d+)/$', views.update_current_item, - name='pin'), - url(r'pin-search/(?P<item_type>[a-z-]+)/$', views.pin_search, - name='pin-search'), - url(r'unpin/(?P<item_type>[a-z-]+)/$', views.unpin, name='unpin'), - url(r'get-by-importer/(?P<slug>[\w-]+)/(?P<type>[a-z-]+)?$', - views.get_by_importer, name='get-by-importer'), - url(r'new-person/(?:(?P<parent_name>[^/]+)/)?(?:(?P<limits>[^/]+)/)?$', - views.new_person, name='new-person'), - url(r'modify-person/(?:(?P<parent_name>[^/]+)/)?(?P<pk>[\d+]+)/$', - views.modify_person, name='modify-person'), - url(r'detail-person/(?P<pk>[\d+]+)/$', - views.detail_person, name='detail-person'), - url(r'modify-organization/(?:(?P<parent_name>[^/]+)/)?(?P<pk>[\d+]+)/$', - views.modify_organization, name='modify-organization'), - url(r'detail-organization/(?P<pk>[\d+]+)/$', - views.detail_organization, name='detail-organization'), - url(r'new-person-noorga/' - r'(?:(?P<parent_name>[^/]+)/)?(?:(?P<limits>[^/]+)/)?$', - views.new_person_noorga, name='new-person-noorga'), - url(r'autocomplete-user/$', - views.autocomplete_user, name='autocomplete-user'), - url(r'autocomplete-ishtaruser/$', - views.autocomplete_ishtaruser, name='autocomplete-ishtaruser'), - url(r'autocomplete-person(?:/([0-9_]+))?(?:/([0-9_]*))?/(user)?$', - views.autocomplete_person, name='autocomplete-person'), - url(r'autocomplete-person-permissive(?:/([0-9_]+))?(?:/([0-9_]*))?' - r'/(user)?$', views.autocomplete_person_permissive, - name='autocomplete-person-permissive'), - url(r'get-person/(?P<type>.+)?$', views.get_person, - name='get-person'), - url(r'get-person-full/(?P<type>.+)?$', views.get_person, - name='get-person-full', kwargs={'full': True}), - url(r'get-person-for-account/(?P<type>.+)?$', views.get_person_for_account, - name='get-person-for-account'), - url(r'show-person(?:/(?P<pk>.+))?/(?P<type>.+)?$', - views.show_person, name='show-person'), - url(r'department-by-state/(?P<state_id>.+)?$', views.department_by_state, - name='department-by-state'), - url(r'autocomplete-town/?$', views.autocomplete_town, - name='autocomplete-town'), - url(r'autocomplete-advanced-town/(?P<department_id>[0-9]+[ABab]?)?$', - views.autocomplete_advanced_town, name='autocomplete-advanced-town'), - url(r'autocomplete-department/?$', views.autocomplete_department, - name='autocomplete-department'), - url(r'new-author/(?:(?P<parent_name>[^/]+)/)?(?:(?P<limits>[^/]+)/)?$', - views.new_author, name='new-author'), - url(r'autocomplete-author/$', views.autocomplete_author, - name='autocomplete-author'), - url(r'new-organization/(?:(?P<parent_name>[^/]+)/)?' - r'(?:(?P<limits>[^/]+)/)?$', - views.new_organization, name='new-organization'), - url(r'get-organization/(?P<type>.+)?$', views.get_organization, - name='get-organization'), - url(r'get-organization-full/(?P<type>.+)?$', views.get_organization, - name='get-organization-full', kwargs={'full': True}), - url(r'show-organization(?:/(?P<pk>.+))?/(?P<type>.+)?$', - views.show_organization, name='show-organization'), - url(r'autocomplete-organization/([0-9_]+)?$', - views.autocomplete_organization, name='autocomplete-organization'), - url(r'admin-globalvar/', views.GlobalVarEdit.as_view(), - name='admin-globalvar'), - url(r'person-merge/(?:(?P<page>\d+)/)?$', views.person_merge, - name='person_merge'), - url(r'person-manual-merge/$', + url(r"dashboard-main/$", views.dashboard_main, name="dashboard-main"), + url( + r"dashboard-main/(?P<item_name>[a-z-]+)/$", + views.dashboard_main_detail, + name="dashboard-main-detail", + ), + url( + r"update-current-item/$", views.update_current_item, name="update-current-item" + ), + url( + r"pin/(?P<item_type>[a-z-]+)/(?P<pk>\d+)/$", + views.update_current_item, + name="pin", + ), + url(r"pin-search/(?P<item_type>[a-z-]+)/$", views.pin_search, name="pin-search"), + url(r"unpin/(?P<item_type>[a-z-]+)/$", views.unpin, name="unpin"), + url( + r"get-by-importer/(?P<slug>[\w-]+)/(?P<type>[a-z-]+)?$", + views.get_by_importer, + name="get-by-importer", + ), + url( + r"new-person/(?:(?P<parent_name>[^/]+)/)?(?:(?P<limits>[^/]+)/)?$", + views.new_person, + name="new-person", + ), + url( + r"modify-person/(?:(?P<parent_name>[^/]+)/)?(?P<pk>[\d+]+)/$", + views.modify_person, + name="modify-person", + ), + url(r"detail-person/(?P<pk>[\d+]+)/$", views.detail_person, name="detail-person"), + url( + r"modify-organization/(?:(?P<parent_name>[^/]+)/)?(?P<pk>[\d+]+)/$", + views.modify_organization, + name="modify-organization", + ), + url( + r"detail-organization/(?P<pk>[\d+]+)/$", + views.detail_organization, + name="detail-organization", + ), + url( + r"new-person-noorga/" r"(?:(?P<parent_name>[^/]+)/)?(?:(?P<limits>[^/]+)/)?$", + views.new_person_noorga, + name="new-person-noorga", + ), + url(r"autocomplete-user/$", views.autocomplete_user, name="autocomplete-user"), + url( + r"autocomplete-ishtaruser/$", + views.autocomplete_ishtaruser, + name="autocomplete-ishtaruser", + ), + url( + r"autocomplete-person(?:/([0-9_]+))?(?:/([0-9_]*))?/(user)?$", + views.autocomplete_person, + name="autocomplete-person", + ), + url( + r"autocomplete-person-permissive(?:/([0-9_]+))?(?:/([0-9_]*))?" r"/(user)?$", + views.autocomplete_person_permissive, + name="autocomplete-person-permissive", + ), + url(r"get-person/(?P<type>.+)?$", views.get_person, name="get-person"), + url( + r"get-person-full/(?P<type>.+)?$", + views.get_person, + name="get-person-full", + kwargs={"full": True}, + ), + url( + r"get-person-for-account/(?P<type>.+)?$", + views.get_person_for_account, + name="get-person-for-account", + ), + url( + r"show-person(?:/(?P<pk>.+))?/(?P<type>.+)?$", + views.show_person, + name="show-person", + ), + url( + r"department-by-state/(?P<state_id>.+)?$", + views.department_by_state, + name="department-by-state", + ), + url(r"autocomplete-town/?$", views.autocomplete_town, name="autocomplete-town"), + url( + r"autocomplete-advanced-town/(?P<department_id>[0-9]+[ABab]?)?$", + views.autocomplete_advanced_town, + name="autocomplete-advanced-town", + ), + url( + r"autocomplete-department/?$", + views.autocomplete_department, + name="autocomplete-department", + ), + url( + r"new-author/(?:(?P<parent_name>[^/]+)/)?(?:(?P<limits>[^/]+)/)?$", + views.new_author, + name="new-author", + ), + url( + r"autocomplete-author/$", views.autocomplete_author, name="autocomplete-author" + ), + url( + r"new-organization/(?:(?P<parent_name>[^/]+)/)?" r"(?:(?P<limits>[^/]+)/)?$", + views.new_organization, + name="new-organization", + ), + url( + r"get-organization/(?P<type>.+)?$", + views.get_organization, + name="get-organization", + ), + url( + r"get-organization-full/(?P<type>.+)?$", + views.get_organization, + name="get-organization-full", + kwargs={"full": True}, + ), + url( + r"show-organization(?:/(?P<pk>.+))?/(?P<type>.+)?$", + views.show_organization, + name="show-organization", + ), + url( + r"autocomplete-organization/([0-9_]+)?$", + views.autocomplete_organization, + name="autocomplete-organization", + ), + url(r"admin-globalvar/", views.GlobalVarEdit.as_view(), name="admin-globalvar"), + url(r"person-merge/(?:(?P<page>\d+)/)?$", views.person_merge, name="person_merge"), + url( + r"person-manual-merge/$", views.PersonManualMerge.as_view(), - name='person_manual_merge'), - url(r'person-manual-merge-items/(?P<pks>[0-9_]+?)/$', + name="person_manual_merge", + ), + url( + r"person-manual-merge-items/(?P<pks>[0-9_]+?)/$", views.PersonManualMergeItems.as_view(), - name='person_manual_merge_items'), - url(r'organization-merge/(?:(?P<page>\d+)/)?$', views.organization_merge, - name='organization_merge'), - url(r'orga-manual-merge/$', views.OrgaManualMerge.as_view(), - name='orga_manual_merge'), - url(r'orga-manual-merge-items/(?P<pks>[0-9_]+?)/$', + name="person_manual_merge_items", + ), + url( + r"organization-merge/(?:(?P<page>\d+)/)?$", + views.organization_merge, + name="organization_merge", + ), + url( + r"orga-manual-merge/$", + views.OrgaManualMerge.as_view(), + name="orga_manual_merge", + ), + url( + r"orga-manual-merge-items/(?P<pks>[0-9_]+?)/$", views.OrgaManualMergeItems.as_view(), - name='orga_manual_merge_items'), - url(r'reset/$', views.reset_wizards, name='reset_wizards'), - url(r'activate-all-search/$', views.activate_all_search, - name='activate-all-search'), - url(r'activate-own-search/$', views.activate_own_search, - name='activate-own-search'), - url(r'activate-advanced-menu/$', views.activate_advanced_shortcut_menu, - name='activate-advanced-menu'), - url(r'activate-simple-menu/$', views.activate_simple_shortcut_menu, - name='activate-simple-menu'), - url(r'hide-shortcut-menu/$', views.hide_shortcut_menu, - name='hide-shortcut-menu'), - url(r'show-shortcut-menu/$', views.show_shortcut_menu, - name='show-shortcut-menu'), - - url(r'regenerate-external-id/$', views.regenerate_external_id, - name='regenerate-external-id'), - - - url(r'document/search/(?P<step>.+)?$', - check_rights(['view_document', 'view_own_document'])( - views.document_search_wizard), - name='search-document'), - url(r'document/search/(?P<step>.+)?$', - check_rights(['view_document', 'view_own_document'])( - views.document_search_wizard), - name='document_search'), - url(r'document/create/$', - check_rights(['add_document', 'add_own_document'])( - views.DocumentCreateView.as_view()), - name='create-document'), - url(r'document/edit/$', - check_rights(['change_document', 'change_own_document'])( - views.DocumentSelectView.as_view()), - name='edit-document'), - url(r'document/edit/(?P<pk>.+)/$', - check_rights(['change_document', 'change_own_document'])( - views.DocumentEditView.as_view()), - name='edit-document'), - url(r'document/delete/(?P<step>.+)?$', - check_rights(['change_document', 'change_own_document'])( - views.document_deletion_wizard), - name='document_deletion'), - url(r'autocomplete-document/$', - views.autocomplete_document, name='autocomplete-document'), - url(r'document/shortcut/delete/(?P<pk>.+)/$', views.document_delete, - name='delete-document'), - - url(r'^document-qa-bulk-update/(?P<pks>[0-9-]+)?/$', - check_rights(['change_document', 'change_own_document'])( - views.QADocumentForm.as_view()), - name='document-qa-bulk-update'), - url(r'^document-qa-bulk-update/(?P<pks>[0-9-]+)?/confirm/$', - check_rights(['change_document', 'change_own_document'])( - views.QADocumentForm.as_view()), - name='document-qa-bulk-update-confirm', kwargs={"confirm": True}), - url(r'^document-qa-duplicate/(?P<pks>[0-9-]+)?/$', - check_rights(['change_document', 'change_own_document'])( - views.QADocumentDuplicateFormView.as_view()), - name='document-qa-duplicate'), - url(r'^document-qa-packaging/(?P<pks>[0-9-]+)?/$', - check_rights(['change_document', 'change_own_document'])( - views.QADocumentPackagingFormView.as_view()), - name='document-qa-packaging'), - - url(r'autocomplete-documenttag/$', - views.autocomplete_documenttag, name='autocomplete-documenttag'), - url(r'new-documenttag/(?:(?P<parent_name>[^/]+)/)?' - r'(?:(?P<limits>[^/]+)/)?$', - views.new_document_tag, name='new-documenttag'), - - url(r'^qa-not-available(?:/(?P<context>[0-9a-z-]+))?/$', - views.QANotAvailable.as_view(), name='qa-not-available'), + name="orga_manual_merge_items", + ), + url(r"reset/$", views.reset_wizards, name="reset_wizards"), + url( + r"activate-all-search/$", views.activate_all_search, name="activate-all-search" + ), + url( + r"activate-own-search/$", views.activate_own_search, name="activate-own-search" + ), + url( + r"activate-advanced-menu/$", + views.activate_advanced_shortcut_menu, + name="activate-advanced-menu", + ), + url( + r"activate-simple-menu/$", + views.activate_simple_shortcut_menu, + name="activate-simple-menu", + ), + url(r"hide-shortcut-menu/$", views.hide_shortcut_menu, name="hide-shortcut-menu"), + url(r"show-shortcut-menu/$", views.show_shortcut_menu, name="show-shortcut-menu"), + url( + r"regenerate-external-id/$", + views.regenerate_external_id, + name="regenerate-external-id", + ), + url( + r"document/search/(?P<step>.+)?$", + check_rights(["view_document", "view_own_document"])( + views.document_search_wizard + ), + name="search-document", + ), + url( + r"document/search/(?P<step>.+)?$", + check_rights(["view_document", "view_own_document"])( + views.document_search_wizard + ), + name="document_search", + ), + url( + r"document/create/$", + check_rights(["add_document", "add_own_document"])( + views.DocumentCreateView.as_view() + ), + name="create-document", + ), + url( + r"document/edit/$", + check_rights(["change_document", "change_own_document"])( + views.DocumentSelectView.as_view() + ), + name="edit-document", + ), + url( + r"document/edit/(?P<pk>.+)/$", + check_rights(["change_document", "change_own_document"])( + views.DocumentEditView.as_view() + ), + name="edit-document", + ), + url( + r"document/delete/(?P<step>.+)?$", + check_rights(["change_document", "change_own_document"])( + views.document_deletion_wizard + ), + name="document_deletion", + ), + url( + r"autocomplete-document/$", + views.autocomplete_document, + name="autocomplete-document", + ), + url( + r"document/shortcut/delete/(?P<pk>.+)/$", + views.document_delete, + name="delete-document", + ), + url( + r"^document-qa-bulk-update/(?P<pks>[0-9-]+)?/$", + check_rights(["change_document", "change_own_document"])( + views.QADocumentForm.as_view() + ), + name="document-qa-bulk-update", + ), + url( + r"^document-qa-bulk-update/(?P<pks>[0-9-]+)?/confirm/$", + check_rights(["change_document", "change_own_document"])( + views.QADocumentForm.as_view() + ), + name="document-qa-bulk-update-confirm", + kwargs={"confirm": True}, + ), + url( + r"^document-qa-duplicate/(?P<pks>[0-9-]+)?/$", + check_rights(["change_document", "change_own_document"])( + views.QADocumentDuplicateFormView.as_view() + ), + name="document-qa-duplicate", + ), + url( + r"^document-qa-packaging/(?P<pks>[0-9-]+)?/$", + check_rights(["change_document", "change_own_document"])( + views.QADocumentPackagingFormView.as_view() + ), + name="document-qa-packaging", + ), + url( + r"autocomplete-documenttag/$", + views.autocomplete_documenttag, + name="autocomplete-documenttag", + ), + url( + r"new-documenttag/(?:(?P<parent_name>[^/]+)/)?" r"(?:(?P<limits>[^/]+)/)?$", + views.new_document_tag, + name="new-documenttag", + ), + url( + r"^qa-not-available(?:/(?P<context>[0-9a-z-]+))?/$", + views.QANotAvailable.as_view(), + name="qa-not-available", + ), ] urlpatterns += get_urls_for_model(models.Document, views, own=True) urlpatterns += [ - url(r'(?P<action_slug>' + actions + r')/$', views.action, name='action'), + url(r"(?P<action_slug>" + actions + r")/$", views.action, name="action"), ] if settings.DEBUG: - urlpatterns += static(settings.STATIC_URL, - document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index b4542688b..fa24b3dcb 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2013-2016 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> @@ -62,11 +62,19 @@ from django.template.defaultfilters import slugify if settings.USE_TRANSLATION_OVERLOAD: - from overload_translation.utils import ugettext_lazy, ugettext, \ - pgettext_lazy, pgettext + from overload_translation.utils import ( + ugettext_lazy, + ugettext, + pgettext_lazy, + pgettext, + ) else: - from django.utils.translation import ugettext_lazy, ugettext, \ - pgettext_lazy, pgettext + from django.utils.translation import ( + ugettext_lazy, + ugettext, + pgettext_lazy, + pgettext, + ) _ = ugettext_lazy @@ -87,6 +95,7 @@ def debug_line_no(): def fake_task(*args): def fake(func): return func + return fake @@ -94,6 +103,7 @@ task = fake_task if settings.USE_BACKGROUND_TASK: try: from celery import shared_task + task = shared_task except ModuleNotFoundError: pass @@ -103,23 +113,24 @@ class BColors: """ Bash colors. Don't forget to finish your colored string with ENDC. """ - HEADER = '\033[95m' - OKBLUE = '\033[94m' - OKGREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' + + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" class Round(models.Func): - function = 'ROUND' + function = "ROUND" arity = 2 - arg_joiner = '::numeric, ' + arg_joiner = "::numeric, " -CSV_OPTIONS = {'delimiter': ',', 'quotechar': '"', 'quoting': QUOTE_ALL} +CSV_OPTIONS = {"delimiter": ",", "quotechar": '"', "quoting": QUOTE_ALL} def is_safe_path(basedir, path, follow_symlinks=True): @@ -134,22 +145,23 @@ def import_class(full_path_classname): """ Return the model class from the full path """ - mods = full_path_classname.split('.') + mods = full_path_classname.split(".") if len(mods) == 1: - mods = ['ishtar_common', 'models', mods[0]] - elif 'models' not in mods and 'models_finds' not in mods \ - and 'models_treatments' not in mods: - raise SuspiciousOperation( - "Try to import a non model from a string") - module = import_module('.'.join(mods[:-1])) + mods = ["ishtar_common", "models", mods[0]] + elif ( + "models" not in mods + and "models_finds" not in mods + and "models_treatments" not in mods + ): + raise SuspiciousOperation("Try to import a non model from a string") + module = import_module(".".join(mods[:-1])) model = getattr(module, mods[-1]) if not issubclass(model, models.Model): - raise SuspiciousOperation( - "Try to import a non model from a string") + raise SuspiciousOperation("Try to import a non model from a string") return model -def check_rights(rights=None, redirect_url='/'): +def check_rights(rights=None, redirect_url="/"): """ Decorator that checks the rights to access the view. """ @@ -158,25 +170,25 @@ def check_rights(rights=None, redirect_url='/'): def _wrapped_view(request, *args, **kwargs): if not rights: return view_func(request, *args, **kwargs) - if hasattr(request.user, 'ishtaruser'): - if request.user.ishtaruser.has_right('administrator', - request.session): - kwargs['current_right'] = 'administrator' + if hasattr(request.user, "ishtaruser"): + if request.user.ishtaruser.has_right("administrator", request.session): + kwargs["current_right"] = "administrator" return view_func(request, *args, **kwargs) for right in rights: # be careful to put the more permissive rights first # if granted it can allow more - if request.user.ishtaruser.has_right(right, - request.session): - kwargs['current_right'] = right + if request.user.ishtaruser.has_right(right, request.session): + kwargs["current_right"] = right return view_func(request, *args, **kwargs) put_session_message( request.session.session_key, _("You don't have sufficient permissions to do this action."), - 'warning' + "warning", ) return HttpResponseRedirect(redirect_url) + return _wrapped_view + return decorator @@ -184,14 +196,16 @@ def check_rights_condition(rights): """ To be used to check in wizard condition_dict """ + def func(self): request = self.request - if request.user.ishtaruser.has_right('administrator', request.session): + if request.user.ishtaruser.has_right("administrator", request.session): return True for right in rights: if request.user.ishtaruser.has_right(right, request.session): return True return False + return func @@ -211,13 +225,15 @@ def check_model_access_control(request, model, available_perms=None): return allowed, own if not available_perms: - available_perms = ['view_' + model.__name__.lower(), - 'view_own_' + model.__name__.lower()] + available_perms = [ + "view_" + model.__name__.lower(), + "view_own_" + model.__name__.lower(), + ] try: ishtaruser = request.user.ishtaruser except request.user._meta.model.ishtaruser.RelatedObjectDoesNotExist: return False, True - if ishtaruser.has_right('administrator', session=request.session): + if ishtaruser.has_right("administrator", session=request.session): allowed = True own = False return allowed, own @@ -266,12 +282,12 @@ def move_dict_data(data, key1, key2): "data__") :return: result data """ - keys1 = key1.split('__') - keys2 = key2.split('__') + keys1 = key1.split("__") + keys2 = key2.split("__") value = data for idx, key in enumerate(keys1): if not idx: - if key != 'data': + if key != "data": return data continue if key not in value: @@ -284,7 +300,7 @@ def move_dict_data(data, key1, key2): new_value = data for idx, key in enumerate(keys2): if not idx: - if key != 'data': + if key != "data": return data continue if idx == (len(keys2) - 1): # last @@ -333,10 +349,10 @@ def is_downloadable(url): """ h = requests.head(url, allow_redirects=True) header = h.headers - content_type = header.get('content-type') - if 'text' in content_type.lower(): + content_type = header.get("content-type") + if "text" in content_type.lower(): return False - if 'html' in content_type.lower(): + if "html" in content_type.lower(): return False return True @@ -348,21 +364,21 @@ def get_current_year(): def get_cache(cls, extra_args=tuple(), app_label=None): if not app_label: app_label = cls._meta.app_label - cache_key = "{}-{}-{}".format( - settings.PROJECT_SLUG, app_label, cls.__name__) + cache_key = "{}-{}-{}".format(settings.PROJECT_SLUG, app_label, cls.__name__) for arg in extra_args: if not arg: - cache_key += '-0' + cache_key += "-0" else: if type(arg) == dict: - cache_key += '-' + "_".join([str(arg[k]) for k in arg]) + cache_key += "-" + "_".join([str(arg[k]) for k in arg]) elif type(arg) in (list, tuple): - cache_key += '-' + "_".join([str(v) for v in arg]) + cache_key += "-" + "_".join([str(v) for v in arg]) else: - cache_key += '-' + str(arg) + cache_key += "-" + str(arg) cache_key = slugify(cache_key) - if not cache_key.endswith('_current_keys') \ - and hasattr(cls, '_add_cache_key_to_refresh'): + if not cache_key.endswith("_current_keys") and hasattr( + cls, "_add_cache_key_to_refresh" + ): cls._add_cache_key_to_refresh(extra_args) if len(cache_key) >= 250: m = hashlib.md5() @@ -372,9 +388,9 @@ def get_cache(cls, extra_args=tuple(), app_label=None): def force_cached_label_changed(sender, **kwargs): - if not kwargs.get('instance'): + if not kwargs.get("instance"): return - kwargs['instance']._cached_label_checked = False + kwargs["instance"]._cached_label_checked = False cached_label_changed(sender, **kwargs) @@ -384,10 +400,10 @@ class SecretaryRenderer(MainSecretaryRenderer): Overload _pack_document: obsolete files can be referenced - continue on null content for files """ - self.log.debug('packing document') + self.log.debug("packing document") zip_file = io.BytesIO() - zipdoc = zipfile.ZipFile(zip_file, 'a') + zipdoc = zipfile.ZipFile(zip_file, "a") for fname, content in files.items(): if isinstance(content, UndefinedSilently): continue @@ -395,21 +411,20 @@ class SecretaryRenderer(MainSecretaryRenderer): zipdoc.writestr(fname, content, zipfile.ZIP_DEFLATED) else: zipdoc.writestr(fname, content) - self.log.debug('Document packing completed') + self.log.debug("Document packing completed") return zip_file def serialize_args_for_tasks(sender, instance, kwargs, extra_kwargs=None): - if 'instance' in kwargs: - kwargs['instance'] = kwargs["instance"].pk + if "instance" in kwargs: + kwargs["instance"] = kwargs["instance"].pk sender = (sender._meta.app_label, sender._meta.object_name) if extra_kwargs: for kw in extra_kwargs: if getattr(instance, kw, None): kwargs[kw] = getattr(instance, kw) for k in list(kwargs.keys()): - if k in ["model", "signal", - "_cached_labels_bulk_update"] or kwargs[k] is None: + if k in ["model", "signal", "_cached_labels_bulk_update"] or kwargs[k] is None: kwargs.pop(k) continue if isinstance(kwargs[k], set): @@ -429,9 +444,9 @@ def deserialize_args_for_tasks(sender, kwargs, extra_kwargs=None): # waiting for it while not instance and retried < 6: if retried: - time.sleep(.5) - if sender.objects.filter(pk=kwargs['instance']).count(): - instance = sender.objects.get(pk=kwargs['instance']) + time.sleep(0.5) + if sender.objects.filter(pk=kwargs["instance"]).count(): + instance = sender.objects.get(pk=kwargs["instance"]) else: retried += 1 if not instance: @@ -444,8 +459,12 @@ def deserialize_args_for_tasks(sender, kwargs, extra_kwargs=None): EXTRA_KWARGS_TRIGGER = [ - "_cascade_change", "_cached_labels_bulk_update", "skip_history_when_saving", - "_post_saved_geo", "_search_updated", "_cached_label_checked" + "_cascade_change", + "_cached_labels_bulk_update", + "skip_history_when_saving", + "_post_saved_geo", + "_search_updated", + "_cached_label_checked", ] @@ -455,11 +474,11 @@ def cached_label_and_geo_changed(sender, **kwargs): def cached_label_changed(sender, **kwargs): - if not kwargs.get('instance'): + if not kwargs.get("instance"): return - instance = kwargs.get('instance') + instance = kwargs.get("instance") - if hasattr(instance, 'test_obj'): + if hasattr(instance, "test_obj"): instance.test_obj.reached(sender, **kwargs) # cache_key, value = get_cache( @@ -470,8 +489,11 @@ def cached_label_changed(sender, **kwargs): # return # cache.set(cache_key, True, settings.CACHE_TASK_TIMEOUT) - if not settings.USE_BACKGROUND_TASK or not instance.pk \ - or not sender.objects.filter(pk=instance.pk).count(): + if ( + not settings.USE_BACKGROUND_TASK + or not instance.pk + or not sender.objects.filter(pk=instance.pk).count() + ): # no background task or not yet fully saved return _cached_label_changed(sender, **kwargs) @@ -479,34 +501,34 @@ def cached_label_changed(sender, **kwargs): kwargs["cascade_change"] = True sender, kwargs = serialize_args_for_tasks( - sender, instance, kwargs, EXTRA_KWARGS_TRIGGER) + sender, instance, kwargs, EXTRA_KWARGS_TRIGGER + ) return _cached_label_changed.delay(sender, **kwargs) @task() def _cached_label_changed(sender, **kwargs): - sender, instance = deserialize_args_for_tasks(sender, kwargs, - EXTRA_KWARGS_TRIGGER) + sender, instance = deserialize_args_for_tasks(sender, kwargs, EXTRA_KWARGS_TRIGGER) if not instance: return - force_update = kwargs.get('force_update', False) + force_update = kwargs.get("force_update", False) if hasattr(instance, "need_update") and instance.need_update: force_update = True instance.skip_history_when_saving = True - if not force_update and getattr(instance, '_cached_label_checked', False): + if not force_update and getattr(instance, "_cached_label_checked", False): return if hasattr(instance, "refresh_cache"): instance.refresh_cache() instance._cached_label_checked = True - cached_labels = ['cached_label'] - if hasattr(instance, 'CACHED_LABELS'): + cached_labels = ["cached_label"] + if hasattr(instance, "CACHED_LABELS"): cached_labels = instance.CACHED_LABELS changed = [] for cached_label in cached_labels: - gen_func = '_generate_' + cached_label + gen_func = "_generate_" + cached_label if not hasattr(instance, gen_func): continue lbl = getattr(instance, gen_func)() @@ -520,26 +542,23 @@ def _cached_label_changed(sender, **kwargs): if changed: instance._search_updated = False - if hasattr(instance, '_cascade_change') and instance._cascade_change: + if hasattr(instance, "_cascade_change") and instance._cascade_change: instance.skip_history_when_saving = True - instance.__class__.objects.filter(pk=instance.pk).update( - **dict(changed)) + instance.__class__.objects.filter(pk=instance.pk).update(**dict(changed)) if (changed or not cached_labels) and hasattr(instance, "cascade_update"): instance.cascade_update() updated = False - if force_update or hasattr(instance, 'update_search_vector'): + if force_update or hasattr(instance, "update_search_vector"): updated = instance.update_search_vector() - if hasattr(instance, '_cached_labels_bulk_update'): + if hasattr(instance, "_cached_labels_bulk_update"): updated = instance._cached_labels_bulk_update() or updated - if not updated and hasattr(instance, '_get_associated_cached_labels'): + if not updated and hasattr(instance, "_get_associated_cached_labels"): for item in instance._get_associated_cached_labels(): item._cascade_change = True - if hasattr(instance, 'test_obj'): + if hasattr(instance, "test_obj"): item.test_obj = instance.test_obj cached_label_changed(item.__class__, instance=item) - cache_key, __ = get_cache( - sender, ["cached_label_changed", instance.pk] - ) + cache_key, __ = get_cache(sender, ["cached_label_changed", instance.pk]) cache.set(cache_key, None, settings.CACHE_TASK_TIMEOUT) if cached_labels: return getattr(instance, cached_labels[0], "") @@ -560,10 +579,10 @@ def regenerate_all_cached_labels(model): def shortify(lbl, number=20): SHORTIFY_STR = ugettext(" (...)") if not lbl: - lbl = '' + lbl = "" if len(lbl) <= number: return lbl - return lbl[:number - len(SHORTIFY_STR)] + SHORTIFY_STR + return lbl[: number - len(SHORTIFY_STR)] + SHORTIFY_STR def mode(array): @@ -578,9 +597,10 @@ def disable_for_loaddata(signal_handler): @wraps(signal_handler) def wrapper(*args, **kwargs): - if kwargs.get('raw'): + if kwargs.get("raw"): return signal_handler(*args, **kwargs) + return wrapper @@ -588,8 +608,7 @@ def _get_image_link(doc): from ishtar_common.models import IshtarSiteProfile # manage missing images - if not doc.thumbnail or not doc.thumbnail.url or not doc.image \ - or not doc.image.url: + if not doc.thumbnail or not doc.thumbnail.url or not doc.image or not doc.image.url: return "" item = None @@ -607,7 +626,8 @@ def _get_image_link(doc): if item.__class__.__name__ == "ArchaeologicalSite": item_class_name = str(IshtarSiteProfile.get_default_site_label()) - return mark_safe(""" + return mark_safe( + """ <div class="col col-lg-3"> <div class="card"> <div id="lightgallery-rand-img"> @@ -630,34 +650,38 @@ def _get_image_link(doc): <script type="text/javascript"> lightGallery(document.getElementById('lightgallery-rand-img')); </script>""".format( - doc.image.url, - doc.thumbnail.url, - item_class_name, - str(item), - reverse(item.SHOW_URL, args=[item.pk, '']), - str(_("Information")), - str(_("Load another random image?")))) + doc.image.url, + doc.thumbnail.url, + item_class_name, + str(item), + reverse(item.SHOW_URL, args=[item.pk, ""]), + str(_("Information")), + str(_("Load another random image?")), + ) + ) def get_random_item_image_link(request): from ishtar_common.models import Document - if not hasattr(request.user, 'ishtaruser'): - return '' + if not hasattr(request.user, "ishtaruser"): + return "" ishtar_user = request.user.ishtaruser - if not ishtar_user.has_right('ishtar_common.view_document', - session=request.session): - return '' + if not ishtar_user.has_right( + "ishtar_common.view_document", session=request.session + ): + return "" - q = Document.objects.filter( - thumbnail__isnull=False, - image__isnull=False - ).exclude(thumbnail='').exclude(image='') + q = ( + Document.objects.filter(thumbnail__isnull=False, image__isnull=False) + .exclude(thumbnail="") + .exclude(image="") + ) total = q.count() if not total: - return '' + return "" image_nb = random.randint(0, total - 1) return _get_image_link(q.all()[image_nb]) @@ -665,9 +689,9 @@ def get_random_item_image_link(request): def convert_coordinates_to_point(x, y, z=None, srid=4326): if z: - geom = GEOSGeometry('POINT({} {} {})'.format(x, y, z), srid=srid) + geom = GEOSGeometry("POINT({} {} {})".format(x, y, z), srid=srid) else: - geom = GEOSGeometry('POINT({} {})'.format(x, y), srid=srid) + geom = GEOSGeometry("POINT({} {})".format(x, y), srid=srid) if not geom.valid: raise forms.ValidationError(geom.valid_reason) return geom @@ -675,13 +699,13 @@ def convert_coordinates_to_point(x, y, z=None, srid=4326): def get_srid_obj_from_point(point): from ishtar_common.models import SpatialReferenceSystem + try: - return SpatialReferenceSystem.objects.get( - srid=int(point.srid)) + return SpatialReferenceSystem.objects.get(srid=int(point.srid)) except SpatialReferenceSystem.DoesNotExist: return SpatialReferenceSystem.objects.create( srid=int(point.srid), - auth_name='EPSG', + auth_name="EPSG", label="EPSG-{}".format(point.srid), txt_idx="epsg-{}".format(point.srid), ) @@ -691,7 +715,7 @@ def post_save_geo(sender, **kwargs): """ Convert raw x, y, z point to real geo field """ - if not kwargs.get('instance'): + if not kwargs.get("instance"): return # cache_key, value = get_cache( # sender, ["post_save_geo", kwargs['instance'].pk]) @@ -700,13 +724,14 @@ def post_save_geo(sender, **kwargs): # return # cache.set(cache_key, True, settings.CACHE_TASK_TIMEOUT) - instance = kwargs.get('instance') + instance = kwargs.get("instance") if hasattr(instance, "_no_geo_check") and instance._no_geo_check: return if not settings.USE_BACKGROUND_TASK: return _post_save_geo(sender, **kwargs) - sender, kwargs = serialize_args_for_tasks(sender, instance, - kwargs, EXTRA_KWARGS_TRIGGER) + sender, kwargs = serialize_args_for_tasks( + sender, instance, kwargs, EXTRA_KWARGS_TRIGGER + ) return _post_save_geo.delay(sender, **kwargs) @@ -718,18 +743,18 @@ def _post_save_geo(sender, **kwargs): profile = get_current_profile() if not profile.mapping: return - sender, instance = deserialize_args_for_tasks(sender, kwargs, - EXTRA_KWARGS_TRIGGER) + sender, instance = deserialize_args_for_tasks(sender, kwargs, EXTRA_KWARGS_TRIGGER) if not instance: return kls_name = instance.__class__.__name__ - if not profile.locate_warehouses and ("Container" in kls_name - or "Warehouse" in kls_name): + if not profile.locate_warehouses and ( + "Container" in kls_name or "Warehouse" in kls_name + ): return - if getattr(instance, '_post_saved_geo', False): + if getattr(instance, "_post_saved_geo", False): return # print(sender, "post_save_geo") @@ -739,24 +764,27 @@ def _post_save_geo(sender, **kwargs): current_source = str(instance.__class__._meta.verbose_name) modified = False - if hasattr(instance, 'multi_polygon') and not getattr( - instance, "DISABLE_POLYGONS", False): - if instance.multi_polygon_source_item and \ - instance.multi_polygon_source_item != current_source: # refetch + if hasattr(instance, "multi_polygon") and not getattr( + instance, "DISABLE_POLYGONS", False + ): + if ( + instance.multi_polygon_source_item + and instance.multi_polygon_source_item != current_source + ): # refetch instance.multi_polygon = None instance.multi_polygon_source = None modified = True if instance.multi_polygon and not instance.multi_polygon_source: # should be a db source - instance.multi_polygon_source = 'P' + instance.multi_polygon_source = "P" instance.multi_polygon_source_item = current_source - elif instance.multi_polygon_source != 'P': + elif instance.multi_polygon_source != "P": precise_poly = instance.get_precise_polygons() if precise_poly: poly, source_item = precise_poly instance.multi_polygon = poly - instance.multi_polygon_source = 'P' + instance.multi_polygon_source = "P" instance.multi_polygon_source_item = source_item modified = True elif profile.use_town_for_geo: @@ -765,21 +793,24 @@ def _post_save_geo(sender, **kwargs): poly, poly_source = poly if poly != instance.multi_polygon: instance.multi_polygon_source_item = poly_source - instance.multi_polygon_source = 'T' # town + instance.multi_polygon_source = "T" # town try: instance.multi_polygon = poly modified = True except TypeError: print(instance, instance.pk) - if (instance.point_source_item and - instance.point_source_item != current_source) or ( - instance.point_source == 'M'): # refetch + if ( + instance.point_source_item and instance.point_source_item != current_source + ) or ( + instance.point_source == "M" + ): # refetch csrs = instance.spatial_reference_system if instance.x and instance.y: new_point = GEOSGeometry( - 'POINT({} {})'.format(instance.x, instance.y), srid=csrs.srid) + "POINT({} {})".format(instance.x, instance.y), srid=csrs.srid + ) proj_point = instance.point_2d.transform(csrs.srid, clone=True) if new_point.distance(proj_point) < 0.01: instance.x, instance.y = None, None @@ -789,8 +820,9 @@ def _post_save_geo(sender, **kwargs): point = instance.point point_2d = instance.point_2d - if (point or point_2d) and instance.x is None and not \ - instance.point_source: # db source + if ( + (point or point_2d) and instance.x is None and not instance.point_source + ): # db source if point: current_point = point instance.z = point.z @@ -800,41 +832,48 @@ def _post_save_geo(sender, **kwargs): instance.y = current_point.y srs = get_srid_obj_from_point(current_point) instance.spatial_reference_system = srs - instance.point_source = 'P' + instance.point_source = "P" instance.point_source_item = current_source if not point_2d: instance.point_2d = convert_coordinates_to_point( - instance.point.x, instance.point.y, - srid=current_point.srid) + instance.point.x, instance.point.y, srid=current_point.srid + ) modified = True - elif instance.x and instance.y and \ - instance.spatial_reference_system and \ - instance.spatial_reference_system.auth_name == 'EPSG' and \ - instance.spatial_reference_system.srid != 0: + elif ( + instance.x + and instance.y + and instance.spatial_reference_system + and instance.spatial_reference_system.auth_name == "EPSG" + and instance.spatial_reference_system.srid != 0 + ): # form input or already precise try: point_2d = convert_coordinates_to_point( - instance.x, instance.y, - srid=instance.spatial_reference_system.srid) + instance.x, instance.y, srid=instance.spatial_reference_system.srid + ) except forms.ValidationError: return # irrelevant data in DB distance = 1 # arbitrary if point_2d and instance.point_2d: - distance = point_2d.transform( - 4326, clone=True).distance( - instance.point_2d.transform(4326, clone=True)) + distance = point_2d.transform(4326, clone=True).distance( + instance.point_2d.transform(4326, clone=True) + ) if instance.z: point = convert_coordinates_to_point( - instance.x, instance.y, instance.z, - srid=instance.spatial_reference_system.srid) + instance.x, + instance.y, + instance.z, + srid=instance.spatial_reference_system.srid, + ) # no change if distance inf to 1 mm - if distance >= 0.0001 and (point_2d != instance.point_2d - or point != instance.point): + if distance >= 0.0001 and ( + point_2d != instance.point_2d or point != instance.point + ): instance.point = point instance.point_2d = point_2d - instance.point_source = 'P' + instance.point_source = "P" instance.point_source_item = current_source modified = True else: @@ -845,7 +884,7 @@ def _post_save_geo(sender, **kwargs): point_2d, point, source_item = precise_points instance.point_2d = point_2d instance.point = point - instance.point_source = 'P' + instance.point_source = "P" instance.point_source_item = source_item instance.x = point_2d.x instance.y = point_2d.y @@ -856,16 +895,16 @@ def _post_save_geo(sender, **kwargs): modified = True else: centroid, source, point_source = None, None, None - if instance.multi_polygon and instance.multi_polygon_source == 'P': + if instance.multi_polygon and instance.multi_polygon_source == "P": source = current_source centroid = instance.multi_polygon.centroid - point_source = 'M' + point_source = "M" if not centroid and profile.use_town_for_geo: # try to get from # parent town_centroid = instance.get_town_centroid() if town_centroid: centroid, source = town_centroid - point_source = 'T' + point_source = "T" if centroid: instance.point_2d, instance.point_source_item = centroid, source instance.point = None @@ -892,34 +931,37 @@ def _post_save_geo(sender, **kwargs): instance.save() if hasattr(instance, "cascade_update"): instance.cascade_update() - cache_key, __ = get_cache( - sender, ["post_save_geo", instance.pk] - ) + cache_key, __ = get_cache(sender, ["post_save_geo", instance.pk]) cache.set(cache_key, None, settings.CACHE_TASK_TIMEOUT) return -def create_slug(model, name, slug_attr='slug', max_length=100): +def create_slug(model, name, slug_attr="slug", max_length=100): base_slug = slugify(name) slug = base_slug[:max_length] final_slug = None idx = 1 while not final_slug: - if slug and not model.objects.filter(**{slug_attr:slug}).exists(): + if slug and not model.objects.filter(**{slug_attr: slug}).exists(): final_slug = slug break - slug = base_slug[:(max_length - 1 - len(str(idx)))] + "-" + str(idx) + slug = base_slug[: (max_length - 1 - len(str(idx)))] + "-" + str(idx) idx += 1 return final_slug def get_all_field_names(model): - return list(set(chain.from_iterable( - (field.name, field.attname) if hasattr(field, 'attname') else ( - field.name,) - for field in model._meta.get_fields() - if not (field.many_to_one and field.related_model is None) - ))) + return list( + set( + chain.from_iterable( + (field.name, field.attname) + if hasattr(field, "attname") + else (field.name,) + for field in model._meta.get_fields() + if not (field.many_to_one and field.related_model is None) + ) + ) + ) def get_all_related_m2m_objects_with_model(model): @@ -932,16 +974,17 @@ def get_all_related_m2m_objects_with_model(model): def get_all_related_many_to_many_objects(model): return [ - f for f in model._meta.get_fields(include_hidden=True) + f + for f in model._meta.get_fields(include_hidden=True) if f.many_to_many and f.auto_created ] def get_all_related_objects(model): return [ - f for f in model._meta.get_fields() - if (f.one_to_many or f.one_to_one) - and f.auto_created and not f.concrete + f + for f in model._meta.get_fields() + if (f.one_to_many or f.one_to_one) and f.auto_created and not f.concrete ] @@ -972,24 +1015,23 @@ def merge_tsvectors(vectors): current_position = max_position for dct_member in vector.split(" "): - splitted = dct_member.split(':') + splitted = dct_member.split(":") key = ":".join(splitted[:-1]) positions = splitted[-1] key = key[1:-1] # remove quotes - positions = [int(pos) + current_position - for pos in positions.split(',')] + positions = [int(pos) + current_position for pos in positions.split(",")] if key in result_dict: result_dict[key] += positions else: result_dict[key] = positions # {'lamelie': [1, 42, 5]} => {'lamelie': "1,42,5"} - result_dict = {k: ",".join([str(val) for val in result_dict[k]]) - for k in result_dict} + result_dict = { + k: ",".join([str(val) for val in result_dict[k]]) for k in result_dict + } # {'lamelie': "1,5", "hagarde": "2", "regarde": "4"} => # "'lamelie':1,5 'hagarde':2 'regarde':4" - result = " ".join(["'{}':{}".format(k, result_dict[k]) - for k in result_dict]) + result = " ".join(["'{}':{}".format(k, result_dict[k]) for k in result_dict]) return result @@ -997,10 +1039,10 @@ def merge_tsvectors(vectors): def put_session_message(session_key, message, message_type): session = SessionStore(session_key=session_key) messages = [] - if 'messages' in session: - messages = session['messages'][:] + if "messages" in session: + messages = session["messages"][:] messages.append((str(message), message_type)) - session['messages'] = messages + session["messages"] = messages session.save() @@ -1019,7 +1061,7 @@ def get_session_var(session_key, key): def clean_session_cache(session): # clean session cache - cache_key_list = 'sessionlist-{}'.format(session.session_key) + cache_key_list = "sessionlist-{}".format(session.session_key) key_list = cache.get(cache_key_list, []) for key in key_list: cache.set(key, None, settings.CACHE_TIMEOUT) @@ -1039,7 +1081,7 @@ def get_field_labels_from_path(model, path): except: labels.append(key) continue - if hasattr(field, 'verbose_name'): + if hasattr(field, "verbose_name"): labels.append(field.verbose_name) else: labels.append(key) @@ -1051,19 +1093,20 @@ def create_default_areas(models=None, verbose=False): if not models: from ishtar_common.models import Area, Town, Department, State else: - Area = models['area'] - Town = models['town'] - Department = models['department'] - State = models['state'] + Area = models["area"] + Town = models["town"] + Department = models["department"] + State = models["state"] areas = {} idx = 0 for state in State.objects.all(): - slug = 'state-' + slugify(state.label) + slug = "state-" + slugify(state.label) area, created = Area.objects.get_or_create( - txt_idx=slug, defaults={'label': state.label}) - areas['state-{}'.format(state.pk)] = area + txt_idx=slug, defaults={"label": state.label} + ) + areas["state-{}".format(state.pk)] = area if created: idx += 1 if verbose: @@ -1071,15 +1114,16 @@ def create_default_areas(models=None, verbose=False): idx, idx2 = 0, 0 for dep in Department.objects.all(): - slug = 'dep-' + slugify(dep.label) + slug = "dep-" + slugify(dep.label) area, created = Area.objects.get_or_create( - txt_idx=slug, defaults={'label': dep.label}) - areas['dep-' + dep.number] = area + txt_idx=slug, defaults={"label": dep.label} + ) + areas["dep-" + dep.number] = area if created: idx += 1 if not dep.state_id: continue - state_slug = 'state-{}'.format(dep.state_id) + state_slug = "state-{}".format(dep.state_id) if state_slug not in areas: continue if area.parent and area.parent.pk == areas[state_slug].pk: @@ -1090,15 +1134,16 @@ def create_default_areas(models=None, verbose=False): if verbose: print( "* {} department areas added with {} associations to state".format( - idx, idx2) + idx, idx2 + ) ) idx = 0 for town in Town.objects.all(): if not town.numero_insee or len(town.numero_insee) != 5: continue - code_dep = 'dep-' + town.numero_insee[:2] - code_dep_dom = 'dep-' + town.numero_insee[:3] + code_dep = "dep-" + town.numero_insee[:2] + code_dep_dom = "dep-" + town.numero_insee[:3] if code_dep in areas: if not areas[code_dep].towns.filter(pk=town.pk).count(): areas[code_dep].towns.add(town) @@ -1112,9 +1157,17 @@ def create_default_areas(models=None, verbose=False): print("* {} town associated to department area".format(idx)) -def get_relations_for_graph(rel_model, obj_pk, above_relations=None, - equal_relations=None, treated=None, styles=None, - render_above=True, render_below=True, full=False): +def get_relations_for_graph( + rel_model, + obj_pk, + above_relations=None, + equal_relations=None, + treated=None, + styles=None, + render_above=True, + render_below=True, + full=False, +): """ Get all above and equal relations of an object (get all child and parent relations) @@ -1144,37 +1197,53 @@ def get_relations_for_graph(rel_model, obj_pk, above_relations=None, treated.append(obj_pk) for q, inverse in ( - (rel_model.objects.filter( - left_record_id=obj_pk, - relation_type__logical_relation__isnull=False), False), - (rel_model.objects.filter( - right_record_id=obj_pk, - relation_type__logical_relation__isnull=False), True)): - q = q.values("left_record_id",'right_record_id', - 'relation_type__logical_relation') + ( + rel_model.objects.filter( + left_record_id=obj_pk, relation_type__logical_relation__isnull=False + ), + False, + ), + ( + rel_model.objects.filter( + right_record_id=obj_pk, relation_type__logical_relation__isnull=False + ), + True, + ), + ): + q = q.values( + "left_record_id", "right_record_id", "relation_type__logical_relation" + ) get_above, get_below = render_above, render_below if inverse and (not render_above or not render_below): get_above, get_below = not render_above, not render_below for relation in q.all(): - logical_relation = relation['relation_type__logical_relation'] - left_record = relation['left_record_id'] - right_record = relation['right_record_id'] + logical_relation = relation["relation_type__logical_relation"] + left_record = relation["left_record_id"] + right_record = relation["right_record_id"] is_above, is_below = False, False if not logical_relation: continue - elif get_below and logical_relation == 'above' and \ - (left_record, right_record) not in above_relations: + elif ( + get_below + and logical_relation == "above" + and (left_record, right_record) not in above_relations + ): above_relations.append((left_record, right_record)) is_below = True - elif get_above and logical_relation == 'below' and \ - (right_record, left_record) not in above_relations: + elif ( + get_above + and logical_relation == "below" + and (right_record, left_record) not in above_relations + ): above_relations.append((right_record, left_record)) is_above = True - elif logical_relation == 'equal' and \ - (right_record, left_record) not in equal_relations and \ - (left_record, right_record) not in equal_relations: + elif ( + logical_relation == "equal" + and (right_record, left_record) not in equal_relations + and (left_record, right_record) not in equal_relations + ): equal_relations.append((left_record, right_record)) else: continue @@ -1183,27 +1252,40 @@ def get_relations_for_graph(rel_model, obj_pk, above_relations=None, other_record = left_record else: other_record = right_record - if get_above and get_below and not full and (is_below or - is_above): + if get_above and get_below and not full and (is_below or is_above): if (is_above and not inverse) or (is_below and inverse): ar, er, substyles = get_relations_for_graph( - rel_model, other_record, above_relations, - equal_relations, treated, styles, + rel_model, + other_record, + above_relations, + equal_relations, + treated, + styles, render_above=True, - render_below=False + render_below=False, ) else: ar, er, substyles = get_relations_for_graph( - rel_model, other_record, above_relations, - equal_relations, treated, styles, + rel_model, + other_record, + above_relations, + equal_relations, + treated, + styles, render_above=False, - render_below=True + render_below=True, ) else: ar, er, substyles = get_relations_for_graph( - rel_model, other_record, above_relations, equal_relations, - treated, styles, render_above=render_above, - render_below=render_below, full=full + rel_model, + other_record, + above_relations, + equal_relations, + treated, + styles, + render_above=render_above, + render_below=render_below, + full=full, ) styles.update(substyles) error_style = "color=red" @@ -1234,19 +1316,28 @@ def get_relations_for_graph(rel_model, obj_pk, above_relations=None, return above_relations, equal_relations, styles -def generate_relation_graph(obj, highlight_current=True, - render_above=True, render_below=True, - full=False, debug=False): +def generate_relation_graph( + obj, + highlight_current=True, + render_above=True, + render_below=True, + full=False, + debug=False, +): if not settings.DOT_BINARY: return model = obj.__class__ - rel_model = model._meta.get_field('right_relations').related_model + rel_model = model._meta.get_field("right_relations").related_model # get relations above_relations, equal_relations, styles = get_relations_for_graph( - rel_model, obj.pk, render_above=render_above, - render_below=render_below, full=full) + rel_model, + obj.pk, + render_above=render_above, + render_below=render_below, + full=full, + ) # generate dotfile dot_str = "digraph relations {\nnode [shape=box];\n" @@ -1258,13 +1349,10 @@ def generate_relation_graph(obj, highlight_current=True, if highlight_current: style += ',style=filled,fillcolor="#C6C0C0"' dot_str += 'item{}[{},href="{}"];\n'.format( - obj.pk, style, - reverse('display-item', - args=[model.SLUG, obj.pk]) + obj.pk, style, reverse("display-item", args=[model.SLUG, obj.pk]) ) rel_str += "}\n" - for list, directed in ((above_relations, True), - (equal_relations, False)): + for list, directed in ((above_relations, True), (equal_relations, False)): if directed: rel_str += "subgraph Dir {\n" else: @@ -1277,8 +1365,7 @@ def generate_relation_graph(obj, highlight_current=True, if left.pk == obj.pk and highlight_current: style += ',style=filled,fillcolor="#C6C0C0"' dot_str += 'item{}[{},href="{}"];\n'.format( - left.pk, style, - reverse('display-item', args=[model.SLUG, left.pk]) + left.pk, style, reverse("display-item", args=[model.SLUG, left.pk]) ) if right_pk not in described: described.append(right_pk) @@ -1287,22 +1374,24 @@ def generate_relation_graph(obj, highlight_current=True, if right.pk == obj.pk and highlight_current: style += ',style=filled,fillcolor="#C6C0C0"' dot_str += 'item{}[{},href="{}"];\n'.format( - right.pk, style, - reverse('display-item', args=[model.SLUG, right.pk]) + right.pk, + style, + reverse("display-item", args=[model.SLUG, right.pk]), ) if not directed: # on the same level rel_str += "{{rank = same; item{}; item{};}}\n".format( - left_pk, right_pk) + left_pk, right_pk + ) style = "" if (left_pk, right_pk) in styles: style = " [{}]".format(", ".join(styles[(left_pk, right_pk)])) - rel_str += 'item{} -> item{}{};\n'.format(left_pk, right_pk, style) + rel_str += "item{} -> item{}{};\n".format(left_pk, right_pk, style) rel_str += "}\n" dot_str += rel_str + "\n}" tempdir = tempfile.mkdtemp("-ishtardot") dot_name = tempdir + os.path.sep + "relations.dot" - with open(dot_name, 'w') as dot_file: + with open(dot_name, "w") as dot_file: dot_file.write(dot_str) if not render_above: @@ -1312,8 +1401,7 @@ def generate_relation_graph(obj, highlight_current=True, else: suffix = "" - if full and obj.MAIN_UP_MODEL_QUERY and getattr(obj, - obj.MAIN_UP_MODEL_QUERY): + if full and obj.MAIN_UP_MODEL_QUERY and getattr(obj, obj.MAIN_UP_MODEL_QUERY): obj = getattr(obj, obj.MAIN_UP_MODEL_QUERY) with open(dot_name, "r") as dot_file: @@ -1353,8 +1441,7 @@ def generate_relation_graph(obj, highlight_current=True, png_name = tempdir + os.path.sep + "relations.png" with open(png_name, "wb") as png_file: - svg2png(open(svg_tmp_name, 'rb').read(), - write_to=png_file) + svg2png(open(svg_tmp_name, "rb").read(), write_to=png_file) with open(png_name, "rb") as png_file: django_file = File(png_file) attr = "relation_bitmap_image" + suffix @@ -1391,58 +1478,79 @@ def create_default_json_fields(model): content_type = ContentType.objects.get_for_model(model) for key in keys: JsonDataField.objects.get_or_create( - content_type=content_type, key=key, + content_type=content_type, + key=key, defaults={ - 'name': " ".join(key.split('__')).capitalize(), - 'value_type': 'T', - 'display': False - } + "name": " ".join(key.split("__")).capitalize(), + "value_type": "T", + "display": False, + }, ) -def get_urls_for_model(model, views, own=False, autocomplete=False, - ): +def get_urls_for_model( + model, + views, + own=False, + autocomplete=False, +): """ Generate get and show url for a model """ urls = [ - url(r'show-{}(?:/(?P<pk>.+))?/(?P<type>.+)?$'.format(model.SLUG), - check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])( - getattr(views, 'show_' + model.SLUG)), - name="show-" + model.SLUG), - url(r'^display-{}/(?P<pk>.+)/$'.format(model.SLUG), - check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])( - getattr(views, 'display_' + model.SLUG)), - name='display-' + model.SLUG), + url( + r"show-{}(?:/(?P<pk>.+))?/(?P<type>.+)?$".format(model.SLUG), + check_rights(["view_" + model.SLUG, "view_own_" + model.SLUG])( + getattr(views, "show_" + model.SLUG) + ), + name="show-" + model.SLUG, + ), + url( + r"^display-{}/(?P<pk>.+)/$".format(model.SLUG), + check_rights(["view_" + model.SLUG, "view_own_" + model.SLUG])( + getattr(views, "display_" + model.SLUG) + ), + name="display-" + model.SLUG, + ), ] if own: urls += [ - url(r'get-{}/own/(?P<type>.+)?$'.format(model.SLUG), - check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])( - getattr(views, 'get_' + model.SLUG)), - name="get-own-" + model.SLUG, kwargs={'force_own': True}), + url( + r"get-{}/own/(?P<type>.+)?$".format(model.SLUG), + check_rights(["view_" + model.SLUG, "view_own_" + model.SLUG])( + getattr(views, "get_" + model.SLUG) + ), + name="get-own-" + model.SLUG, + kwargs={"force_own": True}, + ), ] urls += [ - url(r'get-{}/(?P<type>.+)?$'.format(model.SLUG), - check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])( - getattr(views, 'get_' + model.SLUG)), - name="get-" + model.SLUG), + url( + r"get-{}/(?P<type>.+)?$".format(model.SLUG), + check_rights(["view_" + model.SLUG, "view_own_" + model.SLUG])( + getattr(views, "get_" + model.SLUG) + ), + name="get-" + model.SLUG, + ), ] if autocomplete: urls += [ - url(r'autocomplete-{}/$'.format(model.SLUG), - check_rights(['view_' + model.SLUG, 'view_own_' + model.SLUG])( - getattr(views, 'autocomplete_' + model.SLUG)), - name='autocomplete-' + model.SLUG), + url( + r"autocomplete-{}/$".format(model.SLUG), + check_rights(["view_" + model.SLUG, "view_own_" + model.SLUG])( + getattr(views, "autocomplete_" + model.SLUG) + ), + name="autocomplete-" + model.SLUG, + ), ] return urls def m2m_historization_changed(sender, **kwargs): - obj = kwargs.get('instance', None) + obj = kwargs.get("instance", None) if not obj: return hist_values = obj.history_m2m or {} @@ -1454,11 +1562,11 @@ def m2m_historization_changed(sender, **kwargs): values.append(value.history_compress()) hist_values[attr] = values obj.history_m2m = hist_values - if getattr(obj, 'skip_history_when_saving', False): + if getattr(obj, "skip_history_when_saving", False): # assume the last modifier is good... q = obj.history.filter( history_modifier_id=obj.history_modifier_id, - ).order_by('-history_date', '-history_id') + ).order_by("-history_date", "-history_id") if q.count(): hist = q.all()[0] hist.history_m2m = hist_values @@ -1507,8 +1615,7 @@ def get_broken_links(path): target_path = os.readlink(path) # resolve relative symlinks if not os.path.isabs(target_path): - target_path = os.path.join(os.path.dirname(path), - target_path) + target_path = os.path.join(os.path.dirname(path), target_path) if not os.path.exists(target_path): yield path @@ -1539,12 +1646,12 @@ def simplify_name(full_path_name, check_existing=False, min_len=15): for m in regex.finditer(name): # get the last match match = m if match: - new_name = name.replace(match.group(), '') + new_name = name.replace(match.group(), "") full_new_name = os.sep.join([path, new_name + ext]) try: is_file = os.path.isfile(full_new_name) except UnicodeEncodeError: - is_file = os.path.isfile(full_new_name.encode('utf-8')) + is_file = os.path.isfile(full_new_name.encode("utf-8")) if not check_existing or not is_file: # do not take the place of another file name = new_name[:] @@ -1565,14 +1672,13 @@ def rename_and_simplify_media_name(full_path_name, rename=True): exists = os.path.exists(full_path_name) is_file = os.path.isfile(full_path_name) except UnicodeEncodeError: - full_path_name_unicode = full_path_name.encode('utf-8') + full_path_name_unicode = full_path_name.encode("utf-8") exists = os.path.exists(full_path_name_unicode) is_file = os.path.isfile(full_path_name_unicode) if not exists or not is_file: return full_path_name, False - path, current_name, name = simplify_name(full_path_name, - check_existing=True) + path, current_name, name = simplify_name(full_path_name, check_existing=True) if current_name == name: return full_path_name, False @@ -1595,8 +1701,9 @@ def get_file_fields(): return fields -def get_used_media(exclude=None, limit=None, - return_object_and_field=False, debug=False): +def get_used_media( + exclude=None, limit=None, return_object_and_field=False, debug=False +): """ Get media which are still used in models @@ -1617,28 +1724,32 @@ def get_used_media(exclude=None, limit=None, continue if limit and str(field) not in limit: continue - is_null = {'%s__isnull' % field.name: True} - is_empty = {'%s' % field.name: ''} + is_null = {"%s__isnull" % field.name: True} + is_empty = {"%s" % field.name: ""} storage = field.storage if debug: print("") - q = field.model.objects.values('id', field.name)\ - .exclude(**is_empty).exclude(**is_null) + q = ( + field.model.objects.values("id", field.name) + .exclude(**is_empty) + .exclude(**is_null) + ) ln = q.count() for idx, res in enumerate(q): value = res[field.name] if debug: - sys.stdout.write("* get_used_media {}: {}/{}\r".format( - field, idx, ln)) + sys.stdout.write("* get_used_media {}: {}/{}\r".format(field, idx, ln)) sys.stdout.flush() if value not in EMPTY_VALUES: if return_object_and_field: - media.append(( - field.model.objects.get(pk=res['id']), - field.name, - storage.path(value) - )) + media.append( + ( + field.model.objects.get(pk=res["id"]), + field.name, + storage.path(value), + ) + ) else: media.add(storage.path(value)) return media @@ -1662,15 +1773,17 @@ def get_all_media(exclude=None, debug=False): print("") for idx, name in enumerate(files): if debug: - sys.stdout.write("* get_all_media {} ({}/{}): {}/{}\r".format( - root.encode('utf-8'), idx_main, ln_full, idx, ln)) + sys.stdout.write( + "* get_all_media {} ({}/{}): {}/{}\r".format( + root.encode("utf-8"), idx_main, ln_full, idx, ln + ) + ) sys.stdout.flush() path = os.path.abspath(os.path.join(root, name)) relpath = os.path.relpath(path, settings.MEDIA_ROOT) in_exclude = False for e in exclude: - if re.match(r'^%s$' % re.escape(e).replace('\\*', '.*'), - relpath): + if re.match(r"^%s$" % re.escape(e).replace("\\*", ".*"), relpath): in_exclude = True break @@ -1678,8 +1791,11 @@ def get_all_media(exclude=None, debug=False): media.add(path) else: if debug: - sys.stdout.write("* get_all_media {} ({}/{})\r".format( - root.encode('utf-8'), idx_main, ln_full)) + sys.stdout.write( + "* get_all_media {} ({}/{})\r".format( + root.encode("utf-8"), idx_main, ln_full + ) + ) return media @@ -1763,24 +1879,27 @@ def try_fix_file(filename, make_copy=True, hard=False): """ filename = os.path.abspath(filename) path, current_name, simplified_ref_name = simplify_name( - filename, check_existing=False, min_len=10) + filename, check_existing=False, min_len=10 + ) try: dirs = list(sorted(os.listdir(path))) except UnicodeDecodeError: - dirs = list(sorted(os.listdir(path.encode('utf-8')))) + dirs = list(sorted(os.listdir(path.encode("utf-8")))) # check existing files in the path for f in dirs: - result = _try_copy(path, f, filename, simplified_ref_name, make_copy, - min_len=10) + result = _try_copy( + path, f, filename, simplified_ref_name, make_copy, min_len=10 + ) if result: return result if not hard: return for path, __, files in os.walk(settings.MEDIA_ROOT): for f in files: - result = _try_copy(path, f, filename, simplified_ref_name, - make_copy, min_len=10) + result = _try_copy( + path, f, filename, simplified_ref_name, make_copy, min_len=10 + ) if result: return result @@ -1797,34 +1916,34 @@ PARSE_JINJA_IF = re.compile("{% if ([^}]*)}") def _deduplicate(value): new_values = [] - for v in value.split('-'): + for v in value.split("-"): if v not in new_values: new_values.append(v) - return '-'.join(new_values) + return "-".join(new_values) FORMULA_FILTERS = { - 'upper': lambda x: x.upper(), - 'lower': lambda x: x.lower(), - 'capitalize': lambda x: x.capitalize(), - 'slug': lambda x: slugify(x), - 'deduplicate': _deduplicate + "upper": lambda x: x.upper(), + "lower": lambda x: x.lower(), + "capitalize": lambda x: x.capitalize(), + "slug": lambda x: slugify(x), + "deduplicate": _deduplicate, } def _update_gen_id_dct(item, dct, initial_key, fkey=None, filters=None): if not fkey: fkey = initial_key[:] - if fkey.startswith('settings__'): - dct[fkey] = getattr(settings, fkey[len('settings__'):]) or '' + if fkey.startswith("settings__"): + dct[fkey] = getattr(settings, fkey[len("settings__") :]) or "" return obj = item - for k in fkey.split('__'): + for k in fkey.split("__"): try: obj = getattr(obj, k) except (ObjectDoesNotExist, AttributeError): obj = None - if hasattr(obj, 'all') and hasattr(obj, 'count'): # query manager + if hasattr(obj, "all") and hasattr(obj, "count"): # query manager if not obj.count(): break obj = obj.all()[0] @@ -1833,7 +1952,7 @@ def _update_gen_id_dct(item, dct, initial_key, fkey=None, filters=None): if obj is None: break if obj is None: - dct[initial_key] = '' + dct[initial_key] = "" else: dct[initial_key] = str(obj) if filters: @@ -1867,13 +1986,14 @@ def get_generated_id(key, item): new_keys = [] for key in key_list: if key.startswith("not "): - key = key[len("not "):].strip() + key = key[len("not ") :].strip() key = key.split(".")[0] if " % " in key: keys = key.split(" % ")[1] keys = [ i.replace("(", "").replace(")", "").split("|")[0].strip() - for i in keys.split(",")] + for i in keys.split(",") + ] else: keys = [key] new_keys += keys @@ -1884,7 +2004,7 @@ def get_generated_id(key, item): return tpl.render(dct) for fkey in PARSE_FORMULA.findall(formula): - filtered = fkey.split('|') + filtered = fkey.split("|") initial_key = fkey[:] fkey = filtered[0] filters = [] @@ -1892,17 +2012,17 @@ def get_generated_id(key, item): if filtr in FORMULA_FILTERS: filters.append(FORMULA_FILTERS[filtr]) _update_gen_id_dct(item, dct, initial_key, fkey, filters=filters) - values = formula.format(**dct).split('||') + values = formula.format(**dct).split("||") value = values[0] for filtr in values[1:]: if filtr not in FORMULA_FILTERS: - value += '||' + filtr + value += "||" + filtr continue value = FORMULA_FILTERS[filtr](value) return value -PRIVATE_FIELDS = ('id', 'history_modifier', 'order', 'uuid') +PRIVATE_FIELDS = ("id", "history_modifier", "order", "uuid") def duplicate_item(item, user=None, data=None): @@ -1924,8 +2044,11 @@ def duplicate_item(item, user=None, data=None): new.save() # m2m fields - m2m = [field.name for field in model._meta.many_to_many - if field.name not in PRIVATE_FIELDS] + m2m = [ + field.name + for field in model._meta.many_to_many + if field.name not in PRIVATE_FIELDS + ] for field in m2m: for val in getattr(item, field).all(): if val not in getattr(new, field).all(): @@ -1935,8 +2058,7 @@ def duplicate_item(item, user=None, data=None): def get_image_path(instance, filename): # when using migrations instance is not a real ImageModel instance - if not hasattr(instance, '_get_image_path'): + if not hasattr(instance, "_get_image_path"): n = datetime.datetime.now() - return "upload/{}/{:02d}/{:02d}/{}".format( - n.year, n.month, n.day, filename) + return "upload/{}/{:02d}/{:02d}/{}".format(n.year, n.month, n.day, filename) return instance._get_image_path(filename) diff --git a/ishtar_common/utils_migrations.py b/ishtar_common/utils_migrations.py index 57d75077e..13ebf245e 100644 --- a/ishtar_common/utils_migrations.py +++ b/ishtar_common/utils_migrations.py @@ -11,8 +11,7 @@ from django.core.files import File from django.db import connection -def migrate_simple_image_to_m2m(base_model, image_model, rel_model, - verbose=False): +def migrate_simple_image_to_m2m(base_model, image_model, rel_model, verbose=False): missing, moved = 0, 0 for item in base_model.objects.all(): if not item.image: @@ -22,12 +21,10 @@ def migrate_simple_image_to_m2m(base_model, image_model, rel_model, try: image_instance.image.save( - os.path.basename(item.image.path), - File(open(item.image.path)) + os.path.basename(item.image.path), File(open(item.image.path)) ) image_instance.thumbnail.save( - os.path.basename(item.thumbnail.path), - File(open(item.thumbnail.path)) + os.path.basename(item.thumbnail.path), File(open(item.thumbnail.path)) ) except IOError: # image not on hard-drive @@ -55,26 +52,39 @@ def migrate_simple_image_to_m2m(base_model, image_model, rel_model, def migrate_images(apps, base_model, rel_model): - IshtarImage = apps.get_model('ishtar_common', 'IshtarImage') - Document = apps.get_model('ishtar_common', 'Document') - for image_rel in rel_model.objects.order_by('is_main').all(): + IshtarImage = apps.get_model("ishtar_common", "IshtarImage") + Document = apps.get_model("ishtar_common", "Document") + for image_rel in rel_model.objects.order_by("is_main").all(): image = IshtarImage.objects.get(pk=image_rel.image.pk) - doc = Document.objects.create(image=image.image, - thumbnail=image.thumbnail) + doc = Document.objects.create(image=image.image, thumbnail=image.thumbnail) item = base_model.objects.get(pk=image_rel.item.pk) item.documents.add(doc) def migrate_sources(apps, base_model, source_model, item_attr): - Document = apps.get_model('ishtar_common', 'Document') + Document = apps.get_model("ishtar_common", "Document") for source in source_model.objects.all(): doc = Document.objects.create() - for attr in ['title', 'index', 'external_id', 'reference', - 'internal_reference', 'source_type', 'support_type', - 'format_type', 'scale', 'associated_url', 'receipt_date', - 'creation_date', 'receipt_date_in_documentation', - 'item_number', 'description', 'comment', - 'additional_information', 'duplicate']: + for attr in [ + "title", + "index", + "external_id", + "reference", + "internal_reference", + "source_type", + "support_type", + "format_type", + "scale", + "associated_url", + "receipt_date", + "creation_date", + "receipt_date_in_documentation", + "item_number", + "description", + "comment", + "additional_information", + "duplicate", + ]: setattr(doc, attr, getattr(source, attr)) doc.save() for author in source.authors.all(): @@ -87,13 +97,11 @@ def reinit_last_modified(apps, app_name, models): for model_name in models: model = apps.get_model(app_name, model_name) try: - historical_model = apps.get_model( - app_name, 'Historical' + model_name) + historical_model = apps.get_model(app_name, "Historical" + model_name) except: continue for item in model.objects.all(): - q = historical_model.objects.filter( - id=item.pk).order_by('-history_date') + q = historical_model.objects.filter(id=item.pk).order_by("-history_date") if not q.count(): return edit_date = q.all()[0].history_date @@ -107,21 +115,22 @@ def reinit_last_modified(apps, app_name, models): def migrate_main_image(apps, app_name, model_name, verbose=False): model = apps.get_model(app_name, model_name) q = model.objects.filter(documents__image__isnull=False).exclude( - main_image__isnull=False) + main_image__isnull=False + ) ln = q.count() for idx, item in enumerate(q.all()): if verbose: if not idx: sys.stdout.write("\n") - sys.stdout.write(" * {}.{}: {}/{}\r".format(app_name, model_name, - idx + 1, ln)) + sys.stdout.write( + " * {}.{}: {}/{}\r".format(app_name, model_name, idx + 1, ln) + ) sys.stdout.flush() - q = item.documents.filter( - image__isnull=False).exclude(image='') + q = item.documents.filter(image__isnull=False).exclude(image="") if not q.count(): # no image continue # by default get the lowest pk - item.main_image = q.order_by('pk').all()[0] + item.main_image = q.order_by("pk").all()[0] item.skip_history_when_saving = True item._no_move = True item.save() @@ -141,9 +150,15 @@ def m2m_historization_init(obj): for hist in obj.history.all(): hist.history_m2m = hist_values d = hist.history_date - date = datetime.datetime(year=d.year, month=d.month, day=d.day, - hour=d.hour, minute=d.minute, second=d.second, - microsecond=d.microsecond) + date = datetime.datetime( + year=d.year, + month=d.month, + day=d.day, + hour=d.hour, + minute=d.minute, + second=d.second, + microsecond=d.microsecond, + ) hist.history_date = date hist.last_modified = date hist.save() @@ -151,9 +166,11 @@ def m2m_historization_init(obj): # not clean... but json fields seems to be not well managed by # cursor.execute cursor.execute( - "UPDATE \"" + obj.__class__._meta.db_table + "\" SET " - "history_m2m = '" + json.dumps(hist_values).replace("'", "''") + - "'::json WHERE id = %s", [obj.pk] + 'UPDATE "' + obj.__class__._meta.db_table + '" SET ' + "history_m2m = '" + + json.dumps(hist_values).replace("'", "''") + + "'::json WHERE id = %s", + [obj.pk], ) @@ -168,4 +185,5 @@ def set_uuid_helper(module, model_name): def set_uuid(apps, schema_editor): model = apps.get_model(module, model_name) migrate_uuid(model) + return set_uuid diff --git a/ishtar_common/utils_secretary.py b/ishtar_common/utils_secretary.py index b3f55de4f..9100dac15 100644 --- a/ishtar_common/utils_secretary.py +++ b/ishtar_common/utils_secretary.py @@ -25,7 +25,7 @@ def parse_value_unit(value): def replace_line_breaks(value): - return (value or "").replace('\r\n', '\n') + return (value or "").replace("\r\n", "\n") def capfirst_filter(value): @@ -61,7 +61,7 @@ def human_date_filter(value): value = datetime.strptime(value, "%Y-%m-%d") except ValueError: return "" - language_code = settings.LANGUAGE_CODE.split('-') + language_code = settings.LANGUAGE_CODE.split("-") language_code = language_code[0] + "_" + language_code[1].upper() for language_suffix in (".utf8", ""): try: @@ -72,7 +72,7 @@ def human_date_filter(value): return value.strftime(settings.DATE_FORMAT) -def splitpart(value, index, char=',', merge_end=False): +def splitpart(value, index, char=",", merge_end=False): if not value or not index: return "" splited = value.split(char) @@ -88,12 +88,12 @@ class IshtarSecretaryRenderer(Renderer): super(IshtarSecretaryRenderer, self).__init__(*args, **kwargs) self.media_callback = self.ishtar_media_loader self.media_path = settings.MEDIA_ROOT - self.environment.filters['human_date'] = human_date_filter - self.environment.filters['capfirst'] = capfirst_filter - self.environment.filters['lowerfirst'] = lowerfirst_filter - self.environment.filters['capitalize'] = capitalize_filter - self.environment.filters['replace_line_breaks'] = replace_line_breaks - self.environment.filters['splitpart'] = splitpart + self.environment.filters["human_date"] = human_date_filter + self.environment.filters["capfirst"] = capfirst_filter + self.environment.filters["lowerfirst"] = lowerfirst_filter + self.environment.filters["capitalize"] = capitalize_filter + self.environment.filters["replace_line_breaks"] = replace_line_breaks + self.environment.filters["splitpart"] = splitpart def ishtar_media_loader(self, media, *args, **kwargs): res = self.fs_loader(media, *args, **kwargs) @@ -101,65 +101,68 @@ class IshtarSecretaryRenderer(Renderer): return image_file, mime = res if "width" in kwargs: - kwargs['frame_attrs']['svg:width'] = kwargs["width"] + kwargs["frame_attrs"]["svg:width"] = kwargs["width"] if "height" in kwargs: - kwargs['frame_attrs']['svg:height'] = kwargs["height"] + kwargs["frame_attrs"]["svg:height"] = kwargs["height"] if "keep_ratio" in args: image = Image.open(image_file.name) - width, width_unit = parse_value_unit( - kwargs['frame_attrs']['svg:width']) - height, height_unit = parse_value_unit( - kwargs['frame_attrs']['svg:height']) + width, width_unit = parse_value_unit(kwargs["frame_attrs"]["svg:width"]) + height, height_unit = parse_value_unit(kwargs["frame_attrs"]["svg:height"]) if "height" not in kwargs and width: new_height = width * image.height / image.width - kwargs['frame_attrs']['svg:height'] = "{}{}".format( + kwargs["frame_attrs"]["svg:height"] = "{}{}".format( new_height, width_unit ) if "width" not in kwargs and height: new_width = height * image.width / image.height - kwargs['frame_attrs']['svg:width'] = "{}{}".format( + kwargs["frame_attrs"]["svg:width"] = "{}{}".format( new_width, height_unit ) return image_file, mime def _render_xml(self, xml_document, **kwargs): # Prepare the xml object to be processed by jinja2 - self.log.debug('Rendering XML object') + self.log.debug("Rendering XML object") template_string = "" try: self.template_images = dict() self._prepare_document_tags(xml_document) xml_source = xml_document.toxml() - xml_source = xml_source.encode('ascii', 'xmlcharrefreplace') + xml_source = xml_source.encode("ascii", "xmlcharrefreplace") jinja_template = self.environment.from_string( - self._unescape_entities(xml_source.decode('utf-8')) + self._unescape_entities(xml_source.decode("utf-8")) ) result = jinja_template.render(**kwargs) - final_xml = parseString(result.encode('ascii', 'xmlcharrefreplace')) + final_xml = parseString(result.encode("ascii", "xmlcharrefreplace")) if self.template_images: self.replace_images(final_xml) return final_xml except ExpatError as e: - if not 'result' in locals(): + if not "result" in locals(): result = xml_source ### changes try: - near = result.split('\n')[e.lineno -1][e.offset-200:e.offset+200] + near = result.split("\n")[e.lineno - 1][e.offset - 200 : e.offset + 200] except IndexError: near = "..." print(result) ### endchanges - raise ExpatError('ExpatError "%s" at line %d, column %d\nNear of: "[...]%s[...]"' % \ - (ErrorString(e.code), e.lineno, e.offset, near)) + raise ExpatError( + 'ExpatError "%s" at line %d, column %d\nNear of: "[...]%s[...]"' + % (ErrorString(e.code), e.lineno, e.offset, near) + ) except: - self.log.error('Error rendering template:\n%s', - xml_document.toprettyxml(), exc_info=True) + self.log.error( + "Error rendering template:\n%s", + xml_document.toprettyxml(), + exc_info=True, + ) - self.log.error('Unescaped template was:\n{0}'.format(template_string)) + self.log.error("Unescaped template was:\n{0}".format(template_string)) raise finally: - self.log.debug('Rendering xml object finished') + self.log.debug("Rendering xml object finished") diff --git a/ishtar_common/version.py b/ishtar_common/version.py index 2a795c085..4d1892603 100644 --- a/ishtar_common/version.py +++ b/ishtar_common/version.py @@ -3,7 +3,7 @@ VERSION = (3, 1, 4) def get_version(): - return '.'.join((str(num) for num in VERSION)) + return ".".join((str(num) for num in VERSION)) __version__ = get_version() diff --git a/ishtar_common/views.py b/ishtar_common/views.py index 6ae0c1f3b..4e9fff2da 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -37,14 +37,18 @@ from django.core.urlresolvers import reverse, NoReverseMatch from django.db.models import Q from django.template import loader from django.forms.models import modelformset_factory -from django.http import HttpResponse, Http404, HttpResponseRedirect, \ - HttpResponseBadRequest, JsonResponse +from django.http import ( + HttpResponse, + Http404, + HttpResponseRedirect, + HttpResponseBadRequest, + JsonResponse, +) from django.shortcuts import redirect, render, get_object_or_404 from django.utils.decorators import method_decorator from django.utils.translation import ugettext, ugettext_lazy as _ from django.views.generic import ListView, TemplateView, View -from django.views.generic.edit import CreateView, DeleteView, FormView, \ - UpdateView +from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from extra_views import ModelFormSetView from markdown import markdown @@ -52,8 +56,10 @@ from . import models from archaeological_context_records.models import ContextRecord from archaeological_files.forms import DashboardForm as DashboardFormFile from archaeological_files.models import File -from archaeological_finds.forms import DashboardTreatmentForm, \ - DashboardTreatmentFileForm +from archaeological_finds.forms import ( + DashboardTreatmentForm, + DashboardTreatmentFileForm, +) from archaeological_finds.models import Find, Treatment, TreatmentFile from archaeological_operations.forms import DashboardForm as DashboardFormOpe from archaeological_operations.models import Operation, ArchaeologicalSite @@ -63,31 +69,46 @@ from ishtar_common import wizards from ishtar_common.forms import FinalForm, FinalDeleteForm from ishtar_common.models import get_current_profile from ishtar_common.templatetags.link_to_window import simple_link_to_window -from ishtar_common.utils import clean_session_cache, CSV_OPTIONS, \ - get_field_labels_from_path, get_random_item_image_link, shortify, \ - dict_to_tuple, put_session_message, get_model_by_slug +from ishtar_common.utils import ( + clean_session_cache, + CSV_OPTIONS, + get_field_labels_from_path, + get_random_item_image_link, + shortify, + dict_to_tuple, + put_session_message, + get_model_by_slug, +) from ishtar_common.widgets import JQueryAutoComplete from ishtar_common import tasks -from .views_item import CURRENT_ITEM_KEYS, CURRENT_ITEM_KEYS_DICT, \ - check_permission, display_item, get_item, show_item, new_qa_item, \ - modify_qa_item, get_short_html_detail +from .views_item import ( + CURRENT_ITEM_KEYS, + CURRENT_ITEM_KEYS_DICT, + check_permission, + display_item, + get_item, + show_item, + new_qa_item, + modify_qa_item, + get_short_html_detail, +) logger = logging.getLogger(__name__) def status(request): - return HttpResponse('OK') + return HttpResponse("OK") def raise_error(request): - return 1/0 + return 1 / 0 def raise_task_error(request): if settings.USE_BACKGROUND_TASK: tasks.trigger_error.delay() - return HttpResponse('OK') + return HttpResponse("OK") def wizard_is_available(wizard, request, model, pk): @@ -97,7 +118,7 @@ def wizard_is_available(wizard, request, model, pk): put_session_message( request.session.session_key, _("You don't have sufficient permissions to do this action."), - 'warning' + "warning", ) return q = model.objects.filter(pk=pk) @@ -119,204 +140,240 @@ def index(request): """ Main page """ - dct = { - 'warnings': [], - "extra_form_modals": wizards.EXTRA_FORM_MODALS - } - if settings.PROJECT_SLUG == 'default': - dct['warnings'].append(_( - "PROJECT_SLUG is set to \"default\". Change it in your " - "local_settings (or ask your admin to do it).")) + dct = {"warnings": [], "extra_form_modals": wizards.EXTRA_FORM_MODALS} + if settings.PROJECT_SLUG == "default": + dct["warnings"].append( + _( + 'PROJECT_SLUG is set to "default". Change it in your ' + "local_settings (or ask your admin to do it)." + ) + ) profile = get_current_profile() - if profile.slug == 'default': - dct['warnings'].append(_( - "The slug of your current profile is set to \"default\". Change it " - "on the administration page (or ask your admin to do it).")) + if profile.slug == "default": + dct["warnings"].append( + _( + 'The slug of your current profile is set to "default". Change it ' + "on the administration page (or ask your admin to do it)." + ) + ) image = get_random_item_image_link(request) - if hasattr(profile, 'homepage') and profile.homepage: - dct['homepage'] = markdown(profile.homepage) - if '{random_image}' in dct['homepage']: - dct['homepage'] = dct['homepage'].replace( - '{random_image}', image) + if hasattr(profile, "homepage") and profile.homepage: + dct["homepage"] = markdown(profile.homepage) + if "{random_image}" in dct["homepage"]: + dct["homepage"] = dct["homepage"].replace("{random_image}", image) else: - dct['random_image'] = image + dct["random_image"] = image try: - return render(request, 'index.html', dct) + return render(request, "index.html", dct) except NoReverseMatch: # probably rights exception (rights revoked) logout(request) - return render(request, 'index.html', dct) + return render(request, "index.html", dct) + person_search_wizard = wizards.PersonSearch.as_view( - [('general-person_search', forms.PersonFormSelection)], + [("general-person_search", forms.PersonFormSelection)], label=_("Person search"), - url_name='person_search',) + url_name="person_search", +) person_creation_wizard = wizards.PersonWizard.as_view( - [('identity-person_creation', forms.SimplePersonForm), - ('person_type-person_creation', forms.PersonTypeForm), - ('final-person_creation', FinalForm)], + [ + ("identity-person_creation", forms.SimplePersonForm), + ("person_type-person_creation", forms.PersonTypeForm), + ("final-person_creation", FinalForm), + ], label=_("New person"), - url_name='person_creation') + url_name="person_creation", +) person_modification_wizard = wizards.PersonModifWizard.as_view( - [('selec-person_modification', forms.PersonFormSelection), - ('identity-person_modification', forms.SimplePersonForm), - ('person_type-person_creation', forms.PersonTypeForm), - ('final-person_modification', FinalForm)], + [ + ("selec-person_modification", forms.PersonFormSelection), + ("identity-person_modification", forms.SimplePersonForm), + ("person_type-person_creation", forms.PersonTypeForm), + ("final-person_modification", FinalForm), + ], label=_("Person modification"), - url_name='person_modification') + url_name="person_modification", +) def person_modify(request, pk): - if not wizard_is_available(person_modification_wizard, request, - models.Person, pk): + if not wizard_is_available(person_modification_wizard, request, models.Person, pk): return HttpResponseRedirect("/") wizards.PersonModifWizard.session_set_value( - request, 'selec-person_modification', 'pk', pk, reset=True) - return redirect(reverse('person_modification', - kwargs={'step': 'identity-person_modification'})) + request, "selec-person_modification", "pk", pk, reset=True + ) + return redirect( + reverse("person_modification", kwargs={"step": "identity-person_modification"}) + ) person_deletion_wizard = wizards.PersonDeletionWizard.as_view( - [('selec-person_deletion', forms.PersonFormMultiSelection), - ('final-person_deletion', FinalDeleteForm)], + [ + ("selec-person_deletion", forms.PersonFormMultiSelection), + ("final-person_deletion", FinalDeleteForm), + ], label=_("Person deletion"), - url_name='person_deletion',) + url_name="person_deletion", +) def person_delete(request, pk): - if not wizard_is_available(person_deletion_wizard, request, - models.Person, pk): + if not wizard_is_available(person_deletion_wizard, request, models.Person, pk): return HttpResponseRedirect("/") wizards.PersonDeletionWizard.session_set_value( - request, 'selec-person_deletion', 'pks', pk, reset=True) - return redirect(reverse('person_deletion', - kwargs={'step': 'final-person_deletion'})) + request, "selec-person_deletion", "pks", pk, reset=True + ) + return redirect( + reverse("person_deletion", kwargs={"step": "final-person_deletion"}) + ) + organization_search_wizard = wizards.OrganizationSearch.as_view( - [('general-organization_search', forms.OrganizationFormSelection)], + [("general-organization_search", forms.OrganizationFormSelection)], label=_("Organization search"), - url_name='organization_search',) + url_name="organization_search", +) organization_creation_wizard = wizards.OrganizationWizard.as_view( - [('identity-organization_creation', forms.OrganizationForm), - ('final-organization_creation', FinalForm)], + [ + ("identity-organization_creation", forms.OrganizationForm), + ("final-organization_creation", FinalForm), + ], label=_("New organization"), - url_name='organization_creation') + url_name="organization_creation", +) organization_modification_wizard = wizards.OrganizationModifWizard.as_view( - [('selec-organization_modification', forms.OrganizationFormSelection), - ('identity-organization_modification', forms.OrganizationForm), - ('final-organization_modification', FinalForm)], + [ + ("selec-organization_modification", forms.OrganizationFormSelection), + ("identity-organization_modification", forms.OrganizationForm), + ("final-organization_modification", FinalForm), + ], label=_("Organization modification"), - url_name='organization_modification') + url_name="organization_modification", +) def organization_modify(request, pk): - if not wizard_is_available(organization_modification_wizard, request, - models.Organization, pk): + if not wizard_is_available( + organization_modification_wizard, request, models.Organization, pk + ): return HttpResponseRedirect("/") wizards.OrganizationModifWizard.session_set_value( - request, 'selec-organization_modification', 'pk', pk, reset=True) + request, "selec-organization_modification", "pk", pk, reset=True + ) return redirect( - reverse('organization_modification', - kwargs={'step': 'identity-organization_modification'})) + reverse( + "organization_modification", + kwargs={"step": "identity-organization_modification"}, + ) + ) organization_deletion_wizard = wizards.OrganizationDeletionWizard.as_view( - [('selec-organization_deletion', forms.OrganizationFormMultiSelection), - ('final-organization_deletion', FinalDeleteForm)], + [ + ("selec-organization_deletion", forms.OrganizationFormMultiSelection), + ("final-organization_deletion", FinalDeleteForm), + ], label=_("Organization deletion"), - url_name='organization_deletion',) + url_name="organization_deletion", +) def organization_delete(request, pk): - if not wizard_is_available(organization_deletion_wizard, request, - models.Organization, pk): + if not wizard_is_available( + organization_deletion_wizard, request, models.Organization, pk + ): return HttpResponseRedirect("/") - wizard_url = 'organization_deletion' + wizard_url = "organization_deletion" wizards.OrganizationDeletionWizard.session_set_value( - request, 'selec-' + wizard_url, 'pks', pk, reset=True) - return redirect( - reverse(wizard_url, - kwargs={'step': 'final-' + wizard_url})) + request, "selec-" + wizard_url, "pks", pk, reset=True + ) + return redirect(reverse(wizard_url, kwargs={"step": "final-" + wizard_url})) + account_wizard_steps = [ - ('selec-account_management', forms.PersonUserFormSelection), - ('account-account_management', forms.AccountForm), - ('profile-account_management', forms.ProfileFormset), - ('final-account_management', forms.FinalAccountForm)] + ("selec-account_management", forms.PersonUserFormSelection), + ("account-account_management", forms.AccountForm), + ("profile-account_management", forms.ProfileFormset), + ("final-account_management", forms.FinalAccountForm), +] account_management_wizard = wizards.AccountWizard.as_view( account_wizard_steps, label=_("Account management"), - url_name='account_management',) + url_name="account_management", +) account_deletion_wizard = wizards.IshtarUserDeletionWizard.as_view( - [('selec-account_deletion', forms.AccountFormSelection), - ('final-account_deletion', FinalDeleteForm)], + [ + ("selec-account_deletion", forms.AccountFormSelection), + ("final-account_deletion", FinalDeleteForm), + ], label=_("Account deletion"), - url_name='account_deletion',) + url_name="account_deletion", +) def get_autocomplete_generic(model, extra=None): if not extra: - extra = {'available': True} + extra = {"available": True} def func(request): - q = request.GET.get('term') + q = request.GET.get("term") query = Q(**extra) if not q: q = "" - for q in q.split(' '): + for q in q.split(" "): if not q: continue query = query & Q(label__icontains=q) limit = 20 objects = model.objects.filter(query).distinct()[:limit] - get_label = lambda x: x.full_label() if hasattr(x, 'full_label') \ - else str(x) - data = json.dumps([{'id': obj.pk, 'value': get_label(obj)} - for obj in objects]) - return HttpResponse(data, content_type='text/plain') + get_label = lambda x: x.full_label() if hasattr(x, "full_label") else str(x) + data = json.dumps([{"id": obj.pk, "value": get_label(obj)} for obj in objects]) + return HttpResponse(data, content_type="text/plain") + return func def hide_shortcut_menu(request): - request.session['SHORTCUT_SHOW'] = 'off' - return HttpResponse('OK', content_type='text/plain') + request.session["SHORTCUT_SHOW"] = "off" + return HttpResponse("OK", content_type="text/plain") def show_shortcut_menu(request): - request.session['SHORTCUT_SHOW'] = 'on' - return HttpResponse('OK', content_type='text/plain') + request.session["SHORTCUT_SHOW"] = "on" + return HttpResponse("OK", content_type="text/plain") def activate_all_search(request): - request.session['SHORTCUT_SEARCH'] = 'all' - return HttpResponse('OK', content_type='text/plain') + request.session["SHORTCUT_SEARCH"] = "all" + return HttpResponse("OK", content_type="text/plain") def activate_own_search(request): - request.session['SHORTCUT_SEARCH'] = 'own' - return HttpResponse('OK', content_type='text/plain') + request.session["SHORTCUT_SEARCH"] = "own" + return HttpResponse("OK", content_type="text/plain") def activate_advanced_shortcut_menu(request): - if not hasattr(request.user, 'ishtaruser'): - return HttpResponse('KO', content_type='text/plain') + if not hasattr(request.user, "ishtaruser"): + return HttpResponse("KO", content_type="text/plain") request.user.ishtaruser.advanced_shortcut_menu = True request.user.ishtaruser.save() - return HttpResponse('OK', content_type='text/plain') + return HttpResponse("OK", content_type="text/plain") def activate_simple_shortcut_menu(request): - if not hasattr(request.user, 'ishtaruser'): - return HttpResponse('KO', content_type='text/plain') + if not hasattr(request.user, "ishtaruser"): + return HttpResponse("KO", content_type="text/plain") request.user.ishtaruser.advanced_shortcut_menu = False request.user.ishtaruser.save() - return HttpResponse('OK', content_type='text/plain') + return HttpResponse("OK", content_type="text/plain") def shortcut_menu(request): @@ -336,21 +393,25 @@ def shortcut_menu(request): if profile.warehouse: CURRENT_ITEMS.append((_("Treatment request"), TreatmentFile)) CURRENT_ITEMS.append((_("Treatment"), Treatment)) - if hasattr(request.user, 'ishtaruser') and \ - request.user.ishtaruser.advanced_shortcut_menu: + if ( + hasattr(request.user, "ishtaruser") + and request.user.ishtaruser.advanced_shortcut_menu + ): dct = { - 'current_menu': [], 'menu': [], - 'SHORTCUT_SEARCH': request.session['SHORTCUT_SEARCH'] - if 'SHORTCUT_SEARCH' in request.session else 'own', - 'SHORTCUT_SHOW': request.session['SHORTCUT_SHOW'] - if 'SHORTCUT_SHOW' in request.session else 'on' + "current_menu": [], + "menu": [], + "SHORTCUT_SEARCH": request.session["SHORTCUT_SEARCH"] + if "SHORTCUT_SEARCH" in request.session + else "own", + "SHORTCUT_SHOW": request.session["SHORTCUT_SHOW"] + if "SHORTCUT_SHOW" in request.session + else "on", } current_selected_labels = [] for lbl, model in CURRENT_ITEMS: model_name = model.SLUG - current = model_name in request.session \ - and request.session[model_name] + current = model_name in request.session and request.session[model_name] if current: try: item = model.objects.get(pk=int(current)) @@ -358,21 +419,27 @@ def shortcut_menu(request): current_selected_labels.append(item_label) except model.DoesNotExist: pass - dct['menu'].append(( - lbl, model_name, current or 0, - JQueryAutoComplete( - reverse('get-' + model.SLUG + '-shortcut'), - model).render( - model.SLUG + '-shortcut', value=current, - attrs={'id': 'current_' + model.SLUG}))) - dct['current_selected_labels'] = current_selected_labels - return render( - request, 'ishtar/blocks/advanced_shortcut_menu.html', dct - ) + dct["menu"].append( + ( + lbl, + model_name, + current or 0, + JQueryAutoComplete( + reverse("get-" + model.SLUG + "-shortcut"), model + ).render( + model.SLUG + "-shortcut", + value=current, + attrs={"id": "current_" + model.SLUG}, + ), + ) + ) + dct["current_selected_labels"] = current_selected_labels + return render(request, "ishtar/blocks/advanced_shortcut_menu.html", dct) dct = { - 'current_menu': [], - 'SHORTCUT_SHOW': request.session['SHORTCUT_SHOW'] - if 'SHORTCUT_SHOW' in request.session else 'off' + "current_menu": [], + "SHORTCUT_SHOW": request.session["SHORTCUT_SHOW"] + if "SHORTCUT_SHOW" in request.session + else "off", } current_selected_labels = [] current_selected_item = {} @@ -380,7 +447,7 @@ def shortcut_menu(request): for lbl, model in CURRENT_ITEMS: new_selected_item = None model_name = model.SLUG - cls = '' + cls = "" current = model_name in request.session and request.session[model_name] items = [] current_items = [] @@ -388,14 +455,21 @@ def shortcut_menu(request): lbl_key = "cached_label" if model.SLUG == "warehouse": lbl_key = "name" - values = ['id', lbl_key] - - owns = model.get_owns( - request.user, menu_filtr=current_selected_item, - limit=100, values=values, get_short_menu_class=True) or [] + values = ["id", lbl_key] + + owns = ( + model.get_owns( + request.user, + menu_filtr=current_selected_item, + limit=100, + values=values, + get_short_menu_class=True, + ) + or [] + ) for item, shortmenu_class in owns: - pk = str(item['id']) - if shortmenu_class == 'basket': + pk = str(item["id"]) + if shortmenu_class == "basket": pk = "basket-" + pk # prevent duplicates if pk in current_items: @@ -407,8 +481,7 @@ def shortcut_menu(request): cls = shortmenu_class new_selected_item = pk labels[model_name][str(pk)] = item_label - items.append((pk, item_label, - selected, shortmenu_class)) + items.append((pk, item_label, selected, shortmenu_class)) # selected is not in owns - add it to the list if not new_selected_item and current: try: @@ -416,19 +489,21 @@ def shortcut_menu(request): new_selected_item = item.pk item_label = shortify(str(item), 60) labels[model_name][str(item.pk)] = item_label - items.append((item.pk, item_label, - True, item.get_short_menu_class(item.pk))) + items.append( + (item.pk, item_label, True, item.get_short_menu_class(item.pk)) + ) except (model.DoesNotExist, ValueError): pass if items: - dct['current_menu'].append((lbl, model_name, cls, items)) + dct["current_menu"].append((lbl, model_name, cls, items)) if new_selected_item: current_selected_item[model_name] = new_selected_item if str(new_selected_item) in labels[model_name]: current_selected_labels.append( - labels[model_name][str(new_selected_item)]) - dct['current_selected_labels'] = current_selected_labels - return render(request, 'ishtar/blocks/shortcut_menu.html', dct) + labels[model_name][str(new_selected_item)] + ) + dct["current_selected_labels"] = current_selected_labels + return render(request, "ishtar/blocks/shortcut_menu.html", dct) def get_current_items(request): @@ -446,219 +521,241 @@ def get_current_items(request): def unpin(request, item_type, cascade=False): if item_type not in CURRENT_ITEM_KEYS_DICT.keys(): logger.warning("unpin unknow type: {}".format(item_type)) - return HttpResponse('nok') - if 'administrativeact' in item_type: - request.session[item_type] = '' - return HttpResponse('ok') - request.session['treatment'] = '' - if item_type == 'treatment' and not cascade: - return HttpResponse('ok') - request.session['treatmentfile'] = '' - if item_type == 'treatmentfile' and not cascade: - return HttpResponse('ok') - request.session['find'] = '' - if item_type == 'find' and not cascade: - return HttpResponse('ok') - request.session['warehouse'] = '' - if item_type == 'warehouse' and not cascade: - return HttpResponse('ok') - request.session['contextrecord'] = '' - if item_type == 'contextrecord' and not cascade: - return HttpResponse('ok') - request.session['site'] = '' - if item_type == 'site' and not cascade: - return HttpResponse('ok') - request.session['operation'] = '' - if item_type == 'operation' and not cascade: - return HttpResponse('ok') - request.session['file'] = '' - if item_type == 'file' and not cascade: - return HttpResponse('ok') + return HttpResponse("nok") + if "administrativeact" in item_type: + request.session[item_type] = "" + return HttpResponse("ok") + request.session["treatment"] = "" + if item_type == "treatment" and not cascade: + return HttpResponse("ok") + request.session["treatmentfile"] = "" + if item_type == "treatmentfile" and not cascade: + return HttpResponse("ok") + request.session["find"] = "" + if item_type == "find" and not cascade: + return HttpResponse("ok") + request.session["warehouse"] = "" + if item_type == "warehouse" and not cascade: + return HttpResponse("ok") + request.session["contextrecord"] = "" + if item_type == "contextrecord" and not cascade: + return HttpResponse("ok") + request.session["site"] = "" + if item_type == "site" and not cascade: + return HttpResponse("ok") + request.session["operation"] = "" + if item_type == "operation" and not cascade: + return HttpResponse("ok") + request.session["file"] = "" + if item_type == "file" and not cascade: + return HttpResponse("ok") def update_current_item(request, item_type=None, pk=None): if not item_type or not pk: - if not request.is_ajax() and not request.method == 'POST': + if not request.is_ajax() and not request.method == "POST": raise Http404 - item_type = request.POST['item'] - if 'value' in request.POST and 'item' in request.POST: - request.session[item_type] = request.POST['value'] + item_type = request.POST["item"] + if "value" in request.POST and "item" in request.POST: + request.session[item_type] = request.POST["value"] else: request.session[item_type] = str(pk) - request.session['SHORTCUT_SEARCH'] = 'all' + request.session["SHORTCUT_SEARCH"] = "all" currents = get_current_items(request) # re-init when descending item are not relevant - if item_type == 'file' and currents['file'] and currents['operation'] and \ - currents['operation'].associated_file != currents['file']: - request.session["operation"] = '' - currents['operation'] = None - - if item_type in ('operation', 'file') and currents['contextrecord'] and \ - (not request.session.get("operation", None) or - currents['contextrecord'].operation != currents['operation']): - request.session["contextrecord"] = '' - currents['contextrecord'] = None + if ( + item_type == "file" + and currents["file"] + and currents["operation"] + and currents["operation"].associated_file != currents["file"] + ): + request.session["operation"] = "" + currents["operation"] = None + + if ( + item_type in ("operation", "file") + and currents["contextrecord"] + and ( + not request.session.get("operation", None) + or currents["contextrecord"].operation != currents["operation"] + ) + ): + request.session["contextrecord"] = "" + currents["contextrecord"] = None from archaeological_finds.models import Find - if item_type in ('contextrecord', 'operation', 'file') and \ - currents['find'] and \ - (not request.session.get("contextrecord", None) or - not Find.objects.filter( - downstream_treatment__isnull=True, - base_finds__context_record__pk=request.session["contextrecord"], - pk=currents['find'].pk).count()): - request.session["find"] = '' - currents['find'] = None + + if ( + item_type in ("contextrecord", "operation", "file") + and currents["find"] + and ( + not request.session.get("contextrecord", None) + or not Find.objects.filter( + downstream_treatment__isnull=True, + base_finds__context_record__pk=request.session["contextrecord"], + pk=currents["find"].pk, + ).count() + ) + ): + request.session["find"] = "" + currents["find"] = None # re-init ascending item with relevant values - if item_type == "find" and currents['find']: + if item_type == "find" and currents["find"]: from archaeological_context_records.models import ContextRecord - q = ContextRecord.objects.filter( - base_finds__find__pk=currents['find'].pk) + + q = ContextRecord.objects.filter(base_finds__find__pk=currents["find"].pk) if q.count(): - currents['contextrecord'] = q.all()[0] - request.session['contextrecord'] = str( - currents['contextrecord'].pk) - if item_type in ("find", 'contextrecord') and currents['contextrecord']: - currents['operation'] = currents['contextrecord'].operation - request.session['operation'] = str(currents['operation'].pk) - if item_type in ("find", 'contextrecord', 'operation') and \ - currents['operation']: - currents['file'] = currents['operation'].associated_file - request.session['file'] = str(currents['file'].pk) if currents['file'] \ - else None - return HttpResponse('ok') + currents["contextrecord"] = q.all()[0] + request.session["contextrecord"] = str(currents["contextrecord"].pk) + if item_type in ("find", "contextrecord") and currents["contextrecord"]: + currents["operation"] = currents["contextrecord"].operation + request.session["operation"] = str(currents["operation"].pk) + if item_type in ("find", "contextrecord", "operation") and currents["operation"]: + currents["file"] = currents["operation"].associated_file + request.session["file"] = str(currents["file"].pk) if currents["file"] else None + return HttpResponse("ok") def pin_search(request, item_type): key = "pin-search-" + item_type if not item_type or not ( - request.is_ajax() and request.method == 'POST' - and 'value' in request.POST): + request.is_ajax() and request.method == "POST" and "value" in request.POST + ): raise Http404 - request.session[key] = request.POST['value'] - if not request.POST['value']: + request.session[key] = request.POST["value"] + if not request.POST["value"]: # empty all unpin(request, item_type, cascade=True) else: unpin(request, item_type) - return HttpResponse('ok') + return HttpResponse("ok") -def get_by_importer(request, slug, data_type='json', full=False, - force_own=False, **dct): +def get_by_importer( + request, slug, data_type="json", full=False, force_own=False, **dct +): q = models.ImporterType.objects.filter(slug=slug) if not q.count(): - res = '' + res = "" if data_type == "json": - res = '{}' - return HttpResponse(res, content_type='text/plain') + res = "{}" + return HttpResponse(res, content_type="text/plain") imp = q.all()[0].get_importer_class() cols, col_names = [], [] for formater in imp.LINE_EXPORT_FORMAT: if not formater: - cols.append('') + cols.append("") col_names.append("") continue cols.append(formater.export_field_name) col_names.append(formater.label) obj_name = imp.OBJECT_CLS.__name__.lower() - return get_item( - imp.OBJECT_CLS, 'get_' + obj_name, obj_name, own_table_cols=cols - )(request, data_type, full, force_own, col_names=col_names, **dct) + return get_item(imp.OBJECT_CLS, "get_" + obj_name, obj_name, own_table_cols=cols)( + request, data_type, full, force_own, col_names=col_names, **dct + ) -def autocomplete_person_permissive(request, person_types=None, - attached_to=None, is_ishtar_user=None): +def autocomplete_person_permissive( + request, person_types=None, attached_to=None, is_ishtar_user=None +): return autocomplete_person( - request, person_types=person_types, attached_to=attached_to, - is_ishtar_user=is_ishtar_user, permissive=True) + request, + person_types=person_types, + attached_to=attached_to, + is_ishtar_user=is_ishtar_user, + permissive=True, + ) def autocomplete_user(request): - if not request.user.has_perm('ishtar_common.view_person', models.Person): - return HttpResponse('[]', content_type='text/plain') - q = request.GET.get('term') - limit = request.GET.get('limit', 20) + if not request.user.has_perm("ishtar_common.view_person", models.Person): + return HttpResponse("[]", content_type="text/plain") + q = request.GET.get("term") + limit = request.GET.get("limit", 20) try: limit = int(limit) except ValueError: return HttpResponseBadRequest() query = Q() - for q in q.split(' '): - qu = (Q(ishtaruser__person__name__icontains=q) | - Q(ishtaruser__person__surname__icontains=q) | - Q(first_name__icontains=q) | - Q(last_name__icontains=q)) + for q in q.split(" "): + qu = ( + Q(ishtaruser__person__name__icontains=q) + | Q(ishtaruser__person__surname__icontains=q) + | Q(first_name__icontains=q) + | Q(last_name__icontains=q) + ) query = query & qu users = models.User.objects.filter(query).distinct()[:limit] values = [] for user in users: try: if user and user.ishtaruser: - values.append({'id': user.pk, 'value': str(user.ishtaruser)}) + values.append({"id": user.pk, "value": str(user.ishtaruser)}) except models.User.ishtaruser.RelatedObjectDoesNotExist: pass data = json.dumps(values) - return HttpResponse(data, content_type='text/plain') + return HttpResponse(data, content_type="text/plain") def autocomplete_ishtaruser(request): - if not request.user.has_perm('ishtar_common.view_person', models.Person): - return HttpResponse('[]', content_type='text/plain') - q = request.GET.get('term', '') - limit = request.GET.get('limit', 20) + if not request.user.has_perm("ishtar_common.view_person", models.Person): + return HttpResponse("[]", content_type="text/plain") + q = request.GET.get("term", "") + limit = request.GET.get("limit", 20) try: limit = int(limit) except ValueError: return HttpResponseBadRequest() query = Q() - for q in q.split(' '): - qu = (Q(person__name__unaccent__icontains=q) | - Q(person__surname__unaccent__icontains=q) | - Q(person__raw_name__unaccent__icontains=q)) + for q in q.split(" "): + qu = ( + Q(person__name__unaccent__icontains=q) + | Q(person__surname__unaccent__icontains=q) + | Q(person__raw_name__unaccent__icontains=q) + ) query = query & qu users = models.IshtarUser.objects.filter(query).distinct()[:limit] - data = json.dumps([ - {'id': user.pk, - 'value': str(user)} - for user in users]) - return HttpResponse(data, content_type='text/plain') + data = json.dumps([{"id": user.pk, "value": str(user)} for user in users]) + return HttpResponse(data, content_type="text/plain") -def autocomplete_person(request, person_types=None, attached_to=None, - is_ishtar_user=None, permissive=False): - all_items = request.user.has_perm('ishtar_common.view_person', - models.Person) +def autocomplete_person( + request, person_types=None, attached_to=None, is_ishtar_user=None, permissive=False +): + all_items = request.user.has_perm("ishtar_common.view_person", models.Person) own_items = False if not all_items: - own_items = request.user.has_perm('ishtar_common.view_own_person', - models.Person) - if not all_items and not own_items or not request.GET.get('term'): - return HttpResponse('[]', content_type='text/plain') - q = request.GET.get('term') - limit = request.GET.get('limit', 20) + own_items = request.user.has_perm( + "ishtar_common.view_own_person", models.Person + ) + if not all_items and not own_items or not request.GET.get("term"): + return HttpResponse("[]", content_type="text/plain") + q = request.GET.get("term") + limit = request.GET.get("limit", 20) try: limit = int(limit) except ValueError: return HttpResponseBadRequest() query = Q() - for q in q.split(' '): - qu = (Q(name__unaccent__icontains=q) | - Q(surname__unaccent__icontains=q) | - Q(email__unaccent__icontains=q) | - Q(attached_to__name__unaccent__icontains=q)) + for q in q.split(" "): + qu = ( + Q(name__unaccent__icontains=q) + | Q(surname__unaccent__icontains=q) + | Q(email__unaccent__icontains=q) + | Q(attached_to__name__unaccent__icontains=q) + ) if permissive: qu = qu | Q(raw_name__unaccent__icontains=q) query = query & qu if attached_to: - query = query & Q(attached_to__pk__in=attached_to.split('_')) + query = query & Q(attached_to__pk__in=attached_to.split("_")) - if person_types and str(person_types) != '0': + if person_types and str(person_types) != "0": try: - typs = [int(tp) for tp in person_types.split('_') if tp] + typs = [int(tp) for tp in person_types.split("_") if tp] typ = models.PersonType.objects.filter(pk__in=typs).all() query = query & Q(person_types__in=typ) except (ValueError, ObjectDoesNotExist): @@ -666,58 +763,59 @@ def autocomplete_person(request, person_types=None, attached_to=None, if is_ishtar_user: query = query & Q(ishtaruser__isnull=False) if own_items: - if not hasattr(request.user, 'ishtaruser'): - return HttpResponse(json.dumps([]), content_type='text/plain') + if not hasattr(request.user, "ishtaruser"): + return HttpResponse(json.dumps([]), content_type="text/plain") query &= models.Person.get_query_owns(request.user.ishtaruser) persons = models.Person.objects.filter(query).distinct()[:limit] - data = json.dumps([{'id': person.pk, 'value': str(person)} - for person in persons if person]) - return HttpResponse(data, content_type='text/plain') + data = json.dumps( + [{"id": person.pk, "value": str(person)} for person in persons if person] + ) + return HttpResponse(data, content_type="text/plain") def autocomplete_department(request): - if not request.GET.get('term'): - return HttpResponse('[]', content_type='text/plain') - q = request.GET.get('term') - q = unicodedata.normalize("NFKD", q).encode('ascii', 'ignore').decode() + if not request.GET.get("term"): + return HttpResponse("[]", content_type="text/plain") + q = request.GET.get("term") + q = unicodedata.normalize("NFKD", q).encode("ascii", "ignore").decode() query = Q() - for q in q.split(' '): - extra = (Q(label__icontains=q) | Q(number__istartswith=q)) + for q in q.split(" "): + extra = Q(label__icontains=q) | Q(number__istartswith=q) query = query & extra limit = 20 departments = models.Department.objects.filter(query).distinct()[:limit] - data = json.dumps([{'id': department.pk, 'value': str(department)} - for department in departments]) - return HttpResponse(data, content_type='text/plain') + data = json.dumps( + [{"id": department.pk, "value": str(department)} for department in departments] + ) + return HttpResponse(data, content_type="text/plain") def autocomplete_town(request): - if not request.GET.get('term'): - return HttpResponse(content_type='text/plain') - q = request.GET.get('term') - q = unicodedata.normalize("NFKD", q).encode('ascii', 'ignore').decode() + if not request.GET.get("term"): + return HttpResponse(content_type="text/plain") + q = request.GET.get("term") + q = unicodedata.normalize("NFKD", q).encode("ascii", "ignore").decode() query = Q() - for q in q.split(' '): + for q in q.split(" "): extra = Q(name__icontains=q) - if settings.COUNTRY == 'fr': + if settings.COUNTRY == "fr": extra |= Q(numero_insee__istartswith=q) query &= extra limit = 20 towns = models.Town.objects.filter(query).distinct()[:limit] - data = json.dumps([{'id': town.pk, 'value': str(town)} - for town in towns]) - return HttpResponse(data, content_type='text/plain') + data = json.dumps([{"id": town.pk, "value": str(town)} for town in towns]) + return HttpResponse(data, content_type="text/plain") def autocomplete_advanced_town(request, department_id=None, state_id=None): - if not request.GET.get('term'): - return HttpResponse(content_type='text/plain') - q = request.GET.get('term') - q = unicodedata.normalize("NFKD", q).encode('ascii', 'ignore').decode() + if not request.GET.get("term"): + return HttpResponse(content_type="text/plain") + q = request.GET.get("term") + q = unicodedata.normalize("NFKD", q).encode("ascii", "ignore").decode() query = Q() - for q in q.split(' '): + for q in q.split(" "): extra = Q(name__icontains=q) - if settings.COUNTRY == 'fr': + if settings.COUNTRY == "fr": extra = extra | Q(numero_insee__istartswith=q) if not department_id: extra = extra | Q(departement__label__istartswith=q) @@ -731,123 +829,138 @@ def autocomplete_advanced_town(request, department_id=None, state_id=None): result = [] for town in towns: val = town.name - if hasattr(town, 'numero_insee'): + if hasattr(town, "numero_insee"): val += " (%s)" % town.numero_insee - result.append({'id': town.pk, 'value': val}) + result.append({"id": town.pk, "value": val}) data = json.dumps(result) - return HttpResponse(data, content_type='text/plain') + return HttpResponse(data, content_type="text/plain") def autocomplete_document(request): - if not request.GET.get('term'): - return HttpResponse(content_type='text/plain') - q = request.GET.get('term') - q = unicodedata.normalize("NFKD", q).encode('ascii', 'ignore').decode() - fields = ["title__icontains", "reference__icontains", - "internal_reference__icontains", "isbn__icontains", - "authors__person__cached_label__icontains", - "authors_raw__icontains"] + if not request.GET.get("term"): + return HttpResponse(content_type="text/plain") + q = request.GET.get("term") + q = unicodedata.normalize("NFKD", q).encode("ascii", "ignore").decode() + fields = [ + "title__icontains", + "reference__icontains", + "internal_reference__icontains", + "isbn__icontains", + "authors__person__cached_label__icontains", + "authors_raw__icontains", + ] query = None - for q in q.split(' '): + for q in q.split(" "): qu = Q(**{fields[0]: q}) for field in fields[1:]: qu |= Q(**{field: q}) query = qu if not query else query & qu limit = 20 - items = models.Document.objects.filter( - query).exclude(title="").distinct()[:limit] - data = json.dumps([{'id': item.pk, 'value': str(item)} - for item in items]) - return HttpResponse(data, content_type='text/plain') + items = models.Document.objects.filter(query).exclude(title="").distinct()[:limit] + data = json.dumps([{"id": item.pk, "value": str(item)} for item in items]) + return HttpResponse(data, content_type="text/plain") -def department_by_state(request, state_id=''): +def department_by_state(request, state_id=""): if not state_id: data = [] else: departments = models.Department.objects.filter(state__number=state_id) - data = json.dumps([{'id': department.pk, 'number': department.number, - 'value': str(department)} - for department in departments]) - return HttpResponse(data, content_type='text/plain') + data = json.dumps( + [ + { + "id": department.pk, + "number": department.number, + "value": str(department), + } + for department in departments + ] + ) + return HttpResponse(data, content_type="text/plain") def autocomplete_organization(request, orga_type=None): - if (not request.user.has_perm('ishtar_common.view_organization', - models.Organization) and - not request.user.has_perm('ishtar_common.view_own_organization', - models.Organization) - and not request.user.ishtaruser.has_right( - 'person_search', session=request.session)): - return HttpResponse('[]', content_type='text/plain') - if not request.GET.get('term'): - return HttpResponse('[]', content_type='text/plain') - q = request.GET.get('term') + if ( + not request.user.has_perm( + "ishtar_common.view_organization", models.Organization + ) + and not request.user.has_perm( + "ishtar_common.view_own_organization", models.Organization + ) + and not request.user.ishtaruser.has_right( + "person_search", session=request.session + ) + ): + return HttpResponse("[]", content_type="text/plain") + if not request.GET.get("term"): + return HttpResponse("[]", content_type="text/plain") + q = request.GET.get("term") query = Q() - for q in q.split(' '): + for q in q.split(" "): extra = Q(cached_label__unaccent__icontains=q) query = query & extra if orga_type: try: - typs = [int(tp) for tp in orga_type.split('_') if tp] + typs = [int(tp) for tp in orga_type.split("_") if tp] typ = models.OrganizationType.objects.filter(pk__in=typs).all() query = query & Q(organization_type__in=typ) except (ValueError, ObjectDoesNotExist): pass limit = 15 organizations = models.Organization.objects.filter(query).distinct()[:limit] - data = json.dumps([{'id': org.pk, 'value': str(org)} - for org in organizations]) - return HttpResponse(data, content_type='text/plain') + data = json.dumps([{"id": org.pk, "value": str(org)} for org in organizations]) + return HttpResponse(data, content_type="text/plain") def autocomplete_author(request): - if not request.user.has_perm('ishtar_common.view_author', models.Author)\ - and not request.user.has_perm('ishtar_common.view_own_author', - models.Author): - return HttpResponse('[]', content_type='text/plain') - if not request.GET.get('term'): - return HttpResponse('[]', content_type='text/plain') - q = request.GET.get('term') + if not request.user.has_perm( + "ishtar_common.view_author", models.Author + ) and not request.user.has_perm("ishtar_common.view_own_author", models.Author): + return HttpResponse("[]", content_type="text/plain") + if not request.GET.get("term"): + return HttpResponse("[]", content_type="text/plain") + q = request.GET.get("term") query = Q() - for q in q.split(' '): - extra = Q(person__name__icontains=q) | \ - Q(person__surname__icontains=q) | \ - Q(person__email__icontains=q) | \ - Q(author_type__label__icontains=q) + for q in q.split(" "): + extra = ( + Q(person__name__icontains=q) + | Q(person__surname__icontains=q) + | Q(person__email__icontains=q) + | Q(author_type__label__icontains=q) + ) query = query & extra limit = 15 authors = models.Author.objects.filter(query).distinct()[:limit] - data = json.dumps([{'id': author.pk, 'value': str(author)} - for author in authors]) - return HttpResponse(data, content_type='text/plain') + data = json.dumps([{"id": author.pk, "value": str(author)} for author in authors]) + return HttpResponse(data, content_type="text/plain") -new_person = new_qa_item(models.Person, forms.PersonForm, - page_name=_("New person")) +new_person = new_qa_item(models.Person, forms.PersonForm, page_name=_("New person")) modify_person = modify_qa_item(models.Person, forms.PersonForm) detail_person = get_short_html_detail(models.Person) -new_person_noorga = new_qa_item(models.Person, forms.NoOrgaPersonForm, - page_name=_("New person")) -new_organization = new_qa_item(models.Organization, forms.OrganizationForm, - page_name=_("New organization")) -show_organization = show_item(models.Organization, 'organization') -get_organization = get_item(models.Organization, 'get_organization', - 'organization') +new_person_noorga = new_qa_item( + models.Person, forms.NoOrgaPersonForm, page_name=_("New person") +) +new_organization = new_qa_item( + models.Organization, forms.OrganizationForm, page_name=_("New organization") +) +show_organization = show_item(models.Organization, "organization") +get_organization = get_item(models.Organization, "get_organization", "organization") modify_organization = modify_qa_item(models.Organization, forms.OrganizationForm) detail_organization = get_short_html_detail(models.Organization) -new_author = new_qa_item(models.Author, forms.AuthorForm, - page_name=_("New author")) -show_person = show_item(models.Person, 'person') +new_author = new_qa_item(models.Author, forms.AuthorForm, page_name=_("New author")) +show_person = show_item(models.Person, "person") -get_person = get_item(models.Person, 'get_person', 'person') +get_person = get_item(models.Person, "get_person", "person") get_person_for_account = get_item( models.Person, - 'get_person', 'person', - own_table_cols=models.Person.TABLE_COLS_ACCOUNT) + "get_person", + "person", + own_table_cols=models.Person.TABLE_COLS_ACCOUNT, +) -get_ishtaruser = get_item(models.IshtarUser, 'get_ishtaruser', 'ishtaruser') +get_ishtaruser = get_item(models.IshtarUser, "get_ishtaruser", "ishtaruser") def action(request, action_slug, obj_id=None, *args, **kwargs): @@ -857,12 +970,12 @@ def action(request, action_slug, obj_id=None, *args, **kwargs): if not check_permission(request, action_slug, obj_id): not_permitted_msg = ugettext("Operation not permitted.") return HttpResponse(not_permitted_msg) - request.session['CURRENT_ACTION'] = action_slug + request.session["CURRENT_ACTION"] = action_slug dct = {} globals_dct = globals() if action_slug in globals_dct: return globals_dct[action_slug](request, dct, obj_id, *args, **kwargs) - return render(request, 'index.html', dct) + return render(request, "index.html", dct) def dashboard_main(request, dct, obj_id=None, *args, **kwargs): @@ -872,23 +985,24 @@ def dashboard_main(request, dct, obj_id=None, *args, **kwargs): app_list = [] profile = models.get_current_profile() if profile.files: - app_list.append((_("Archaeological files"), 'files')) - app_list.append((_("Operations"), 'operations')) + app_list.append((_("Archaeological files"), "files")) + app_list.append((_("Operations"), "operations")) if profile.context_record: - app_list.append((_("Context records"), 'contextrecords')) + app_list.append((_("Context records"), "contextrecords")) if profile.find: - app_list.append((_("Finds"), 'finds')) + app_list.append((_("Finds"), "finds")) if profile.warehouse: - app_list.append((_("Treatment requests"), 'treatmentfiles')) - app_list.append((_("Treatments"), 'treatments')) - dct = {'app_list': app_list} - return render(request, 'ishtar/dashboards/dashboard_main.html', dct) + app_list.append((_("Treatment requests"), "treatmentfiles")) + app_list.append((_("Treatments"), "treatments")) + dct = {"app_list": app_list} + return render(request, "ishtar/dashboards/dashboard_main.html", dct) DASHBOARD_FORMS = { - 'files': DashboardFormFile, 'operations': DashboardFormOpe, - 'treatments': DashboardTreatmentForm, - 'treatmentfiles': DashboardTreatmentFileForm + "files": DashboardFormFile, + "operations": DashboardFormOpe, + "treatments": DashboardTreatmentForm, + "treatmentfiles": DashboardTreatmentFileForm, } @@ -896,89 +1010,101 @@ def dashboard_main_detail(request, item_name): """ Specific tab of the main dashboard """ - if item_name == 'users': - dct = {'ishtar_users': models.UserDashboard()} + if item_name == "users": + dct = {"ishtar_users": models.UserDashboard()} return render( - request, 'ishtar/dashboards/dashboard_main_detail_users.html', dct) + request, "ishtar/dashboards/dashboard_main_detail_users.html", dct + ) form = None - slicing, date_source, fltr, show_detail = 'year', None, {}, False + slicing, date_source, fltr, show_detail = "year", None, {}, False profile = models.get_current_profile() - has_form = (item_name == 'files' and profile.files) \ - or item_name == 'operations' \ - or (item_name in ('treatmentfiles', 'treatments') - and profile.warehouse) + has_form = ( + (item_name == "files" and profile.files) + or item_name == "operations" + or (item_name in ("treatmentfiles", "treatments") and profile.warehouse) + ) if has_form: - slicing = 'month' + slicing = "month" if item_name in DASHBOARD_FORMS: - if request.method == 'POST': + if request.method == "POST": form = DASHBOARD_FORMS[item_name](request.POST) if form.is_valid(): - slicing = form.cleaned_data['slicing'] + slicing = form.cleaned_data["slicing"] fltr = form.get_filter() - if hasattr(form, 'get_date_source'): + if hasattr(form, "get_date_source"): date_source = form.get_date_source() - if hasattr(form, 'get_show_detail'): + if hasattr(form, "get_show_detail"): show_detail = form.get_show_detail() else: form = DASHBOARD_FORMS[item_name]() lbl, dashboard = None, None dashboard_kwargs = {} if has_form: - dashboard_kwargs = {'slice': slicing, 'fltr': fltr, - 'show_detail': show_detail} + dashboard_kwargs = {"slice": slicing, "fltr": fltr, "show_detail": show_detail} # date_source is only relevant when the form has set one if date_source: - dashboard_kwargs['date_source'] = date_source - if item_name == 'files' and profile.files: - lbl, dashboard = (_("Archaeological files"), - models.Dashboard(File, **dashboard_kwargs)) - elif item_name == 'operations': + dashboard_kwargs["date_source"] = date_source + if item_name == "files" and profile.files: + lbl, dashboard = ( + _("Archaeological files"), + models.Dashboard(File, **dashboard_kwargs), + ) + elif item_name == "operations": from archaeological_operations.models import Operation - lbl, dashboard = (_("Operations"), - models.Dashboard(Operation, **dashboard_kwargs)) - elif item_name == 'contextrecords' and profile.context_record: + + lbl, dashboard = ( + _("Operations"), + models.Dashboard(Operation, **dashboard_kwargs), + ) + elif item_name == "contextrecords" and profile.context_record: lbl, dashboard = ( _("Context records"), - models.Dashboard(ContextRecord, slice=slicing, fltr=fltr)) - elif item_name == 'finds' and profile.find: - lbl, dashboard = (_("Finds"), models.Dashboard(Find, - slice=slicing, - fltr=fltr)) - elif item_name == 'treatmentfiles' and profile.warehouse: + models.Dashboard(ContextRecord, slice=slicing, fltr=fltr), + ) + elif item_name == "finds" and profile.find: + lbl, dashboard = (_("Finds"), models.Dashboard(Find, slice=slicing, fltr=fltr)) + elif item_name == "treatmentfiles" and profile.warehouse: lbl, dashboard = ( _("Treatment requests"), - models.Dashboard(TreatmentFile, **dashboard_kwargs)) - elif item_name == 'treatments' and profile.warehouse: - if 'date_source' not in dashboard_kwargs: - dashboard_kwargs['date_source'] = 'start' + models.Dashboard(TreatmentFile, **dashboard_kwargs), + ) + elif item_name == "treatments" and profile.warehouse: + if "date_source" not in dashboard_kwargs: + dashboard_kwargs["date_source"] = "start" lbl, dashboard = ( _("Treatments"), - models.Dashboard(Treatment, **dashboard_kwargs)) + models.Dashboard(Treatment, **dashboard_kwargs), + ) if not lbl: raise Http404 - dct = {'lbl': lbl, 'dashboard': dashboard, - 'item_name': item_name.replace('-', '_'), - 'VALUE_QUOTE': '' if slicing == "year" else "'", - 'form': form, 'slicing': slicing} + dct = { + "lbl": lbl, + "dashboard": dashboard, + "item_name": item_name.replace("-", "_"), + "VALUE_QUOTE": "" if slicing == "year" else "'", + "form": form, + "slicing": slicing, + } n = datetime.datetime.now() - dct['unique_id'] = dct['item_name'] + "_" + \ - '%d_%d_%d' % (n.minute, n.second, n.microsecond) - return render(request, 'ishtar/dashboards/dashboard_main_detail.html', dct) + dct["unique_id"] = ( + dct["item_name"] + "_" + "%d_%d_%d" % (n.minute, n.second, n.microsecond) + ) + return render(request, "ishtar/dashboards/dashboard_main_detail.html", dct) def reset_wizards(request): # dynamically execute each reset_wizards of each ishtar app for app in settings.INSTALLED_APPS: - if app == 'ishtar_common': + if app == "ishtar_common": # no need for infinite recursion continue try: module = __import__(app) except ImportError: continue - if hasattr(module, 'views') and hasattr(module.views, 'reset_wizards'): + if hasattr(module, "views") and hasattr(module.views, "reset_wizards"): module.views.reset_wizards(request) - return redirect(reverse('start')) + return redirect(reverse("start")) ITEM_PER_PAGE = 20 @@ -986,42 +1112,46 @@ ITEM_PER_PAGE = 20 def merge_action(model, form, key, name_key="name"): def merge(request, page=1): - current_url = key + '_merge' + current_url = key + "_merge" if not page: page = 1 page = int(page) FormSet = modelformset_factory( - model.merge_candidate.through, form=form, - formset=forms.MergeFormSet, extra=0) + model.merge_candidate.through, + form=form, + formset=forms.MergeFormSet, + extra=0, + ) q = model.merge_candidate.through.objects count = q.count() max_page = count // ITEM_PER_PAGE if count % ITEM_PER_PAGE != 0: max_page += 1 - context = {'current_url': current_url, - 'current_page': page, - 'max_page': max_page} + context = { + "current_url": current_url, + "current_page": page, + "max_page": max_page, + } if page < max_page: - context['next_page'] = page + 1 + context["next_page"] = page + 1 if page > 1: - context['previous_page'] = page - 1 + context["previous_page"] = page - 1 item_nb = (page - 1) * ITEM_PER_PAGE item_nb_1 = item_nb + ITEM_PER_PAGE - from_key = 'from_' + key - to_key = 'to_' + key - queryset = q.all().order_by( - from_key + '__' + name_key)[item_nb:item_nb_1] + from_key = "from_" + key + to_key = "to_" + key + queryset = q.all().order_by(from_key + "__" + name_key)[item_nb:item_nb_1] FormSet.from_key = from_key FormSet.to_key = to_key - if request.method == 'POST': - context['formset'] = FormSet(request.POST, queryset=queryset) - if context['formset'].is_valid(): - context['formset'].merge() - return redirect(reverse(current_url, kwargs={'page': page})) + if request.method == "POST": + context["formset"] = FormSet(request.POST, queryset=queryset) + if context["formset"].is_valid(): + context["formset"].merge() + return redirect(reverse(current_url, kwargs={"page": page})) else: - context['formset'] = FormSet(queryset=queryset) - return render(request, 'ishtar/merge_' + key + '.html', context) + context["formset"] = FormSet(queryset=queryset) + return render(request, "ishtar/merge_" + key + ".html", context) return merge @@ -1044,11 +1174,9 @@ def regenerate_external_id(request): return HttpResponseRedirect(reverse("success")) -person_merge = merge_action(models.Person, forms.MergePersonForm, 'person') +person_merge = merge_action(models.Person, forms.MergePersonForm, "person") organization_merge = merge_action( - models.Organization, - forms.MergeOrganizationForm, - 'organization' + models.Organization, forms.MergeOrganizationForm, "organization" ) @@ -1057,7 +1185,7 @@ class IshtarMixin(object): def get_context_data(self, **kwargs): context = super(IshtarMixin, self).get_context_data(**kwargs) - context['page_name'] = self.page_name + context["page_name"] = self.page_name return context @@ -1065,11 +1193,9 @@ class JSONResponseMixin: """ Render a JSON response. """ + def render_to_response(self, context, **response_kwargs): - return JsonResponse( - self.get_data(context), - **response_kwargs - ) + return JsonResponse(self.get_data(context), **response_kwargs) def get_data(self, context): return context @@ -1078,30 +1204,29 @@ class JSONResponseMixin: class LoginRequiredMixin(object): @method_decorator(login_required) def dispatch(self, request, *args, **kwargs): - return super(LoginRequiredMixin, self).dispatch(request, *args, - **kwargs) + return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs) class AdminLoginRequiredMixin(LoginRequiredMixin): def dispatch(self, request, *args, **kwargs): if not request.user.is_staff: - return redirect(reverse('start')) - return super(AdminLoginRequiredMixin, self).dispatch( - request, *args, **kwargs) + return redirect(reverse("start")) + return super(AdminLoginRequiredMixin, self).dispatch(request, *args, **kwargs) class ProfileEdit(LoginRequiredMixin, FormView): - template_name = 'ishtar/forms/profile.html' + template_name = "ishtar/forms/profile.html" form_class = forms.ProfilePersonForm def dispatch(self, request, *args, **kwargs): - if kwargs.get('pk'): + if kwargs.get("pk"): try: profile = models.UserProfile.objects.get( - pk=kwargs['pk'], person__ishtaruser__user_ptr=request.user) + pk=kwargs["pk"], person__ishtaruser__user_ptr=request.user + ) except models.UserProfile.DoesNotExist: # cannot edit a profile that is not yours... - return redirect(reverse('index')) + return redirect(reverse("index")) current_changed = False # the profile edited became the current profile if not profile.current: @@ -1114,16 +1239,16 @@ class ProfileEdit(LoginRequiredMixin, FormView): return super(ProfileEdit, self).dispatch(request, *args, **kwargs) def get_success_url(self): - return reverse('profile') + return reverse("profile") def get_form_kwargs(self): kwargs = super(ProfileEdit, self).get_form_kwargs() - kwargs['user'] = self.request.user + kwargs["user"] = self.request.user return kwargs def get_context_data(self, **kwargs): data = super(ProfileEdit, self).get_context_data(**kwargs) - data['page_name'] = _("Current profile") + data["page_name"] = _("Current profile") return data def form_valid(self, form): @@ -1133,9 +1258,9 @@ class ProfileEdit(LoginRequiredMixin, FormView): class DynamicModelView: def get_model(self, kwargs): - app = kwargs.get('app').replace('-', "_") + app = kwargs.get("app").replace("-", "_") model_name = "".join( - [part.capitalize() for part in kwargs.get('model_name').split('-')] + [part.capitalize() for part in kwargs.get("model_name").split("-")] ) try: return apps.get_model(app, model_name) @@ -1148,7 +1273,7 @@ class QRCodeView(DynamicModelView, IshtarMixin, LoginRequiredMixin, View): model = self.get_model(kwargs) try: item = model.objects.get(pk=kwargs.get("pk")) - assert hasattr(item, 'qrcode') + assert hasattr(item, "qrcode") except (model.DoesNotExist, AssertionError): raise Http404() @@ -1166,8 +1291,7 @@ class GenerateView(IshtarMixin, LoginRequiredMixin, View): def get_template(self, template_slug): try: return models.DocumentTemplate.objects.get( - slug=template_slug, available=True, - for_labels=False + slug=template_slug, available=True, for_labels=False ) except models.DocumentTemplate.DoesNotExist: raise Http404() @@ -1176,7 +1300,7 @@ class GenerateView(IshtarMixin, LoginRequiredMixin, View): return tpl.publish(objects[0]) def get_items(self, request, model): - item_pk = self.kwargs.get('item_pk') + item_pk = self.kwargs.get("item_pk") try: object = model.objects.get(pk=item_pk) if not object.can_view(request): @@ -1186,23 +1310,23 @@ class GenerateView(IshtarMixin, LoginRequiredMixin, View): return [object] def get(self, request, *args, **kwargs): - template_slug = kwargs.get('template_slug') + template_slug = kwargs.get("template_slug") tpl = self.get_template(template_slug) app, __, model_name = tpl.associated_model.klass.split(".") model = apps.get_model(app, model_name) objects = self.get_items(request, model) if not objects: - return HttpResponse(content_type='text/plain') + return HttpResponse(content_type="text/plain") document = self.publish(tpl, objects) if not document: - return HttpResponse(content_type='text/plain') + return HttpResponse(content_type="text/plain") with open(document, "rb") as f: response = HttpResponse( - f.read(), - content_type="application/vnd.oasis.opendocument.text" + f.read(), content_type="application/vnd.oasis.opendocument.text" + ) + response["Content-Disposition"] = "attachment; filename={}".format( + document.split(os.sep)[-1] ) - response['Content-Disposition'] = 'attachment; filename={}'.format( - document.split(os.sep)[-1]) return response @@ -1210,8 +1334,7 @@ class GenerateLabelView(GenerateView): def get_template(self, template_slug): try: return models.DocumentTemplate.objects.get( - slug=template_slug, available=True, - for_labels=True + slug=template_slug, available=True, for_labels=True ) except models.DocumentTemplate.DoesNotExist: raise Http404() @@ -1221,43 +1344,37 @@ class GenerateLabelView(GenerateView): def get_items(self, request, model): # rights are managed by get_item - get_list = get_item( - model, None, model.SLUG, own_table_cols=["id"])( - request, no_link=True, no_limit=True) + get_list = get_item(model, None, model.SLUG, own_table_cols=["id"])( + request, no_link=True, no_limit=True + ) item_list = json.loads(get_list.content.decode("utf-8"))["rows"] try: - objects = [ - model.objects.get(pk=int(dct["id"])) - for dct in item_list - ] + objects = [model.objects.get(pk=int(dct["id"])) for dct in item_list] except model.DoesNotExist: raise Http404() return objects class GlobalVarEdit(IshtarMixin, AdminLoginRequiredMixin, ModelFormSetView): - template_name = 'ishtar/formset.html' + template_name = "ishtar/formset.html" model = models.GlobalVar - factory_kwargs = { - 'extra': 1, - 'can_delete': True - } + factory_kwargs = {"extra": 1, "can_delete": True} page_name = _("Global variables") - fields = ['slug', 'value', 'description'] + fields = ["slug", "value", "description"] class NewImportView(IshtarMixin, LoginRequiredMixin, CreateView): - template_name = 'ishtar/form.html' + template_name = "ishtar/form.html" model = models.Import form_class = forms.NewImportForm page_name = _("New import") def get_success_url(self): - return reverse('current_imports') + return reverse("current_imports") def get_form_kwargs(self): kwargs = super(NewImportView, self).get_form_kwargs() - kwargs['user'] = self.request.user + kwargs["user"] = self.request.user return kwargs def form_valid(self, form): @@ -1267,26 +1384,25 @@ class NewImportView(IshtarMixin, LoginRequiredMixin, CreateView): class ImportListView(IshtarMixin, LoginRequiredMixin, ListView): - template_name = 'ishtar/import_list.html' + template_name = "ishtar/import_list.html" model = models.Import page_name = _("Current imports") - current_url = 'current_imports' + current_url = "current_imports" def get_queryset(self): - q = self.model.objects.exclude(state='AC') + q = self.model.objects.exclude(state="AC") if self.request.user.is_superuser: - return q.order_by('-pk') + return q.order_by("-pk") user = models.IshtarUser.objects.get(pk=self.request.user.pk) - return q.filter(user=user).order_by('-pk') + return q.filter(user=user).order_by("-pk") def post(self, request, *args, **kwargs): for field in request.POST: - if not field.startswith('import-action-') or \ - not request.POST[field]: + if not field.startswith("import-action-") or not request.POST[field]: continue # prevent forged forms try: - imprt = models.Import.objects.get(pk=int(field.split('-')[-1])) + imprt = models.Import.objects.get(pk=int(field.split("-")[-1])) except (models.Import.DoesNotExist, ValueError): continue if not self.request.user.is_superuser: @@ -1295,54 +1411,54 @@ class ImportListView(IshtarMixin, LoginRequiredMixin, ListView): if imprt.user != user: continue action = request.POST[field] - if action == 'D': - return HttpResponseRedirect(reverse('import_delete', - kwargs={'pk': imprt.pk})) - elif action == 'A': - imprt.initialize(user=self.request.user.ishtaruser, - session_key=request.session.session_key) - elif action == 'I': + if action == "D": + return HttpResponseRedirect( + reverse("import_delete", kwargs={"pk": imprt.pk}) + ) + elif action == "A": + imprt.initialize( + user=self.request.user.ishtaruser, + session_key=request.session.session_key, + ) + elif action == "I": if settings.USE_BACKGROUND_TASK: - imprt.delayed_importation( - request, request.session.session_key - ) + imprt.delayed_importation(request, request.session.session_key) else: imprt.importation() - elif action == 'CH': + elif action == "CH": if settings.USE_BACKGROUND_TASK: imprt.delayed_check_modified(request.session.session_key) else: imprt.check_modified() - elif action == 'IS': + elif action == "IS": if imprt.current_line is None: imprt.current_line = imprt.skip_lines imprt.save() return HttpResponseRedirect( - reverse('import_step_by_step', - args=[imprt.pk, imprt.current_line + 1]) + reverse( + "import_step_by_step", args=[imprt.pk, imprt.current_line + 1] + ) ) - elif action == 'AC': + elif action == "AC": imprt.archive() - elif action in ('F', 'FE'): + elif action in ("F", "FE"): imprt.unarchive(action) return HttpResponseRedirect(reverse(self.current_url)) def get_context_data(self, **kwargs): dct = super(ImportListView, self).get_context_data(**kwargs) - dct['autorefresh_available'] = settings.USE_BACKGROUND_TASK + dct["autorefresh_available"] = settings.USE_BACKGROUND_TASK return dct class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): - template_name = 'ishtar/import_step_by_step.html' + template_name = "ishtar/import_step_by_step.html" page_name = _("Import step by step") - current_url = 'import_step_by_step' + current_url = "import_step_by_step" def get_import(self): try: - self.imprt_obj = models.Import.objects.get( - pk=int(self.kwargs['pk']) - ) + self.imprt_obj = models.Import.objects.get(pk=int(self.kwargs["pk"])) except (models.Import.DoesNotExist, ValueError): raise Http404 if not self.request.user.is_superuser: @@ -1350,38 +1466,40 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): user = models.IshtarUser.objects.get(pk=self.request.user.pk) if self.imprt_obj.user != user: raise Http404 - if not hasattr(self, 'current_line_number'): - self.current_line_number = int(self.kwargs['line_number']) - 1 + if not hasattr(self, "current_line_number"): + self.current_line_number = int(self.kwargs["line_number"]) - 1 def update_csv(self, request): - prefix = 'col-' - submited_line = [(int(k[len(prefix):]), request.POST[k]) - for k in request.POST if k.startswith(prefix)] + prefix = "col-" + submited_line = [ + (int(k[len(prefix) :]), request.POST[k]) + for k in request.POST + if k.startswith(prefix) + ] updated_line = [value for line, value in sorted(submited_line)] filename = self.imprt_obj.imported_file.path - with open(filename, 'r', encoding=self.imprt_obj.encoding) as f: + with open(filename, "r", encoding=self.imprt_obj.encoding) as f: reader = csv.reader(f) lines = [] for idx, line in enumerate(reader): if idx == self.current_line_number: line = updated_line - line = [v.encode(self.imprt_obj.encoding, errors='replace') - for v in line] + line = [ + v.encode(self.imprt_obj.encoding, errors="replace") for v in line + ] lines.append(line) - with open(filename, 'w') as f: + with open(filename, "w") as f: writer = csv.writer(f, **CSV_OPTIONS) writer.writerows(lines) def import_line(self, request, *args, **kwargs): try: self.imprt, data = self.imprt_obj.importation( - line_to_process=self.current_line_number, - return_importer_and_data=True + line_to_process=self.current_line_number, return_importer_and_data=True ) except IOError as e: self.errors = [str(e)] - return super(ImportStepByStepView, self).get(request, *args, - **kwargs) + return super(ImportStepByStepView, self).get(request, *args, **kwargs) if self.imprt_obj.get_number_of_lines() >= self.current_line_number: self.current_line_number += 1 else: @@ -1391,27 +1509,35 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): return self.current_line_number def post(self, request, *args, **kwargs): - if not request.POST or request.POST.get('valid', None) not in ( - 'change-csv', 'import', 'change-page'): + if not request.POST or request.POST.get("valid", None) not in ( + "change-csv", + "import", + "change-page", + ): return self.get(request, *args, **kwargs) self.get_import() - if request.POST.get('valid') == 'change-page': + if request.POST.get("valid") == "change-page": return HttpResponseRedirect( - reverse('import_step_by_step', - args=[self.imprt_obj.pk, - request.POST.get('line-to-go', None)])) + reverse( + "import_step_by_step", + args=[self.imprt_obj.pk, request.POST.get("line-to-go", None)], + ) + ) - if request.POST.get('valid') == 'change-csv': + if request.POST.get("valid") == "change-csv": self.update_csv(request) return self.get(request, *args, **kwargs) if not self.import_line(request, *args, **kwargs): - return HttpResponseRedirect(reverse('current_imports')) + return HttpResponseRedirect(reverse("current_imports")) else: return HttpResponseRedirect( - reverse('import_step_by_step', - args=[self.imprt_obj.pk, self.current_line_number + 1])) + reverse( + "import_step_by_step", + args=[self.imprt_obj.pk, self.current_line_number + 1], + ) + ) def get(self, request, *args, **kwargs): self.get_import() @@ -1421,12 +1547,11 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): self.imprt, data = self.imprt_obj.importation( simulate=True, line_to_process=self.current_line_number, - return_importer_and_data=True + return_importer_and_data=True, ) except IOError as e: self.errors = [None, None, str(e)] - return super(ImportStepByStepView, self).get(request, *args, - **kwargs) + return super(ImportStepByStepView, self).get(request, *args, **kwargs) if not data or not data[0]: self.errors = self.imprt.errors if not self.errors: @@ -1437,24 +1562,25 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): def get_pagination(self, dct): pagination_step = 5 - only_modified = not self.kwargs.get('all_pages', False) - dct['all'] = not only_modified - dct['import_url'] = 'import_step_by_step' if only_modified else \ - 'import_step_by_step_all' + only_modified = not self.kwargs.get("all_pages", False) + dct["all"] = not only_modified + dct["import_url"] = ( + "import_step_by_step" if only_modified else "import_step_by_step_all" + ) line_nb = self.imprt_obj.get_number_of_lines() total_line_nb = self.imprt_obj.skip_lines + line_nb delta = 0 already_imported = [] if self.imprt_obj.imported_line_numbers: - already_imported = self.imprt_obj.imported_line_numbers.split(',') + already_imported = self.imprt_obj.imported_line_numbers.split(",") changes = [] if self.imprt_obj.changed_line_numbers: - changes = self.imprt_obj.changed_line_numbers.split(',') + changes = self.imprt_obj.changed_line_numbers.split(",") - dct['page_is_last'] = self.current_line_number == line_nb + dct["page_is_last"] = self.current_line_number == line_nb # label, number, enabled, is_imported, has_changes - dct['page_numbers'] = [] + dct["page_numbers"] = [] # first pass for the delta current = 0 for idx in range(self.imprt_obj.skip_lines, total_line_nb): @@ -1480,39 +1606,38 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): previous = idx continue nb = idx + 1 - dct['page_numbers'].append((nb, nb, True, imported, changed)) + dct["page_numbers"].append((nb, nb, True, imported, changed)) if previous: - dct['page_numbers'].insert(0, - (_("Previous"), previous + 1, True, False, - True) + dct["page_numbers"].insert( + 0, (_("Previous"), previous + 1, True, False, True) ) else: - dct['page_numbers'].insert(0, - (_("Previous"), self.imprt_obj.skip_lines, False, False, True) + dct["page_numbers"].insert( + 0, (_("Previous"), self.imprt_obj.skip_lines, False, False, True) ) if has_next: - dct['page_numbers'].append((_("Next"), has_next + 1, True, False, - True)) + dct["page_numbers"].append((_("Next"), has_next + 1, True, False, True)) else: - dct['page_numbers'].append((_("Next"), total_line_nb, False, - False, True)) + dct["page_numbers"].append((_("Next"), total_line_nb, False, False, True)) def get_context_data(self, **kwargs): dct = super(ImportStepByStepView, self).get_context_data(**kwargs) - dct['import'] = self.imprt_obj - dct['line_number_displayed'] = self.current_line_number + 1 - dct['line_is_imported'] = self.imprt_obj.line_is_imported( - self.current_line_number) + dct["import"] = self.imprt_obj + dct["line_number_displayed"] = self.current_line_number + 1 + dct["line_is_imported"] = self.imprt_obj.line_is_imported( + self.current_line_number + ) self.get_pagination(dct) - dct['errors'] = self.errors + dct["errors"] = self.errors if self.errors: if self.imprt.current_csv_line: - headers = [f.label if f else _("Not imported") - for f in self.imprt.get_formaters()] - dct['values'] = zip( - range(1, len(headers) + 1), headers, - self.imprt.current_csv_line + headers = [ + f.label if f else _("Not imported") + for f in self.imprt.get_formaters() + ] + dct["values"] = zip( + range(1, len(headers) + 1), headers, self.imprt.current_csv_line ) return dct headers, self.path_to_column, interpreted_values = [], {}, [] @@ -1526,8 +1651,9 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): lbl += ' <i data-toggle="tooltip" class="fa ' lbl += 'fa-question-circle"' lbl += ' aria-hidden="true" title="{}">'.format( - formater.comment.replace('"', '"')) - lbl += '</i>' + formater.comment.replace('"', """) + ) + lbl += "</i>" headers.append(lbl) field_name = "" @@ -1537,16 +1663,16 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): field_name = formater.export_field_name[0] value = self.new_data[0].copy() - field_name_tuple = field_name.split('__') + field_name_tuple = field_name.split("__") # associate each path level to this column while field_name_tuple: - current_field_name = '__'.join(field_name_tuple) + current_field_name = "__".join(field_name_tuple) if current_field_name not in self.path_to_column: self.path_to_column[current_field_name] = [] self.path_to_column[current_field_name].append(idx) field_name_tuple.pop() - for idx_split, key in enumerate(field_name.split('__')): + for idx_split, key in enumerate(field_name.split("__")): if isinstance(value, dict) and key in value: value = value[key] elif not idx_split: @@ -1557,9 +1683,11 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): value = self.get_value(value) interpreted_values.append(value) - dct['values'] = zip( - range(1, len(headers) + 1), headers, self.imprt.current_csv_line, - interpreted_values + dct["values"] = zip( + range(1, len(headers) + 1), + headers, + self.imprt.current_csv_line, + interpreted_values, ) new_objects = {} @@ -1569,7 +1697,7 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): created_dict = {} for k, val in new_dct.items(): - if val in ('', None, [], [None]): + if val in ("", None, [], [None]): continue created_dict[k] = val # check if it is not previously created @@ -1594,7 +1722,7 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): new_objects[key] = ([label], cls, value_dct) - dct['new_objects'] = [ + dct["new_objects"] = [ [" – ".join(lbls), cls, new_dct] for lbls, cls, new_dct in new_objects.values() ] @@ -1614,7 +1742,7 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): for k in updated_values.keys(): current_value = getattr(obj, k) updated_value = updated_values[k] - if hasattr(current_value, 'all'): + if hasattr(current_value, "all"): current_value = list(current_value.all()) changed = False for v in updated_value: @@ -1627,40 +1755,33 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): current_value = self.get_value(current_value) updated_value = self.get_value(updated_value) main_changed |= changed - old_and_updated[k] = [current_value, updated_value, - changed] + old_and_updated[k] = [current_value, updated_value, changed] # transform key into explicit label - old_and_updated = self.transform_keys_to_label(path, obj.__class__, - old_and_updated) + old_and_updated = self.transform_keys_to_label( + path, obj.__class__, old_and_updated + ) updated_objects.append((label, obj, values, old_and_updated)) - dct['have_change'] = main_changed or self.imprt.new_objects + dct["have_change"] = main_changed or self.imprt.new_objects if dct["have_change"]: self.imprt_obj.add_changed_line(self.current_line_number) else: self.imprt_obj.remove_changed_line(self.current_line_number) - dct['updated_objects'] = [] - dct['matched_objects'] = [] + dct["updated_objects"] = [] + dct["matched_objects"] = [] for path, obj, values, old_and_updated in updated_objects: if old_and_updated: - dct['updated_objects'].append( - (path, obj, values, old_and_updated) - ) + dct["updated_objects"].append((path, obj, values, old_and_updated)) else: - dct['matched_objects'].append( - (path, obj, values) - ) - dct['ambiguous_objects'] = self.imprt.ambiguous_objects - dct['not_find_objects'] = self.imprt.not_find_objects + dct["matched_objects"].append((path, obj, values)) + dct["ambiguous_objects"] = self.imprt.ambiguous_objects + dct["not_find_objects"] = self.imprt.not_find_objects return dct def transform_path_to_label(self, cls, path): - label = " > ".join( - str(l) - for l in get_field_labels_from_path(cls, path) - ) + label = " > ".join(str(l) for l in get_field_labels_from_path(cls, path)) if not label: label = str(cls._meta.verbose_name) return label @@ -1678,24 +1799,29 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): if concat_path in self.path_to_column: for col in self.path_to_column[concat_path]: col += 1 - label += " <a href=\"#col-{}\">"\ - "<span class=\"badge badge-info\"> {} {} </span>"\ - "</a>".format(col, _("Col. "), col) + label += ( + ' <a href="#col-{}">' + '<span class="badge badge-info"> {} {} </span>' + "</a>".format(col, _("Col. "), col) + ) value_dct[label] = dct[k] return value_dct def list_to_html(self, lst): if not lst: return _("* empty *") - return "<ul class='list-group'><li class='list-group-item'>" + \ - "</li><li class='list-group-item'>".join([ - self.get_value(item) for item in lst - ]) + "</li></ul>" + return ( + "<ul class='list-group'><li class='list-group-item'>" + + "</li><li class='list-group-item'>".join( + [self.get_value(item) for item in lst] + ) + + "</li></ul>" + ) def get_value(self, item): - if hasattr(item, 'SHOW_URL'): + if hasattr(item, "SHOW_URL"): return "{}{}".format(str(item), simple_link_to_window(item)) - if hasattr(item, 'explicit_label'): + if hasattr(item, "explicit_label"): return item.explicit_label if item in (None, [], [None]): return _("* empty *") @@ -1705,43 +1831,44 @@ class ImportStepByStepView(IshtarMixin, LoginRequiredMixin, TemplateView): class ImportListTableView(ImportListView): - template_name = 'ishtar/import_table.html' - current_url = 'current_imports_table' + template_name = "ishtar/import_table.html" + current_url = "current_imports_table" def get_context_data(self, **kwargs): dct = super(ImportListTableView, self).get_context_data(**kwargs) - dct['AJAX'] = True - dct['MESSAGES'] = [] + dct["AJAX"] = True + dct["MESSAGES"] = [] request = self.request - if 'messages' in request.session and \ - request.session['messages']: - for message, message_type in request.session['messages']: - dct['MESSAGES'].append((message, message_type)) - request.session['messages'] = [] - if 'current_import_id' in request.session and \ - request.session['current_import_id']: - dct['refreshed_pks'] = request.session.pop('current_import_id') + if "messages" in request.session and request.session["messages"]: + for message, message_type in request.session["messages"]: + dct["MESSAGES"].append((message, message_type)) + request.session["messages"] = [] + if ( + "current_import_id" in request.session + and request.session["current_import_id"] + ): + dct["refreshed_pks"] = request.session.pop("current_import_id") return dct class ImportOldListView(ImportListView): page_name = _("Old imports") - current_url = 'old_imports' + current_url = "old_imports" def get_queryset(self): - q = self.model.objects.filter(state='AC') + q = self.model.objects.filter(state="AC") if self.request.user.is_superuser: - return q.order_by('-creation_date') + return q.order_by("-creation_date") user = models.IshtarUser.objects.get(pk=self.request.user.pk) - return q.filter(user=user).order_by('-creation_date') + return q.filter(user=user).order_by("-creation_date") class ImportLinkView(IshtarMixin, LoginRequiredMixin, ModelFormSetView): - template_name = 'ishtar/formset_import_match.html' + template_name = "ishtar/formset_import_match.html" model = models.TargetKey page_name = _("Link unmatched items") factory_kwargs = { - 'extra': 0, + "extra": 0, } form_class = forms.TargetKeyForm formset_class = forms.TargetKeyFormset @@ -1749,53 +1876,54 @@ class ImportLinkView(IshtarMixin, LoginRequiredMixin, ModelFormSetView): def get_formset_kwargs(self): kwargs = super(ImportLinkView, self).get_formset_kwargs() - kwargs['user'] = self.request.user + kwargs["user"] = self.request.user return kwargs def full_queryset(self): return self.model.objects.filter( - is_set=False, associated_import=self.kwargs['pk']) + is_set=False, associated_import=self.kwargs["pk"] + ) def get_queryset(self): - return self.full_queryset()[:self.max_fields] + return self.full_queryset()[: self.max_fields] def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) total = self.full_queryset().count() if total > self.max_fields: - data['MAX_FIELDS_REACHED'] = self.max_fields - data['TOTAL'] = total + data["MAX_FIELDS_REACHED"] = self.max_fields + data["TOTAL"] = total return data def get_success_url(self): - return reverse('import_link_unmatched', args=[self.kwargs['pk']]) + return reverse("import_link_unmatched", args=[self.kwargs["pk"]]) class ImportDeleteView(IshtarMixin, LoginRequiredMixin, DeleteView): - template_name = 'ishtar/import_delete.html' + template_name = "ishtar/import_delete.html" model = models.Import page_name = _("Delete import") def get_success_url(self): - return reverse('current_imports') + return reverse("current_imports") class PersonCreate(LoginRequiredMixin, CreateView): model = models.Person form_class = forms.BasePersonForm - template_name = 'ishtar/person_form.html' + template_name = "ishtar/person_form.html" def get_success_url(self): - return reverse('person_edit', args=[self.object.pk]) + return reverse("person_edit", args=[self.object.pk]) class PersonEdit(LoginRequiredMixin, UpdateView): model = models.Person form_class = forms.BasePersonForm - template_name = 'ishtar/person_form.html' + template_name = "ishtar/person_form.html" def get_success_url(self): - return reverse('person_edit', args=[self.object.pk]) + return reverse("person_edit", args=[self.object.pk]) class ManualMergeMixin(object): @@ -1805,23 +1933,22 @@ class ManualMergeMixin(object): def get_success_url(self): return reverse( - self.redir_url, - args=["_".join([str(item.pk) for item in self.items])]) + self.redir_url, args=["_".join([str(item.pk) for item in self.items])] + ) -class PersonManualMerge(ManualMergeMixin, IshtarMixin, LoginRequiredMixin, - FormView): +class PersonManualMerge(ManualMergeMixin, IshtarMixin, LoginRequiredMixin, FormView): form_class = forms.PersonMergeFormSelection - template_name = 'ishtar/form.html' + template_name = "ishtar/form.html" page_name = _("Merge persons") - current_url = 'person-manual-merge' - redir_url = 'person_manual_merge_items' + current_url = "person-manual-merge" + redir_url = "person_manual_merge_items" class ManualMergeItemsMixin(object): def get_form_kwargs(self): kwargs = super(ManualMergeItemsMixin, self).get_form_kwargs() - kwargs['items'] = self.kwargs['pks'].split('_') + kwargs["items"] = self.kwargs["pks"].split("_") return kwargs def form_valid(self, form): @@ -1829,123 +1956,122 @@ class ManualMergeItemsMixin(object): return super(ManualMergeItemsMixin, self).form_valid(form) def get_success_url(self): - return reverse('display-item', args=[self.item_type, self.item.pk]) + return reverse("display-item", args=[self.item_type, self.item.pk]) class PersonManualMergeItems( - ManualMergeItemsMixin, IshtarMixin, - LoginRequiredMixin, FormView): + ManualMergeItemsMixin, IshtarMixin, LoginRequiredMixin, FormView +): form_class = forms.PersonMergeIntoForm - template_name = 'ishtar/form.html' + template_name = "ishtar/form.html" page_name = _("Select the main person") - current_url = 'person-manual-merge-items' - item_type = 'person' + current_url = "person-manual-merge-items" + item_type = "person" -class OrgaManualMerge(ManualMergeMixin, IshtarMixin, LoginRequiredMixin, - FormView): +class OrgaManualMerge(ManualMergeMixin, IshtarMixin, LoginRequiredMixin, FormView): form_class = forms.OrgaMergeFormSelection - template_name = 'ishtar/form.html' + template_name = "ishtar/form.html" page_name = _("Merge organization") - current_url = 'orga-manual-merge' - redir_url = 'orga_manual_merge_items' + current_url = "orga-manual-merge" + redir_url = "orga_manual_merge_items" class OrgaManualMergeItems( - ManualMergeItemsMixin, IshtarMixin, - LoginRequiredMixin, FormView): + ManualMergeItemsMixin, IshtarMixin, LoginRequiredMixin, FormView +): form_class = forms.OrgaMergeIntoForm - template_name = 'ishtar/form.html' + template_name = "ishtar/form.html" page_name = _("Select the main organization") - current_url = 'orga-manual-merge-items' - item_type = 'organization' + current_url = "orga-manual-merge-items" + item_type = "organization" class OrganizationCreate(LoginRequiredMixin, CreateView): model = models.Organization form_class = forms.BaseOrganizationForm - template_name = 'ishtar/organization_form.html' + template_name = "ishtar/organization_form.html" form_prefix = "orga" def get_form_kwargs(self): kwargs = super(OrganizationCreate, self).get_form_kwargs() - if hasattr(self.form_class, 'form_prefix'): - kwargs.update({'prefix': self.form_class.form_prefix}) + if hasattr(self.form_class, "form_prefix"): + kwargs.update({"prefix": self.form_class.form_prefix}) return kwargs def get_success_url(self): - return reverse('organization_edit', args=[self.object.pk]) + return reverse("organization_edit", args=[self.object.pk]) class OrganizationEdit(LoginRequiredMixin, UpdateView): model = models.Organization form_class = forms.BaseOrganizationForm - template_name = 'ishtar/organization_form.html' + template_name = "ishtar/organization_form.html" def get_form_kwargs(self): kwargs = super(OrganizationEdit, self).get_form_kwargs() - if hasattr(self.form_class, 'form_prefix'): - kwargs.update({'prefix': self.form_class.form_prefix}) + if hasattr(self.form_class, "form_prefix"): + kwargs.update({"prefix": self.form_class.form_prefix}) return kwargs def get_success_url(self): - return reverse('organization_edit', args=[self.object.pk]) + return reverse("organization_edit", args=[self.object.pk]) class OrganizationPersonCreate(LoginRequiredMixin, CreateView): model = models.Person form_class = forms.BaseOrganizationPersonForm - template_name = 'ishtar/organization_person_form.html' + template_name = "ishtar/organization_person_form.html" relative_label = _("Corporation manager") def get_context_data(self, *args, **kwargs): - data = super(OrganizationPersonCreate, self).get_context_data(*args, - **kwargs) - data['relative_label'] = self.relative_label + data = super(OrganizationPersonCreate, self).get_context_data(*args, **kwargs) + data["relative_label"] = self.relative_label return data def get_success_url(self): - return reverse('organization_person_edit', args=[self.object.pk]) + return reverse("organization_person_edit", args=[self.object.pk]) class OrganizationPersonEdit(LoginRequiredMixin, UpdateView): model = models.Person form_class = forms.BaseOrganizationPersonForm - template_name = 'ishtar/organization_person_form.html' + template_name = "ishtar/organization_person_form.html" relative_label = _("Corporation manager") def get_context_data(self, *args, **kwargs): - data = super(OrganizationPersonEdit, self).get_context_data(*args, - **kwargs) - data['relative_label'] = self.relative_label + data = super(OrganizationPersonEdit, self).get_context_data(*args, **kwargs) + data["relative_label"] = self.relative_label return data def get_success_url(self): - return reverse('organization_person_edit', args=[self.object.pk]) + return reverse("organization_person_edit", args=[self.object.pk]) # documents -new_document_tag = new_qa_item(models.DocumentTag, forms.AddDocumentTagForm, - page_name=_("New tag")) +new_document_tag = new_qa_item( + models.DocumentTag, forms.AddDocumentTagForm, page_name=_("New tag") +) autocomplete_documenttag = get_autocomplete_generic(models.DocumentTag) -show_document = show_item(models.Document, 'document') -get_document = get_item(models.Document, 'get_document', 'document', - search_form=forms.DocumentSelect) +show_document = show_item(models.Document, "document") +get_document = get_item( + models.Document, "get_document", "document", search_form=forms.DocumentSelect +) display_document = display_item(models.Document) document_search_wizard = wizards.DocumentSearch.as_view( - [('selec-document_search', forms.DocumentFormSelection)], + [("selec-document_search", forms.DocumentFormSelection)], label=_("Document: search"), - url_name='search-document', + url_name="search-document", ) class DocumentFormMixin(IshtarMixin, LoginRequiredMixin): form_class = forms.DocumentForm - template_name = 'ishtar/forms/document.html' + template_name = "ishtar/forms/document.html" model = models.Document def get_context_data(self, **kwargs): @@ -1962,7 +2088,7 @@ class DocumentCreateView(DocumentFormMixin, CreateView): def get_form_kwargs(self): kwargs = super(DocumentCreateView, self).get_form_kwargs() - initial = kwargs.get('initial', {}) + initial = kwargs.get("initial", {}) for related_key in models.Document.RELATED_MODELS_ALT: model = models.Document._meta.get_field(related_key).related_model if model.SLUG in self.request.GET: @@ -1972,19 +2098,18 @@ class DocumentCreateView(DocumentFormMixin, CreateView): continue initial[related_key] = str(item.pk) if initial: - kwargs['initial'] = initial + kwargs["initial"] = initial kwargs["user"] = self.request.user return kwargs -class DocumentSelectView(IshtarMixin, LoginRequiredMixin, - FormView): +class DocumentSelectView(IshtarMixin, LoginRequiredMixin, FormView): form_class = forms.DocumentFormSelection - template_name = 'ishtar/form.html' - redir_url = 'edit-document' + template_name = "ishtar/form.html" + redir_url = "edit-document" def form_valid(self, form): - self.pk = form.cleaned_data['pk'] + self.pk = form.cleaned_data["pk"] return super(DocumentSelectView, self).form_valid(form) def get_form_kwargs(self): @@ -1995,9 +2120,9 @@ class DocumentSelectView(IshtarMixin, LoginRequiredMixin, def get_context_data(self, **kwargs): data = super(DocumentSelectView, self).get_context_data(**kwargs) if self.request.GET and "open_item" in self.request.GET: - data["open_url"] =\ - reverse("show-document", - args=[self.request.GET["open_item"]]) + "/" + data["open_url"] = ( + reverse("show-document", args=[self.request.GET["open_item"]]) + "/" + ) return data def get_success_url(self): @@ -2010,17 +2135,18 @@ class DocumentEditView(DocumentFormMixin, UpdateView): def get_form_kwargs(self): kwargs = super(DocumentEditView, self).get_form_kwargs() try: - document = models.Document.objects.get(pk=self.kwargs.get('pk')) - assert check_permission(self.request, 'document/edit', document.pk) + document = models.Document.objects.get(pk=self.kwargs.get("pk")) + assert check_permission(self.request, "document/edit", document.pk) except (AssertionError, models.Document.DoesNotExist): raise Http404() initial = {} - for k in list(self.form_class.base_fields.keys()) + \ - models.Document.RELATED_MODELS: + for k in ( + list(self.form_class.base_fields.keys()) + models.Document.RELATED_MODELS + ): value = getattr(document, k) - if hasattr(value, 'all'): + if hasattr(value, "all"): value = ",".join([str(v.pk) for v in value.all()]) - if hasattr(value, 'pk'): + if hasattr(value, "pk"): value = value.pk initial[k] = value # main image initialisation @@ -2031,11 +2157,11 @@ class DocumentEditView(DocumentFormMixin, UpdateView): for related_item in getattr(document, k).all(): key = "{}_{}_main_image".format(k, related_item.pk) kwargs["main_items_fields"][k].append( - (key, "{} - {}".format( - _("Main image for"), related_item))) + (key, "{} - {}".format(_("Main image for"), related_item)) + ) if related_item.main_image == document: initial[key] = True - kwargs['initial'] = initial + kwargs["initial"] = initial kwargs["user"] = self.request.user self.document = document return kwargs @@ -2050,45 +2176,42 @@ class DocumentEditView(DocumentFormMixin, UpdateView): document_deletion_steps = [ - ('selec-document_deletion', forms.DocumentFormMultiSelection), - ('final-document_deletion', FinalDeleteForm) + ("selec-document_deletion", forms.DocumentFormMultiSelection), + ("final-document_deletion", FinalDeleteForm), ] document_deletion_wizard = wizards.DocumentDeletionWizard.as_view( document_deletion_steps, label=_("Document deletion"), - url_name='document_deletion',) + url_name="document_deletion", +) def document_delete(request, pk): - if not wizard_is_available(document_deletion_wizard, request, - models.Document, pk): + if not wizard_is_available(document_deletion_wizard, request, models.Document, pk): return HttpResponseRedirect("/") - wizard_url = 'document_deletion' + wizard_url = "document_deletion" wizards.DocumentDeletionWizard.session_set_value( - request, 'selec-' + wizard_url, 'pks', pk, reset=True) - return redirect(reverse(wizard_url, - kwargs={'step': 'final-' + wizard_url})) + request, "selec-" + wizard_url, "pks", pk, reset=True + ) + return redirect(reverse(wizard_url, kwargs={"step": "final-" + wizard_url})) def get_bookmark(request, pk): try: sq = models.SearchQuery.objects.get( - pk=pk, - profile__person__ishtaruser__user_ptr=request.user) + pk=pk, profile__person__ishtaruser__user_ptr=request.user + ) except models.SearchQuery.DoesNotExist: raise Http404() slug = sq.content_type.model_class().SLUG - return redirect( - reverse(slug + '_search') + "?bookmark={}".format(sq.pk) - ) + return redirect(reverse(slug + "_search") + "?bookmark={}".format(sq.pk)) def gen_generate_doc(model): - def func(request, pk, template_pk=None): - if not request.user.has_perm('view_' + model.SLUG, model): - return HttpResponse(content_type='text/plain') + if not request.user.has_perm("view_" + model.SLUG, model): + return HttpResponse(content_type="text/plain") try: item = model.objects.get(pk=pk) doc = item.publish(template_pk) @@ -2098,24 +2221,26 @@ def gen_generate_doc(model): dct = { "error_title": _("Error on your template"), "error": str(e), - "back_url": reverse("display-item", args=[item.SLUG, pk]) + "back_url": reverse("display-item", args=[item.SLUG, pk]), } template = loader.get_template("error.html") return HttpResponse(template.render(dct, request)) if doc: - MIMES = {'odt': 'application/vnd.oasis.opendocument.text', - 'ods': 'application/vnd.oasis.opendocument.spreadsheet'} - ext = doc.split('.')[-1] + MIMES = { + "odt": "application/vnd.oasis.opendocument.text", + "ods": "application/vnd.oasis.opendocument.spreadsheet", + } + ext = doc.split(".")[-1] doc_name = item.get_filename() + "." + ext - mimetype = 'text/csv' + mimetype = "text/csv" if ext in MIMES: mimetype = MIMES[ext] with open(doc, "rb") as d: response = HttpResponse(d, content_type=mimetype) - response['Content-Disposition'] = 'attachment; filename=%s' % \ - doc_name + response["Content-Disposition"] = "attachment; filename=%s" % doc_name return response - return HttpResponse(content_type='text/plain') + return HttpResponse(content_type="text/plain") + return func @@ -2123,24 +2248,26 @@ class SearchQueryMixin(object): """ Manage content type and profile init """ + def dispatch(self, request, *args, **kwargs): if not request.user.pk: raise Http404() try: self.profile = models.UserProfile.objects.get( - current=True, person__ishtaruser__user_ptr=request.user) + current=True, person__ishtaruser__user_ptr=request.user + ) except models.UserProfile.DoesNotExist: # no current profile raise Http404() - self.app_label = kwargs.get('app_label') - self.model = kwargs.get('model') + self.app_label = kwargs.get("app_label") + self.model = kwargs.get("model") model = self.model - if model == 'site': - model = 'archaeologicalsite' + if model == "site": + model = "archaeologicalsite" try: self.content_type = ContentType.objects.get( - app_label=self.app_label.replace('-', '_'), - model=model.replace('-', '_') + app_label=self.app_label.replace("-", "_"), + model=model.replace("-", "_"), ) except ContentType.DoesNotExist: raise Http404() @@ -2148,19 +2275,19 @@ class SearchQueryMixin(object): class SearchQueryEdit(SearchQueryMixin, LoginRequiredMixin, FormView): - template_name = 'ishtar/forms/search_query.html' + template_name = "ishtar/forms/search_query.html" form_class = forms.SearchQueryForm def get_form_kwargs(self): kwargs = super(SearchQueryEdit, self).get_form_kwargs() - kwargs['profile'] = self.profile - kwargs['content_type'] = self.content_type + kwargs["profile"] = self.profile + kwargs["content_type"] = self.content_type return kwargs def get_context_data(self, **kwargs): data = super(SearchQueryEdit, self).get_context_data(**kwargs) - data['app_label'] = self.app_label - data['model'] = self.model + data["app_label"] = self.app_label + data["model"] = self.model return data def form_valid(self, form): @@ -2168,25 +2295,25 @@ class SearchQueryEdit(SearchQueryMixin, LoginRequiredMixin, FormView): return HttpResponseRedirect(self.get_success_url()) def get_success_url(self): - return reverse('success', args=['bookmark']) + return reverse("success", args=["bookmark"]) -class BookmarkList(SearchQueryMixin, JSONResponseMixin, LoginRequiredMixin, - TemplateView): +class BookmarkList( + SearchQueryMixin, JSONResponseMixin, LoginRequiredMixin, TemplateView +): def get_data(self, context): q = models.SearchQuery.objects.filter( - content_type=self.content_type, - profile=self.profile + content_type=self.content_type, profile=self.profile ) return { - 'bookmarks': [ - {'label': sq.label, 'query': sq.query, - 'id': sq.id} for sq in q.all()] + "bookmarks": [ + {"label": sq.label, "query": sq.query, "id": sq.id} for sq in q.all() + ] } class QRCodeForSearchView(LoginRequiredMixin, FormView): - template_name = 'ishtar/forms/qrcode_for_search.html' + template_name = "ishtar/forms/qrcode_for_search.html" form_class = forms.QRSearchForm def form_valid(self, form): @@ -2197,7 +2324,7 @@ class QRCodeForSearchView(LoginRequiredMixin, FormView): class SearchQueryDelete(LoginRequiredMixin, DeleteView): model = models.SearchQuery - template_name = 'ishtar/forms/bookmark_delete.html' + template_name = "ishtar/forms/bookmark_delete.html" page_name = _("Delete bookmark") def dispatch(self, request, *args, **kwargs): @@ -2205,14 +2332,14 @@ class SearchQueryDelete(LoginRequiredMixin, DeleteView): raise Http404() try: self.profile = models.UserProfile.objects.get( - current=True, person__ishtaruser__user_ptr=request.user) + current=True, person__ishtaruser__user_ptr=request.user + ) except models.UserProfile.DoesNotExist: # no current profile raise Http404() try: self.search_query = models.SearchQuery.objects.get( - profile=self.profile, - pk=kwargs['pk'] + profile=self.profile, pk=kwargs["pk"] ) except models.SearchQuery.DoesNotExist: raise Http404() @@ -2220,67 +2347,58 @@ class SearchQueryDelete(LoginRequiredMixin, DeleteView): def get_context_data(self, **kwargs): data = super(SearchQueryDelete, self).get_context_data(**kwargs) - data['modal_size'] = "small" - data['page_name'] = _("Bookmark - Delete") - data['action_name'] = _("Delete") - data['item'] = self.search_query.label - data['url'] = reverse('bookmark-delete', args=[self.search_query.pk]) + data["modal_size"] = "small" + data["page_name"] = _("Bookmark - Delete") + data["action_name"] = _("Delete") + data["item"] = self.search_query.label + data["url"] = reverse("bookmark-delete", args=[self.search_query.pk]) return data def get_success_url(self): - return reverse('success', args=['bookmark']) + return reverse("success", args=["bookmark"]) -class AlertList(JSONResponseMixin, LoginRequiredMixin, - TemplateView): +class AlertList(JSONResponseMixin, LoginRequiredMixin, TemplateView): def dispatch(self, request, *args, **kwargs): if not request.user.pk: raise Http404() try: self.profile = models.UserProfile.objects.get( - current=True, person__ishtaruser__user_ptr=request.user) + current=True, person__ishtaruser__user_ptr=request.user + ) except models.UserProfile.DoesNotExist: # no current profile raise Http404() return super(AlertList, self).dispatch(request, *args, **kwargs) def get_data(self, context): - q = models.SearchQuery.objects.filter( - profile=self.profile, - is_alert=True - ) + q = models.SearchQuery.objects.filter(profile=self.profile, is_alert=True) alerts = [] for sq in q.all(): model = sq.content_type.model_class() - module = model.__module__.split('.')[0] - views = importlib.import_module(module + '.views') + module = model.__module__.split(".")[0] + views = importlib.import_module(module + ".views") try: get_view = getattr(views, "get_" + model.SLUG) except AttributeError: continue - nb = get_view( - self.request, - query={'search_vector': sq.query}, - count=True - ) - alerts.append( - {'label': sq.label, 'query_id': sq.pk, - 'number': nb} - ) - return {'alerts': alerts} + nb = get_view(self.request, query={"search_vector": sq.query}, count=True) + alerts.append({"label": sq.label, "query_id": sq.pk, "number": nb}) + return {"alerts": alerts} class QANotAvailable(IshtarMixin, LoginRequiredMixin, TemplateView): - template_name = 'ishtar/forms/qa_message.html' + template_name = "ishtar/forms/qa_message.html" modal_size = "small" - contexts = {"locked-by-others": _("Some items have been locked by other " - "users."), - "locked": _("Some items are locked.")} + contexts = { + "locked-by-others": _("Some items have been locked by other " "users."), + "locked": _("Some items are locked."), + } def get_context_data(self, **kwargs): data = super(QANotAvailable, self).get_context_data(**kwargs) data["page_name"] = _("Not available") - data['message'] = _("Action not available for these items.") + data["message"] = _("Action not available for these items.") if self.kwargs.get("context"): context = self.kwargs.get("context") if context in self.contexts: @@ -2289,13 +2407,13 @@ class QANotAvailable(IshtarMixin, LoginRequiredMixin, TemplateView): class QAItemForm(IshtarMixin, LoginRequiredMixin, FormView): - template_name = 'ishtar/forms/qa_form.html' + template_name = "ishtar/forms/qa_form.html" model = None base_url = None form_class = None page_name = "" success_url = "/success/" - modal_size = None # large, small or None (medium) + modal_size = None # large, small or None (medium) def get_quick_action(self): # if not listed in QUICK_ACTIONS overload this method @@ -2303,7 +2421,7 @@ class QAItemForm(IshtarMixin, LoginRequiredMixin, FormView): def pre_dispatch(self, request, *args, **kwargs): assert self.model - pks = [int(pk) for pk in kwargs.get('pks').split('-')] + pks = [int(pk) for pk in kwargs.get("pks").split("-")] self.items = list(self.model.objects.filter(pk__in=pks)) if not self.items: raise Http404() @@ -2312,11 +2430,11 @@ class QAItemForm(IshtarMixin, LoginRequiredMixin, FormView): quick_action = self.get_quick_action() if not quick_action: raise Http404() - if not quick_action.is_available( - user=request.user, session=request.session): + if not quick_action.is_available(user=request.user, session=request.session): for item in self.items: if not quick_action.is_available( - user=request.user, session=request.session, obj=item): + user=request.user, session=request.session, obj=item + ): raise Http404() self.url = request.get_full_path() @@ -2329,16 +2447,17 @@ class QAItemForm(IshtarMixin, LoginRequiredMixin, FormView): def get_form_kwargs(self): kwargs = super(QAItemForm, self).get_form_kwargs() - kwargs['items'] = self.items + kwargs["items"] = self.items return kwargs def get_context_data(self, **kwargs): data = super(QAItemForm, self).get_context_data(**kwargs) - data['url'] = self.url - data['items'] = self.items - data['modal_size'] = self.modal_size - data['page_name'] = "{} – {}".format( - self.model._meta.verbose_name, self.page_name) + data["url"] = self.url + data["items"] = self.items + data["modal_size"] = self.modal_size + data["page_name"] = "{} – {}".format( + self.model._meta.verbose_name, self.page_name + ) return data @@ -2350,16 +2469,16 @@ class QAItemEditForm(QAItemForm): return self.model.QA_EDIT def pre_dispatch(self, request, *args, **kwargs): - self.confirm = kwargs.get('confirm', False) and True - redirected = super(QAItemEditForm, self).pre_dispatch( - request, *args, **kwargs) + self.confirm = kwargs.get("confirm", False) and True + redirected = super(QAItemEditForm, self).pre_dispatch(request, *args, **kwargs) if redirected: return redirected if hasattr(self.model, "is_locked"): for item in self.items: if item.is_locked(request.user): redirected = HttpResponseRedirect( - reverse("qa-not-available", args=["locked"])) + reverse("qa-not-available", args=["locked"]) + ) return redirected def get_form_class(self): @@ -2369,25 +2488,25 @@ class QAItemEditForm(QAItemForm): def get_form_kwargs(self): kwargs = super(QAItemEditForm, self).get_form_kwargs() - kwargs['confirm'] = self.confirm + kwargs["confirm"] = self.confirm return kwargs def get_context_data(self, **kwargs): data = super(QAItemEditForm, self).get_context_data(**kwargs) - data['page_name'] = "{} – {}".format( - self.model._meta.verbose_name, self.model.QA_EDIT.text) + data["page_name"] = "{} – {}".format( + self.model._meta.verbose_name, self.model.QA_EDIT.text + ) if self.confirm: - if 'confirm' not in self.url: - data['url'] = self.url.split('?')[0] + "confirm/" - data['confirm'] = True - data['action_name'] = _("Confirm") + if "confirm" not in self.url: + data["url"] = self.url.split("?")[0] + "confirm/" + data["confirm"] = True + data["action_name"] = _("Confirm") return data def form_valid(self, form): if not self.confirm: self.confirm = True - return self.render_to_response( - self.get_context_data(form=self.get_form())) + return self.render_to_response(self.get_context_data(form=self.get_form())) return self.form_save(form) def form_save(self, form): @@ -2400,10 +2519,12 @@ class QABaseLockView(QAItemForm): page_name = _("lock/unlock") def pre_dispatch(self, request, *args, **kwargs): - super(QABaseLockView, self).pre_dispatch( - request, *args, **kwargs) - if [True for item in self.items - if item.lock_user and item.lock_user != request.user]: + super(QABaseLockView, self).pre_dispatch(request, *args, **kwargs) + if [ + True + for item in self.items + if item.lock_user and item.lock_user != request.user + ]: url = reverse("qa-not-available", args=["locked-by-others"]) return HttpResponseRedirect(url) @@ -2428,16 +2549,15 @@ class QADocumentForm(QAItemEditForm): class QADocumentDuplicateFormView(QAItemForm): - template_name = 'ishtar/forms/qa_document_duplicate.html' + template_name = "ishtar/forms/qa_document_duplicate.html" model = models.Document page_name = _("Duplicate") form_class = forms.QADocumentDuplicateForm base_url = "document-qa-duplicate" def get_form_kwargs(self): - kwargs = super(QADocumentDuplicateFormView, - self).get_form_kwargs() - kwargs['user'] = self.request.user + kwargs = super(QADocumentDuplicateFormView, self).get_form_kwargs() + kwargs["user"] = self.request.user return kwargs def form_valid(self, form): @@ -2445,14 +2565,13 @@ class QADocumentDuplicateFormView(QAItemForm): return HttpResponseRedirect(reverse("success")) def get_context_data(self, **kwargs): - data = super(QADocumentDuplicateFormView, - self).get_context_data(**kwargs) - data['action_name'] = _("Duplicate") + data = super(QADocumentDuplicateFormView, self).get_context_data(**kwargs) + data["action_name"] = _("Duplicate") return data class QADocumentPackagingFormView(QAItemForm): - template_name = 'ishtar/forms/qa_document_packaging.html' + template_name = "ishtar/forms/qa_document_packaging.html" model = models.Document form_class = forms.QADocumentPackagingForm page_name = _("Packaging") @@ -2460,7 +2579,8 @@ class QADocumentPackagingFormView(QAItemForm): def dispatch(self, request, *args, **kwargs): returned = super(QADocumentPackagingFormView, self).dispatch( - request, *args, **kwargs) + request, *args, **kwargs + ) """ for item in self.items: if item.is_locked(request.user): @@ -2470,8 +2590,8 @@ class QADocumentPackagingFormView(QAItemForm): def get_form_kwargs(self): kwargs = super(QADocumentPackagingFormView, self).get_form_kwargs() - kwargs['user'] = self.request.user - kwargs['prefix'] = "qa-packaging" + kwargs["user"] = self.request.user + kwargs["prefix"] = "qa-packaging" return kwargs def form_valid(self, form): @@ -2480,13 +2600,9 @@ class QADocumentPackagingFormView(QAItemForm): class DisplayItemView(IshtarMixin, TemplateView): - template_name = 'ishtar/display_item.html' - SHOW_VIEWS = { - 'document': show_document - } - ASSOCIATED_MODEL = { - "document": models.Document - } + template_name = "ishtar/display_item.html" + SHOW_VIEWS = {"document": show_document} + ASSOCIATED_MODEL = {"document": models.Document} def dispatch(self, request, *args, **kwargs): if not self.request.user.is_authenticated: @@ -2495,18 +2611,18 @@ class DisplayItemView(IshtarMixin, TemplateView): def get_context_data(self, *args, **kwargs): data = super(DisplayItemView, self).get_context_data(*args, **kwargs) - pk = kwargs.get('pk') - item_type = kwargs.get('item_type') + pk = kwargs.get("pk") + item_type = kwargs.get("item_type") if item_type in self.SHOW_VIEWS: - data["view_content"] = self.SHOW_VIEWS[item_type]( - self.request, pk).content - if item_type in self.ASSOCIATED_MODEL and \ - hasattr(self.ASSOCIATED_MODEL[item_type], "extra_meta"): + data["view_content"] = self.SHOW_VIEWS[item_type](self.request, pk).content + if item_type in self.ASSOCIATED_MODEL and hasattr( + self.ASSOCIATED_MODEL[item_type], "extra_meta" + ): model = self.ASSOCIATED_MODEL[item_type] try: data["extra_meta"] = model.objects.get(pk=pk).extra_meta except model.DoesNotExist: pass else: - data['show_url'] = "/show-{}/{}/".format(item_type, pk) + data["show_url"] = "/show-{}/{}/".format(item_type, pk) return data diff --git a/ishtar_common/views_item.py b/ishtar_common/views_item.py index 0b803b00e..31a16b672 100644 --- a/ishtar_common/views_item.py +++ b/ishtar_common/views_item.py @@ -18,55 +18,74 @@ from django.contrib.staticfiles.templatetags.staticfiles import static from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse, NoReverseMatch -from django.db.models import Q, Count, Sum, ImageField, Func, \ - ExpressionWrapper, FloatField, FileField +from django.db.models import ( + Q, + Count, + Sum, + ImageField, + Func, + ExpressionWrapper, + FloatField, + FileField, +) from django.db.models.fields import FieldDoesNotExist from django.db.utils import ProgrammingError from django.forms.models import model_to_dict from django.http import HttpResponse from django.shortcuts import render from django.template import loader -from django.utils.translation import ugettext, ugettext_lazy as _, \ - activate, deactivate, pgettext_lazy +from django.utils.translation import ( + ugettext, + ugettext_lazy as _, + activate, + deactivate, + pgettext_lazy, +) from tidylib import tidy_document as tidy from unidecode import unidecode from weasyprint import HTML, CSS from weasyprint.fonts import FontConfiguration -from ishtar_common.utils import check_model_access_control, CSV_OPTIONS, \ - get_all_field_names, Round, PRIVATE_FIELDS -from ishtar_common.models import get_current_profile, \ - GeneralType, SearchAltName +from ishtar_common.utils import ( + check_model_access_control, + CSV_OPTIONS, + get_all_field_names, + Round, + PRIVATE_FIELDS, +) +from ishtar_common.models import get_current_profile, GeneralType, SearchAltName from ishtar_common.models_common import HistoryError from .menus import Menu from . import models from archaeological_files.models import File -from archaeological_operations.models import Operation, ArchaeologicalSite, \ - AdministrativeAct +from archaeological_operations.models import ( + Operation, + ArchaeologicalSite, + AdministrativeAct, +) from archaeological_context_records.models import ContextRecord -from archaeological_finds.models import Find, FindBasket, Treatment, \ - TreatmentFile +from archaeological_finds.models import Find, FindBasket, Treatment, TreatmentFile from archaeological_warehouse.models import Warehouse logger = logging.getLogger(__name__) -ENCODING = settings.ENCODING or 'utf-8' +ENCODING = settings.ENCODING or "utf-8" CURRENT_ITEM_KEYS = ( - ('file', File), - ('operation', Operation), - ('site', ArchaeologicalSite), - ('contextrecord', ContextRecord), - ('warehouse', Warehouse), - ('find', Find), - ('treatmentfile', TreatmentFile), - ('treatment', Treatment), - ('administrativeact', AdministrativeAct), - ('administrativeactop', AdministrativeAct), - ('administrativeactfile', AdministrativeAct), - ('administrativeacttreatment', AdministrativeAct), - ('administrativeacttreatmentfile', AdministrativeAct), + ("file", File), + ("operation", Operation), + ("site", ArchaeologicalSite), + ("contextrecord", ContextRecord), + ("warehouse", Warehouse), + ("find", Find), + ("treatmentfile", TreatmentFile), + ("treatment", Treatment), + ("administrativeact", AdministrativeAct), + ("administrativeactop", AdministrativeAct), + ("administrativeactfile", AdministrativeAct), + ("administrativeacttreatment", AdministrativeAct), + ("administrativeacttreatmentfile", AdministrativeAct), ) CURRENT_ITEM_KEYS_DICT = dict(CURRENT_ITEM_KEYS) @@ -74,14 +93,15 @@ CURRENT_ITEM_KEYS_DICT = dict(CURRENT_ITEM_KEYS) def get_autocomplete_queries(request, label_attributes, extra=None): if not label_attributes: return [Q(pk__isnull=True)] - base_q = request.GET.get('term') or "" + base_q = request.GET.get("term") or "" queries = [] - splited_q = base_q.split(' ') + splited_q = base_q.split(" ") for value_prefix, query_suffix, query_endswith in ( - ('', '__startswith', True), # starts with - (' ', '__icontains', True), # contain a word which starts with - ('', '__endswith', False), # ends with - ('', '__icontains', False)): # contains + ("", "__startswith", True), # starts with + (" ", "__icontains", True), # contain a word which starts with + ("", "__endswith", False), # ends with + ("", "__icontains", False), + ): # contains alt_queries = [None] if len(splited_q) == 1 and query_endswith: alt_queries = ["__endswith", None] @@ -92,15 +112,11 @@ def get_autocomplete_queries(request, label_attributes, extra=None): for q in splited_q: if not q: continue - sub_q = Q( - **{label_attributes[0] + query_suffix: value_prefix + q}) + sub_q = Q(**{label_attributes[0] + query_suffix: value_prefix + q}) if alt_query: - sub_q &= Q( - **{label_attributes[0] + alt_query: q} - ) + sub_q &= Q(**{label_attributes[0] + alt_query: q}) for other_label in label_attributes[1:]: - sub_q = sub_q | Q( - **{other_label + query_suffix: value_prefix + q}) + sub_q = sub_q | Q(**{other_label + query_suffix: value_prefix + q}) query = query & sub_q queries.append(query) return queries @@ -112,10 +128,8 @@ def get_autocomplete_item(model, extra=None): def func(request, current_right=None, limit=20): result = OrderedDict() - for query in get_autocomplete_queries(request, ['cached_label'], - extra=extra): - objects = model.objects.filter(query).values( - 'cached_label', 'id')[:limit] + for query in get_autocomplete_queries(request, ["cached_label"], extra=extra): + objects = model.objects.filter(query).values("cached_label", "id")[:limit] for obj in objects: if obj["id"] not in list(result.keys()): result[obj["id"]] = obj["cached_label"] @@ -124,9 +138,11 @@ def get_autocomplete_item(model, extra=None): break if not limit: break - data = json.dumps([{'id': obj[0], 'value': obj[1]} - for obj in list(result.items())]) - return HttpResponse(data, content_type='text/plain') + data = json.dumps( + [{"id": obj[0], "value": obj[1]} for obj in list(result.items())] + ) + return HttpResponse(data, content_type="text/plain") + return func @@ -138,17 +154,20 @@ def check_permission(request, action_slug, obj_id=None): return True if obj_id: return MAIN_MENU.items[action_slug].is_available( - request.user, obj_id, session=request.session) + request.user, obj_id, session=request.session + ) return MAIN_MENU.items[action_slug].can_be_available( - request.user, session=request.session) + request.user, session=request.session + ) -def new_qa_item(model, frm, many=False, - template="ishtar/forms/qa_new_item.html", page_name=""): - def func(request, parent_name, limits=''): +def new_qa_item( + model, frm, many=False, template="ishtar/forms/qa_new_item.html", page_name="" +): + def func(request, parent_name, limits=""): model_name = model._meta.object_name not_permitted_msg = ugettext("Operation not permitted.") - if not check_permission(request, 'add_' + model_name.lower()): + if not check_permission(request, "add_" + model_name.lower()): return HttpResponse(not_permitted_msg) slug = model.SLUG if model.SLUG == "site": @@ -156,29 +175,32 @@ def new_qa_item(model, frm, many=False, url_slug = "new-" + slug current_page_name = page_name[:] if not current_page_name: - current_page_name = _('New %s' % model_name.lower()) - dct = {'page_name': str(current_page_name), - 'url': reverse(url_slug, args=[parent_name]), - 'slug': slug, - 'parent_name': parent_name, - 'many': many} - if request.method == 'POST': - dct['form'] = frm(request.POST, limits=limits) - if dct['form'].is_valid(): - new_item = dct['form'].save(request.user) + current_page_name = _("New %s" % model_name.lower()) + dct = { + "page_name": str(current_page_name), + "url": reverse(url_slug, args=[parent_name]), + "slug": slug, + "parent_name": parent_name, + "many": many, + } + if request.method == "POST": + dct["form"] = frm(request.POST, limits=limits) + if dct["form"].is_valid(): + new_item = dct["form"].save(request.user) lbl = str(new_item) if not lbl and hasattr(new_item, "_generate_cached_label"): lbl = new_item._generate_cached_label() - dct['new_item_label'] = lbl - dct['new_item_pk'] = new_item.pk - dct['parent_pk'] = parent_name - if dct['parent_pk'] and '_select_' in dct['parent_pk']: - parents = dct['parent_pk'].split('_') - dct['parent_pk'] = "_".join([parents[0]] + parents[2:]) + dct["new_item_label"] = lbl + dct["new_item_pk"] = new_item.pk + dct["parent_pk"] = parent_name + if dct["parent_pk"] and "_select_" in dct["parent_pk"]: + parents = dct["parent_pk"].split("_") + dct["parent_pk"] = "_".join([parents[0]] + parents[2:]) return render(request, template, dct) else: - dct['form'] = frm(limits=limits) + dct["form"] = frm(limits=limits) return render(request, template, dct) + return func @@ -186,8 +208,7 @@ def get_short_html_detail(model): def func(request, pk): model_name = model._meta.object_name not_permitted_msg = ugettext("Operation not permitted.") - if not check_permission(request, 'view_' + model_name.lower(), - pk): + if not check_permission(request, "view_" + model_name.lower(), pk): return HttpResponse(not_permitted_msg) try: item = model.objects.get(pk=pk) @@ -195,6 +216,7 @@ def get_short_html_detail(model): return HttpResponse(not_permitted_msg) html = item.get_short_html_detail() return HttpResponse(html) + return func @@ -203,8 +225,7 @@ def modify_qa_item(model, frm): template = "ishtar/forms/qa_new_item.html" model_name = model._meta.object_name not_permitted_msg = ugettext("Operation not permitted.") - if not check_permission(request, 'change_' + model_name.lower(), - pk): + if not check_permission(request, "change_" + model_name.lower(), pk): return HttpResponse(not_permitted_msg) slug = model.SLUG if model.SLUG == "site": @@ -214,43 +235,46 @@ def modify_qa_item(model, frm): except model.DoesNotExist: return HttpResponse(not_permitted_msg) url_slug = "modify-" + slug - dct = {'page_name': str(_('Modify a %s' % model_name.lower())), - 'url': reverse(url_slug, args=[parent_name, pk]), - 'slug': slug, - "modify": True, - 'parent_name': parent_name} - if request.method == 'POST': - dct['form'] = frm(request.POST) - if dct['form'].is_valid(): - new_item = dct['form'].save(request.user, item) + dct = { + "page_name": str(_("Modify a %s" % model_name.lower())), + "url": reverse(url_slug, args=[parent_name, pk]), + "slug": slug, + "modify": True, + "parent_name": parent_name, + } + if request.method == "POST": + dct["form"] = frm(request.POST) + if dct["form"].is_valid(): + new_item = dct["form"].save(request.user, item) lbl = str(new_item) if not lbl and hasattr(new_item, "_generate_cached_label"): lbl = new_item._generate_cached_label() - dct['new_item_label'] = lbl - dct['new_item_pk'] = new_item.pk - dct['parent_pk'] = parent_name - if dct['parent_pk'] and '_select_' in dct['parent_pk']: - parents = dct['parent_pk'].split('_') - dct['parent_pk'] = "_".join([parents[0]] + parents[2:]) + dct["new_item_label"] = lbl + dct["new_item_pk"] = new_item.pk + dct["parent_pk"] = parent_name + if dct["parent_pk"] and "_select_" in dct["parent_pk"]: + parents = dct["parent_pk"].split("_") + dct["parent_pk"] = "_".join([parents[0]] + parents[2:]) return render(request, template, dct) else: data = model_to_dict(item) for k in list(data.keys()): - if data[k] and isinstance(data[k], list) and hasattr( - data[k][0], "pk"): + if data[k] and isinstance(data[k], list) and hasattr(data[k][0], "pk"): data[k] = [i.pk for i in data[k]] - dct['form'] = frm(initial=data) + dct["form"] = frm(initial=data) return render(request, template, dct) + return func def display_item(model, extra_dct=None, show_url=None): def func(request, pk, **dct): if show_url: - dct['show_url'] = "/{}{}/".format(show_url, pk) + dct["show_url"] = "/{}{}/".format(show_url, pk) else: - dct['show_url'] = "/show-{}/{}/".format(model.SLUG, pk) - return render(request, 'ishtar/display_item.html', dct) + dct["show_url"] = "/show-{}/{}/".format(model.SLUG, pk) + return render(request, "ishtar/display_item.html", dct) + return func @@ -261,38 +285,43 @@ def show_item(model, name, extra_dct=None, model_for_perms=None): check_model = model_for_perms allowed, own = check_model_access_control(request, check_model) if not allowed: - return HttpResponse('', content_type="application/xhtml") + return HttpResponse("", content_type="application/xhtml") q = model.objects if own: - if not hasattr(request.user, 'ishtaruser'): - return HttpResponse('') + if not hasattr(request.user, "ishtaruser"): + return HttpResponse("") query_own = model.get_query_owns(request.user.ishtaruser) if query_own: q = q.filter(query_own).distinct() try: item = q.get(pk=pk) except (ObjectDoesNotExist, ValueError): - return HttpResponse('') - doc_type = 'type' in dct and dct.pop('type') - url_name = "/".join(reverse('show-' + name, args=['0', ''] - ).split('/')[:-2]) + "/" + return HttpResponse("") + doc_type = "type" in dct and dct.pop("type") + url_name = ( + "/".join(reverse("show-" + name, args=["0", ""]).split("/")[:-2]) + "/" + ) profile = get_current_profile() - dct['PROFILE'] = profile - dct['CURRENCY'] = profile.currency - dct['ENCODING'] = settings.ENCODING - dct['DOT_GENERATION'] = settings.DOT_BINARY and profile.relation_graph - dct['current_window_url'] = url_name + dct["PROFILE"] = profile + dct["CURRENCY"] = profile.currency + dct["ENCODING"] = settings.ENCODING + dct["DOT_GENERATION"] = settings.DOT_BINARY and profile.relation_graph + dct["current_window_url"] = url_name date = None - if 'date' in dct: - date = dct.pop('date') - dct['sheet_id'] = "%s-%d" % (name, item.pk) - dct['window_id'] = "%s-%d-%s" % ( - name, item.pk, datetime.datetime.now().strftime('%M%s')) + if "date" in dct: + date = dct.pop("date") + dct["sheet_id"] = "%s-%d" % (name, item.pk) + dct["window_id"] = "%s-%d-%s" % ( + name, + item.pk, + datetime.datetime.now().strftime("%M%s"), + ) # list current perms - if hasattr(request.user, 'ishtaruser') and request.user.ishtaruser: + if hasattr(request.user, "ishtaruser") and request.user.ishtaruser: cache_key = "{}-{}-{}".format( - settings.PROJECT_SLUG, "current-perms", + settings.PROJECT_SLUG, + "current-perms", request.session.session_key, ) permissions = cache.get(cache_key) @@ -308,80 +337,96 @@ def show_item(model, name, extra_dct=None, model_for_perms=None): for perm in permissions: dct["permission_" + perm] = True - if hasattr(item, 'history') and request.user.is_superuser: + if hasattr(item, "history") and request.user.is_superuser: if date: try: - date = datetime.datetime.strptime(date, - '%Y-%m-%dT%H:%M:%S.%f') + date = datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%S.%f") if item.get_last_history_date() != date: item = item.get_previous(date=date) assert item is not None - dct['previous'] = item._previous - dct['next'] = item._next + dct["previous"] = item._previous + dct["next"] = item._next else: date = None except (ValueError, AssertionError): - return HttpResponse('', content_type='text/plain') + return HttpResponse("", content_type="text/plain") if not date: historized = item.history.all() if historized: item.history_date = historized[0].history_date if len(historized) > 1: - dct['previous'] = historized[1].history_date - if doc_type in ("odt", "pdf") and hasattr(item, 'qrcode') \ - and (not item.qrcode or not item.qrcode.name): + dct["previous"] = historized[1].history_date + if ( + doc_type in ("odt", "pdf") + and hasattr(item, "qrcode") + and (not item.qrcode or not item.qrcode.name) + ): item.generate_qrcode(request=request) - dct['item'], dct['item_name'] = item, name + dct["item"], dct["item_name"] = item, name # add context if extra_dct: dct.update(extra_dct(request, item)) context_instance = deepcopy(dct) - context_instance['output'] = 'html' - if hasattr(item, 'history_object'): + context_instance["output"] = "html" + if hasattr(item, "history_object"): filename = item.history_object.associated_filename else: filename = item.associated_filename if doc_type == "odt" and settings.ODT_TEMPLATE: - tpl = loader.get_template('ishtar/sheet_%s.html' % name) - context_instance['output'] = 'ODT' + tpl = loader.get_template("ishtar/sheet_%s.html" % name) + context_instance["output"] = "ODT" content = tpl.render(context_instance, request) - tidy_options = {'output-xhtml': 1, 'indent': 1, - 'tidy-mark': 0, 'doctype': 'auto', - 'add-xml-decl': 1, 'wrap': 1} + tidy_options = { + "output-xhtml": 1, + "indent": 1, + "tidy-mark": 0, + "doctype": "auto", + "add-xml-decl": 1, + "wrap": 1, + } html, errors = tidy(content, options=tidy_options) html = html.replace(" ", " ") - html = re.sub('<pre([^>]*)>\n', '<pre\\1>', html) + html = re.sub("<pre([^>]*)>\n", "<pre\\1>", html) odt = NamedTemporaryFile() html_source = NamedTemporaryFile() - with open(html_source.name, 'w') as html_file: + with open(html_source.name, "w") as html_file: html_file.write(html) - pandoc_args = ["pandoc", "-f", "html", "-t", "odt", - "-o", odt.name, html_source.name] + pandoc_args = [ + "pandoc", + "-f", + "html", + "-t", + "odt", + "-o", + odt.name, + html_source.name, + ] try: - subprocess.check_call(pandoc_args, stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.check_call( + pandoc_args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) except subprocess.CalledProcessError: - return HttpResponse(content, - content_type="application/xhtml") + return HttpResponse(content, content_type="application/xhtml") response = HttpResponse( - content_type='application/vnd.oasis.opendocument.text') - response['Content-Disposition'] = \ - 'attachment; filename={}.odt'.format(filename) - with open(odt.name, 'rb') as odt_file: + content_type="application/vnd.oasis.opendocument.text" + ) + response["Content-Disposition"] = "attachment; filename={}.odt".format( + filename + ) + with open(odt.name, "rb") as odt_file: response.write(odt_file.read()) return response - elif doc_type == 'pdf': - base_url = "/".join( - request.build_absolute_uri().split("/")[0:3] - ) + elif doc_type == "pdf": + base_url = "/".join(request.build_absolute_uri().split("/")[0:3]) - tpl = loader.get_template('ishtar/sheet_%s_pdf.html' % name) - context_instance['output'] = 'PDF' + tpl = loader.get_template("ishtar/sheet_%s_pdf.html" % name) + context_instance["output"] = "PDF" html = tpl.render(context_instance, request) font_config = FontConfiguration() - css = CSS(string=''' + css = CSS( + string=""" @font-face { font-family: Gentium; src: url(%s); @@ -389,20 +434,21 @@ def show_item(model, name, extra_dct=None, model_for_perms=None): body{ font-family: Gentium } - ''' % (base_url + static("gentium/GentiumPlus-R.ttf"))) - css2 = CSS(filename=settings.STATIC_ROOT + '/media/style_basic.css') - pdf = HTML( - string=html, base_url=base_url - ).write_pdf( - stylesheets=[css, css2], font_config=font_config) - response = HttpResponse(pdf, content_type='application/pdf') - response['Content-Disposition'] = 'attachment; filename=%s.pdf' % \ - filename + """ + % (base_url + static("gentium/GentiumPlus-R.ttf")) + ) + css2 = CSS(filename=settings.STATIC_ROOT + "/media/style_basic.css") + pdf = HTML(string=html, base_url=base_url).write_pdf( + stylesheets=[css, css2], font_config=font_config + ) + response = HttpResponse(pdf, content_type="application/pdf") + response["Content-Disposition"] = "attachment; filename=%s.pdf" % filename return response else: - tpl = loader.get_template('ishtar/sheet_%s_window.html' % name) + tpl = loader.get_template("ishtar/sheet_%s_window.html" % name) content = tpl.render(context_instance, request) return HttpResponse(content, content_type="application/xhtml") + return func @@ -410,21 +456,29 @@ def revert_item(model): def func(request, pk, date, **dct): try: item = model.objects.get(pk=pk) - date = datetime.datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.%f') + date = datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%S.%f") item.rollback(date) except (ObjectDoesNotExist, ValueError, HistoryError): - return HttpResponse(None, content_type='text/plain') - return HttpResponse("True", content_type='text/plain') + return HttpResponse(None, content_type="text/plain") + return HttpResponse("True", content_type="text/plain") + return func HIERARCHIC_LEVELS = 5 -HIERARCHIC_FIELDS = ['periods', 'period', 'unit', 'material_types', - 'material_type', 'conservatory_state', 'object_types'] +HIERARCHIC_FIELDS = [ + "periods", + "period", + "unit", + "material_types", + "material_type", + "conservatory_state", + "object_types", +] def _get_values(request, val): - if hasattr(val, 'all'): # manage related objects + if hasattr(val, "all"): # manage related objects vals = list(val.all()) else: vals = [val] @@ -433,9 +487,13 @@ def _get_values(request, val): if callable(v): v = v() try: - if hasattr(v, 'url') and v.url: - v = (request.is_secure() and - 'https' or 'http') + '://' + request.get_host() + v.url + if hasattr(v, "url") and v.url: + v = ( + (request.is_secure() and "https" or "http") + + "://" + + request.get_host() + + v.url + ) except ValueError: pass new_vals.append(v) @@ -453,8 +511,11 @@ def _push_to_list(obj, current_group, depth): except IndexError: # tolerant to parentheses mismatch pass - if current_group and type(obj) in (str, str) and \ - type(current_group[-1]) in (str, str): + if ( + current_group + and type(obj) in (str, str) + and type(current_group[-1]) in (str, str) + ): current_group[-1] += obj else: current_group.append(obj) @@ -488,10 +549,10 @@ def _parse_parentheses(s): if char == '"': inside_quote = not inside_quote if not inside_quote: - if char == '(': + if char == "(": _push_to_list([], groups, depth) depth += 1 - elif char == ')': + elif char == ")": if depth > 0: depth -= 1 else: @@ -507,8 +568,9 @@ RESERVED_CHAR = ["|", "&"] RE_FACET = re.compile('([-a-zA-Z]+)="([^"]+)"(?:;"([^"]+)")*') -def _parse_query_string(string, query_parameters, current_dct, exc_dct, - extra_distinct_q): +def _parse_query_string( + string, query_parameters, current_dct, exc_dct, extra_distinct_q +): string = string.strip().lower() match = RE_FACET.search(string) @@ -541,21 +603,20 @@ def _parse_query_string(string, query_parameters, current_dct, exc_dct, is_true = not is_true cfltr, cexclude, cextra = term(is_true=is_true) if cfltr: - if 'and_reqs' not in dct: - dct['and_reqs'] = [] - dct['and_reqs'].append(cfltr) + if "and_reqs" not in dct: + dct["and_reqs"] = [] + dct["and_reqs"].append(cfltr) if cexclude: - if 'exc_and_reqs' not in dct: - dct['exc_and_reqs'] = [] - dct['exc_and_reqs'].append(cexclude) + if "exc_and_reqs" not in dct: + dct["exc_and_reqs"] = [] + dct["exc_and_reqs"].append(cexclude) if cextra: - dct['extras'].append(cextra) + dct["extras"].append(cextra) else: if query_parameters[base_term].distinct_query: extra_distinct_q.append({}) dct = extra_distinct_q[-1] - if not query_parameters[base_term].distinct_query and \ - excluded: + if not query_parameters[base_term].distinct_query and excluded: dct = exc_dct if query_parameters[base_term].extra_query: dct.update(query_parameters[base_term].extra_query) @@ -565,11 +626,10 @@ def _parse_query_string(string, query_parameters, current_dct, exc_dct, dct[term] = query if query_parameters[base_term].distinct_query: for k in dct: # clean " - dct[k] = dct[k].replace('"', '') + dct[k] = dct[k].replace('"', "") # distinct query wait for a query _manage_clean_search_field(dct) - extra_distinct_q[-1] = \ - ~Q(**dct) if excluded else Q(**dct) + extra_distinct_q[-1] = ~Q(**dct) if excluded else Q(**dct) return "" for reserved_char in FORBIDDEN_CHAR: string = string.replace(reserved_char, "") @@ -578,22 +638,23 @@ def _parse_query_string(string, query_parameters, current_dct, exc_dct, string = string.replace(reserved_char, "") if not string: return "" - if string.endswith('*'): + if string.endswith("*"): if len(string.strip()) == 1: return "" - string = string[:-1] + ':*' + string = string[:-1] + ":*" elif string not in ("&", "|", "!", "-"): # like search by default - string = string + ':*' - if string.startswith('-'): + string = string + ":*" + if string.startswith("-"): if len(string.strip()) == 1: return "" string = "!" + string[1:] return string -def _parse_parentheses_groups(groups, query_parameters, current_dct=None, - exc_dct=None, extra_distinct_q=None): +def _parse_parentheses_groups( + groups, query_parameters, current_dct=None, exc_dct=None, extra_distinct_q=None +): """ Transform parentheses groups to query @@ -612,8 +673,7 @@ def _parse_parentheses_groups(groups, query_parameters, current_dct=None, extra_distinct_q = [] if type(groups) is not list: string = groups.strip() - if string.startswith('"') and string.endswith('"') and \ - string.count('"') == 2: + if string.startswith('"') and string.endswith('"') and string.count('"') == 2: string = string[1:-1] # split into many groups if spaces @@ -624,9 +684,11 @@ def _parse_parentheses_groups(groups, query_parameters, current_dct=None, previous_quote = None while found != -1: if previous_quote is not None: - string = string[0:previous_quote] + \ - string[previous_quote:found].replace(' ', SEP) + \ - string[found:] + string = ( + string[0:previous_quote] + + string[previous_quote:found].replace(" ", SEP) + + string[found:] + ) previous_quote = None # SEP is larger than a space found = string.find('"', current_index) @@ -637,20 +699,29 @@ def _parse_parentheses_groups(groups, query_parameters, current_dct=None, string_groups = [gp.replace(SEP, " ") for gp in string.split(" ")] if len(string_groups) == 1: - return _parse_query_string( - string_groups[0], query_parameters, current_dct, exc_dct, - extra_distinct_q - ), current_dct, exc_dct, extra_distinct_q + return ( + _parse_query_string( + string_groups[0], + query_parameters, + current_dct, + exc_dct, + extra_distinct_q, + ), + current_dct, + exc_dct, + extra_distinct_q, + ) return _parse_parentheses_groups( - string_groups, query_parameters, current_dct, exc_dct, - extra_distinct_q) + string_groups, query_parameters, current_dct, exc_dct, extra_distinct_q + ) if not groups: # empty list return "", current_dct, exc_dct, extra_distinct_q query = "(" previous_sep, has_item = None, False for item in groups: q, current_dct, exc_dct, extra_distinct_q = _parse_parentheses_groups( - item, query_parameters, current_dct, exc_dct, extra_distinct_q) + item, query_parameters, current_dct, exc_dct, extra_distinct_q + ) q = q.strip() if not q: continue @@ -671,16 +742,22 @@ def _parse_parentheses_groups(groups, query_parameters, current_dct=None, return query, current_dct, exc_dct, extra_distinct_q -def _search_manage_search_vector(model, dct, exc_dct, distinct_queries, - query_parameters): - if 'search_vector' not in dct \ - or not model._meta.managed: # is a view - no search_vector +def _search_manage_search_vector( + model, dct, exc_dct, distinct_queries, query_parameters +): + if ( + "search_vector" not in dct or not model._meta.managed + ): # is a view - no search_vector return dct, exc_dct, distinct_queries - search_vector = dct['search_vector'] + search_vector = dct["search_vector"] parentheses_groups = _parse_parentheses(search_vector) - search_query, extra_dct, extra_exc_dct, extra_distinct_q = \ - _parse_parentheses_groups(parentheses_groups, query_parameters) + ( + search_query, + extra_dct, + extra_exc_dct, + extra_distinct_q, + ) = _parse_parentheses_groups(parentheses_groups, query_parameters) dct.update(extra_dct) distinct_queries += extra_distinct_q @@ -688,15 +765,18 @@ def _search_manage_search_vector(model, dct, exc_dct, distinct_queries, if not search_query: return dct, exc_dct, distinct_queries # remove inside parenthesis - search_query = search_query.replace('(', '').replace(')', '').strip() + search_query = search_query.replace("(", "").replace(")", "").strip() if search_query: - if 'extras' not in dct: - dct['extras'] = [] - dct['extras'].append( - {'where': [model._meta.db_table + - ".search_vector @@ (to_tsquery(%s, %s)) = true"], - 'params': [settings.ISHTAR_SEARCH_LANGUAGE, - search_query]} + if "extras" not in dct: + dct["extras"] = [] + dct["extras"].append( + { + "where": [ + model._meta.db_table + + ".search_vector @@ (to_tsquery(%s, %s)) = true" + ], + "params": [settings.ISHTAR_SEARCH_LANGUAGE, search_query], + } ) return dct, exc_dct, distinct_queries @@ -709,7 +789,7 @@ def _manage_bool_fields(model, bool_fields, reversed_bool_fields, dct, or_reqs): elif dct[k] == "1": dct.pop(k) continue - dct[k] = dct[k].replace('"', '') + dct[k] = dct[k].replace('"', "") if dct[k] in ["2", "yes", str(_("Yes")).lower(), "True"]: dct[k] = True else: @@ -717,7 +797,7 @@ def _manage_bool_fields(model, bool_fields, reversed_bool_fields, dct, or_reqs): if k in reversed_bool_fields: dct[k] = not dct[k] # check also for empty value with image field - field_names = k.split('__') + field_names = k.split("__") # TODO: can be improved in later version of Django try: c_field = model._meta.get_field(field_names[0]) @@ -725,15 +805,15 @@ def _manage_bool_fields(model, bool_fields, reversed_bool_fields, dct, or_reqs): if not hasattr(c_field, "related_model"): return c_field = c_field.related_model._meta.get_field(field_name) - if k.endswith('__isnull') and \ - (isinstance(c_field, (ImageField, FileField)) - or field_names[-2] == "associated_url"): - key = "__".join(k.split('__')[:-1]) + if k.endswith("__isnull") and ( + isinstance(c_field, (ImageField, FileField)) + or field_names[-2] == "associated_url" + ): + key = "__".join(k.split("__")[:-1]) if dct[k]: - or_reqs.append( - (k, {key + '__exact': ''})) + or_reqs.append((k, {key + "__exact": ""})) else: - dct[key + '__regex'] = '.{1}.*' + dct[key + "__regex"] = ".{1}.*" except FieldDoesNotExist: pass @@ -746,7 +826,7 @@ def _manage_many_counted_fields(fields, reversed_fields, dct, excluded_dct): elif dct[k] == "1": dct.pop(k) continue - dct[k] = dct[k].replace('"', '') + dct[k] = dct[k].replace('"', "") dct[k] = True if dct[k] in ["2", "yes", str(_("Yes")).lower()] else None if reversed_fields and k in reversed_fields: dct[k] = True if not dct[k] else None @@ -758,7 +838,7 @@ def _manage_many_counted_fields(fields, reversed_fields, dct, excluded_dct): today_lbl = pgettext_lazy("key for text search", "today") -TODAYS = ['today'] +TODAYS = ["today"] for language_code, language_lbl in settings.LANGUAGES: activate(language_code) @@ -776,12 +856,12 @@ def _manage_dated_fields(dated_fields, dct): if not dct[k]: dct.pop(k) continue - value = dct[k].replace('"', '').strip() + value = dct[k].replace('"', "").strip() has_today = False for today in TODAYS: if value.startswith(today): base_date = datetime.date.today() - value = value[len(today):].replace(' ', '') + value = value[len(today) :].replace(" ", "") if value and value[0] in ("-", "+"): sign = value[0] try: @@ -790,27 +870,26 @@ def _manage_dated_fields(dated_fields, dct): days = 0 if days: if sign == "-": - base_date = base_date - datetime.timedelta( - days=days) + base_date = base_date - datetime.timedelta(days=days) else: - base_date = base_date + datetime.timedelta( - days=days) - dct[k] = base_date.strftime('%Y-%m-%d') + base_date = base_date + datetime.timedelta(days=days) + dct[k] = base_date.strftime("%Y-%m-%d") has_today = True break if has_today: continue items = [] if "/" in value: - items = list(reversed(value.split('/'))) + items = list(reversed(value.split("/"))) elif "-" in value: # already date formated - items = value.split('-') + items = value.split("-") if len(items) != 3: dct.pop(k) return try: - dct[k] = datetime.datetime( - *map(lambda x: int(x), items)).strftime('%Y-%m-%d') + dct[k] = datetime.datetime(*map(lambda x: int(x), items)).strftime( + "%Y-%m-%d" + ) except ValueError: dct.pop(k) @@ -838,8 +917,7 @@ def _manage_facet_search(model, dct, and_reqs): k = base_k else: k = base_k + "__pk" - if k not in dct or not dct[k].startswith('"') \ - or not dct[k].startswith('"'): + if k not in dct or not dct[k].startswith('"') or not dct[k].startswith('"'): continue val = _clean_type_val(dct.pop(k)) if '";"' in val: @@ -858,9 +936,12 @@ def _manage_facet_search(model, dct, and_reqs): lbl_name = "__cached_label__" except: pass - suffix = "{}icontains".format(lbl_name) if "%" in val else \ - "{}iexact".format(lbl_name) - query = val[1:-1].replace('*', "") + suffix = ( + "{}icontains".format(lbl_name) + if "%" in val + else "{}iexact".format(lbl_name) + ) + query = val[1:-1].replace("*", "") if not reqs: reqs = Q(**{base_k + suffix: query}) else: @@ -868,13 +949,12 @@ def _manage_facet_search(model, dct, and_reqs): if reqs: and_reqs.append(reqs) - POST_PROCESS_REQUEST = getattr(model, 'POST_PROCESS_REQUEST', None) + POST_PROCESS_REQUEST = getattr(model, "POST_PROCESS_REQUEST", None) if not POST_PROCESS_REQUEST: return for k in dct: if k in POST_PROCESS_REQUEST and dct[k]: - dct[k] = getattr(model, POST_PROCESS_REQUEST[k])( - dct[k].replace('"', '')) + dct[k] = getattr(model, POST_PROCESS_REQUEST[k])(dct[k].replace('"', "")) def _manage_hierarchic_fields(model, dct, and_reqs): @@ -885,12 +965,11 @@ def _manage_hierarchic_fields(model, dct, and_reqs): if type(reqs) not in (list, tuple): reqs = [reqs] for req in reqs: - if req.endswith('areas__pk') \ - or req.endswith('areas__label__iexact'): - if req.endswith('pk'): - suffix = 'pk' - elif req.endswith('label__iexact'): - suffix = 'label__iexact' + if req.endswith("areas__pk") or req.endswith("areas__label__iexact"): + if req.endswith("pk"): + suffix = "pk" + elif req.endswith("label__iexact"): + suffix = "label__iexact" else: continue @@ -900,38 +979,41 @@ def _manage_hierarchic_fields(model, dct, and_reqs): reqs = Q(**{req: val}) for idx in range(HIERARCHIC_LEVELS): - req = req[:-(len(suffix))] + 'parent__' + suffix + req = req[: -(len(suffix))] + "parent__" + suffix q = Q(**{req: val}) reqs |= q and_reqs.append(reqs) # TODO: improve query with "IN ()"? continue - if req.endswith('town__pk') or req.endswith('towns__pk') \ - or req.endswith('town__cached_label__iexact') \ - or req.endswith('towns__cached_label__iexact'): - - if req.endswith('pk'): - suffix = 'pk' - elif req.endswith('cached_label__iexact'): - suffix = 'cached_label__iexact' + if ( + req.endswith("town__pk") + or req.endswith("towns__pk") + or req.endswith("town__cached_label__iexact") + or req.endswith("towns__cached_label__iexact") + ): + + if req.endswith("pk"): + suffix = "pk" + elif req.endswith("cached_label__iexact"): + suffix = "cached_label__iexact" else: continue val = _clean_type_val(dct.pop(req)).strip('"') if val.startswith('"') and val.endswith('"'): val = val[1:-1] - vals = [v.replace('"', '') for v in val.split(';')] + vals = [v.replace('"', "") for v in val.split(";")] main_req = None for val in vals: reqs = Q(**{req: val}) nreq = base_req = req[:] for idx in range(HIERARCHIC_LEVELS): - nreq = nreq[:-(len(suffix))] + 'parents__' + suffix + nreq = nreq[: -(len(suffix))] + "parents__" + suffix q = Q(**{nreq: val}) reqs |= q nreq = base_req[:] for idx in range(HIERARCHIC_LEVELS): - nreq = nreq[:-(len(suffix))] + 'children__' + suffix + nreq = nreq[: -(len(suffix))] + "children__" + suffix q = Q(**{nreq: val}) reqs |= q if not main_req: @@ -946,8 +1028,7 @@ def _manage_hierarchic_fields(model, dct, and_reqs): lbl_name = "label" try: rel = getattr(model, k_hr).field.related_model - if not hasattr(rel, "label") and hasattr(rel, - "cached_label"): + if not hasattr(rel, "label") and hasattr(rel, "cached_label"): lbl_name = "cached_label" except: pass @@ -963,8 +1044,9 @@ def _manage_hierarchic_fields(model, dct, and_reqs): q |= Q(**{r: val}) and_reqs.append(q) break - elif req.endswith(k_hr + '__pk') \ - or req.endswith(k_hr + '__{}__iexact'.format(lbl_name)): + elif req.endswith(k_hr + "__pk") or req.endswith( + k_hr + "__{}__iexact".format(lbl_name) + ): val = _clean_type_val(dct.pop(req)) if '";"' in val: @@ -974,9 +1056,9 @@ def _manage_hierarchic_fields(model, dct, and_reqs): values = [val] base_req = req[:] reqs = None - if req.endswith('pk'): + if req.endswith("pk"): base_suffix = "pk" - elif req.endswith('{}__iexact'.format(lbl_name)): + elif req.endswith("{}__iexact".format(lbl_name)): base_suffix = lbl_name + "__iexact" else: continue @@ -988,17 +1070,17 @@ def _manage_hierarchic_fields(model, dct, and_reqs): # manage search text by label if "*" in val: suffix = lbl_name + "__icontains" - val = val.replace('*', "") + val = val.replace("*", "") else: suffix = lbl_name + "__iexact" - req = req[:-(len(base_suffix))] + suffix + req = req[: -(len(base_suffix))] + suffix if not reqs: reqs = Q(**{req: val}) else: reqs |= Q(**{req: val}) for idx in range(HIERARCHIC_LEVELS): - req = req[:-(len(suffix))] + 'parent__' + suffix + req = req[: -(len(suffix))] + "parent__" + suffix q = Q(**{req: val}) reqs |= q # TODO: improve query with "IN ()"? @@ -1012,20 +1094,20 @@ def _manage_clean_search_field(dct, exclude=None): # clean quoted search field if type(dct[k]) != str: continue - dct[k] = dct[k].replace('"', '') + dct[k] = dct[k].replace('"', "") dct[k] = _clean_type_val(dct[k]) - if '*' not in dct[k] or not k.endswith('__iexact'): + if "*" not in dct[k] or not k.endswith("__iexact"): continue value = dct.pop(k).strip() if value.startswith("*"): value = value[1:] if value.endswith("*"): value = value[:-1] - base_key = k[:-len('__iexact')] + base_key = k[: -len("__iexact")] if value: - dct[base_key + '__icontains'] = value + dct[base_key + "__icontains"] = value elif exclude is not None: - exclude[base_key + '__exact'] = "" + exclude[base_key + "__exact"] = "" def _manage_relation_types(relation_types, dct, query, or_reqs): @@ -1033,11 +1115,12 @@ def _manage_relation_types(relation_types, dct, query, or_reqs): vals = relation_types[rtype_prefix] if not vals: continue - vals = list(vals)[0].split(';') + vals = list(vals)[0].split(";") for v in vals: alt_dct = { - rtype_prefix + 'right_relations__relation_type__label__iexact': - v.replace('"', '')} + rtype_prefix + + "right_relations__relation_type__label__iexact": v.replace('"', "") + } for k in dct: val = dct[k] if rtype_prefix: @@ -1046,12 +1129,13 @@ def _manage_relation_types(relation_types, dct, query, or_reqs): continue # tricky: reconstruct the key to make sense - remove the # prefix from the key - k = k[0:k.index(rtype_prefix)] + \ - k[k.index(rtype_prefix) + len(rtype_prefix):] - if k.endswith('year'): - k += '__exact' - alt_dct[rtype_prefix + 'right_relations__right_record__' + k] = \ - val + k = ( + k[0 : k.index(rtype_prefix)] + + k[k.index(rtype_prefix) + len(rtype_prefix) :] + ) + if k.endswith("year"): + k += "__exact" + alt_dct[rtype_prefix + "right_relations__right_record__" + k] = val if not dct: # fake condition to trick Django (1.4): without it only the # alt_dct is managed @@ -1062,11 +1146,9 @@ def _manage_relation_types(relation_types, dct, query, or_reqs): altor_dct.pop(k) for j in or_req: val = or_req[j] - if j == 'year': - j = 'year__exact' - altor_dct[ - rtype_prefix + 'right_relations__right_record__' + j] = \ - val + if j == "year": + j = "year__exact" + altor_dct[rtype_prefix + "right_relations__right_record__" + j] = val query |= Q(**altor_dct) return query @@ -1076,7 +1158,7 @@ def _construct_query(relation_types, dct, or_reqs, and_reqs): # manage multi value not already managed for key in list(dct.keys()): if type(dct[key]) == str and ";" in dct[key]: - values = [v for v in dct[key].split(';') if v] + values = [v for v in dct[key].split(";") if v] if not values: dct.pop(key) continue @@ -1084,9 +1166,7 @@ def _construct_query(relation_types, dct, or_reqs, and_reqs): if len(values) == 1: continue for v in values[1:]: - or_reqs.append( - (key, {key: v}) - ) + or_reqs.append((key, {key: v})) for k in list(dct.keys()): if type(k) not in (list, tuple): @@ -1116,44 +1196,45 @@ def _construct_query(relation_types, dct, or_reqs, and_reqs): return query -def _manage_default_search(dct, request, model, default_name, my_base_request, - my_relative_session_names): +def _manage_default_search( + dct, request, model, default_name, my_base_request, my_relative_session_names +): pinned_search = "" pin_key = "pin-search-" + default_name - if pin_key in request.session and \ - request.session[pin_key]: # a search is pinned + if pin_key in request.session and request.session[pin_key]: # a search is pinned pinned_search = request.session[pin_key] - dct = {'search_vector': request.session[pin_key]} - elif default_name in request.session and \ - request.session[default_name]: # an item is pinned + dct = {"search_vector": request.session[pin_key]} + elif ( + default_name in request.session and request.session[default_name] + ): # an item is pinned value = request.session[default_name] - if 'basket-' in value: + if "basket-" in value: try: - dct = { - "basket__pk": request.session[default_name].split('-')[-1]} - pinned_search = str(FindBasket.objects.get( - pk=dct["basket__pk"])) + dct = {"basket__pk": request.session[default_name].split("-")[-1]} + pinned_search = str(FindBasket.objects.get(pk=dct["basket__pk"])) except FindBasket.DoesNotExist: pass else: try: dct = {"pk": request.session[default_name]} - pinned_search = '"{}"'.format( - model.objects.get(pk=dct["pk"]) - ) + pinned_search = '"{}"'.format(model.objects.get(pk=dct["pk"])) except model.DoesNotExist: pass elif dct == (my_base_request or {}): - if not hasattr(model, 'UP_MODEL_QUERY'): + if not hasattr(model, "UP_MODEL_QUERY"): logger.warning( "**WARN get_item**: - UP_MODEL_QUERY not defined for " - "'{}'".format(model)) + "'{}'".format(model) + ) else: # a parent item may be selected in the default menu for name, key in my_relative_session_names: - if name in request.session and request.session[name] \ - and 'basket-' not in request.session[name] \ - and name in CURRENT_ITEM_KEYS_DICT: + if ( + name in request.session + and request.session[name] + and "basket-" not in request.session[name] + and name in CURRENT_ITEM_KEYS_DICT + ): up_model = CURRENT_ITEM_KEYS_DICT[name] try: dct.update({key: request.session[name]}) @@ -1161,15 +1242,12 @@ def _manage_default_search(dct, request, model, default_name, my_base_request, if up_item.SLUG not in model.UP_MODEL_QUERY: logger.warning( "**WARN get_item**: - {} not in " - "UP_MODEL_QUERY for {}'".format( - up_item.SLUG, - model)) + "UP_MODEL_QUERY for {}'".format(up_item.SLUG, model) + ) else: - req_key, up_attr = model.UP_MODEL_QUERY[ - up_item.SLUG] + req_key, up_attr = model.UP_MODEL_QUERY[up_item.SLUG] pinned_search = '{}="{}"'.format( - req_key, - getattr(up_item, up_attr) + req_key, getattr(up_item, up_attr) ) break except up_model.DoesNotExist: @@ -1190,39 +1268,30 @@ def _format_val(val): def _format_geojson(rows, link_template): data = { - 'type': 'FeatureCollection', - 'crs': { - 'type': 'name', - 'properties': { - 'name': 'EPSG:4326' - } - }, - 'link_template': link_template, - 'features': [], - 'no-geo': [] + "type": "FeatureCollection", + "crs": {"type": "name", "properties": {"name": "EPSG:4326"}}, + "link_template": link_template, + "features": [], + "no-geo": [], } if not rows: return data for row in rows: - feat = {'id': row[0], 'name': row[1]} + feat = {"id": row[0], "name": row[1]} x, y = row[2], row[3] if not x or not y or x < -180 or x > 180 or y < -90 or y > 90: - data['no-geo'].append(feat) + data["no-geo"].append(feat) continue feature = { - 'type': 'Feature', - 'properties': feat, - 'geometry': { - 'type': 'Point', - 'coordinates': [x, y] - } + "type": "Feature", + "properties": feat, + "geometry": {"type": "Point", "coordinates": [x, y]}, } - data['features'].append(feature) + data["features"].append(feature) return data -def _get_data_from_query(items, query_table_cols, extra_request_keys, - point_field=None): +def _get_data_from_query(items, query_table_cols, extra_request_keys, point_field=None): for query_keys in query_table_cols: if not isinstance(query_keys, (tuple, list)): query_keys = [query_keys] @@ -1233,39 +1302,43 @@ def _get_data_from_query(items, query_table_cols, extra_request_keys, # only manage one level for display query_key = query_key[0] # clean - for filtr in ('__icontains', '__contains', '__iexact', - '__exact'): + for filtr in ("__icontains", "__contains", "__iexact", "__exact"): if query_key.endswith(filtr): - query_key = query_key[:len(query_key) - len(filtr)] + query_key = query_key[: len(query_key) - len(filtr)] query_key.replace(".", "__") # class style to query - values = ['id'] + query_table_cols + values = ["id"] + query_table_cols if point_field: profile = get_current_profile() precision = profile.point_precision if precision is not None: exp_x = ExpressionWrapper( - Round(Func(point_field, function='ST_X'), precision), - output_field=FloatField()) + Round(Func(point_field, function="ST_X"), precision), + output_field=FloatField(), + ) exp_y = ExpressionWrapper( - Round(Func(point_field, function='ST_Y'), precision), - output_field=FloatField()) + Round(Func(point_field, function="ST_Y"), precision), + output_field=FloatField(), + ) else: exp_x = ExpressionWrapper( - Func(point_field, function='ST_X'), output_field=FloatField()) + Func(point_field, function="ST_X"), output_field=FloatField() + ) exp_y = ExpressionWrapper( - Func(point_field, function='ST_Y'), output_field=FloatField()) + Func(point_field, function="ST_Y"), output_field=FloatField() + ) items = items.annotate(point_x=exp_x) items = items.annotate(point_y=exp_y) - values += ['point_x', 'point_y'] + values += ["point_x", "point_y"] if hasattr(items.model, "locked"): values.append("locked") values.append("lock_user_id") return items.values_list(*values) -def _get_data_from_query_old(items, query_table_cols, request, - extra_request_keys, do_not_deduplicate=False): +def _get_data_from_query_old( + items, query_table_cols, request, extra_request_keys, do_not_deduplicate=False +): c_ids, datas = [], [] has_lock = items and hasattr(items[0], "locked") @@ -1284,22 +1357,21 @@ def _get_data_from_query_old(items, query_table_cols, request, k = extra_request_keys[k] if type(k) in (list, tuple): k = k[0] - for filtr in ('__icontains', '__contains', '__iexact', - '__exact'): + for filtr in ("__icontains", "__contains", "__iexact", "__exact"): if k.endswith(filtr): - k = k[:len(k) - len(filtr)] + k = k[: len(k) - len(filtr)] vals = [item] # foreign key may be divided by "." or "__" splitted_k = [] - for ky in k.split('.'): - if '__' in ky: - splitted_k += ky.split('__') + for ky in k.split("."): + if "__" in ky: + splitted_k += ky.split("__") else: splitted_k.append(ky) for ky in splitted_k: new_vals = [] for val in vals: - if hasattr(val, 'all'): # manage related objects + if hasattr(val, "all"): # manage related objects val = list(val.all()) for v in val: v = getattr(v, ky) @@ -1313,7 +1385,7 @@ def _get_data_from_query_old(items, query_table_cols, request, pass vals = new_vals # manage last related objects - if vals and hasattr(vals[0], 'all'): + if vals and hasattr(vals[0], "all"): new_vals = [] for val in vals: new_vals += list(val.all()) @@ -1324,12 +1396,12 @@ def _get_data_from_query_old(items, query_table_cols, request, new_vals = [] if not vals: for idx, my_v in enumerate(my_vals): - new_vals.append("{}{}{}".format( - my_v, ' - ', '')) + new_vals.append("{}{}{}".format(my_v, " - ", "")) else: for idx, v in enumerate(vals): - new_vals.append("{}{}{}".format( - vals[idx], ' - ', _format_val(v))) + new_vals.append( + "{}{}{}".format(vals[idx], " - ", _format_val(v)) + ) my_vals = new_vals[:] data.append(" & ".join(my_vals) or "") if has_lock: @@ -1347,14 +1419,15 @@ def _format_modality(value): return value -def _get_json_stats(items, stats_sum_variable, stats_modality_1, - stats_modality_2, multiply=1): +def _get_json_stats( + items, stats_sum_variable, stats_modality_1, stats_modality_2, multiply=1 +): if stats_modality_2: q = items.values(stats_modality_1, stats_modality_2) else: q = items.values(stats_modality_1) - if stats_sum_variable == 'pk': - q = q.annotate(sum=Count('pk')) + if stats_sum_variable == "pk": + q = q.annotate(sum=Count("pk")) else: q = q.annotate(sum=Sum(stats_sum_variable)) data = [] @@ -1365,32 +1438,47 @@ def _get_json_stats(items, stats_sum_variable, stats_modality_1, if not data or data[-1][0] != modality_1: data.append([modality_1, []]) data[-1][1].append( - (_format_modality(values[stats_modality_2]), - int((values["sum"] or 0) * multiply)) + ( + _format_modality(values[stats_modality_2]), + int((values["sum"] or 0) * multiply), + ) ) else: q = q.order_by(stats_modality_1) for values in q.all(): modality_1 = values[stats_modality_1] - data.append([_format_modality(modality_1), - int((values["sum"] or 0) * multiply)]) + data.append( + [_format_modality(modality_1), int((values["sum"] or 0) * multiply)] + ) data = json.dumps({"data": data}) - return HttpResponse(data, content_type='application/json') + return HttpResponse(data, content_type="application/json") DEFAULT_ROW_NUMBER = 10 # length is used by ajax DataTables requests -EXCLUDED_FIELDS = ['length'] -BASE_DATED_FIELDS = ['last_modified'] - - -def get_item(model, func_name, default_name, extra_request_keys=None, - base_request=None, bool_fields=None, reversed_bool_fields=None, - dated_fields=None, associated_models=None, - relative_session_names=None, specific_perms=None, - own_table_cols=None, relation_types_prefix=None, - do_not_deduplicate=False, model_for_perms=None, - alt_query_own=None, search_form=None): +EXCLUDED_FIELDS = ["length"] +BASE_DATED_FIELDS = ["last_modified"] + + +def get_item( + model, + func_name, + default_name, + extra_request_keys=None, + base_request=None, + bool_fields=None, + reversed_bool_fields=None, + dated_fields=None, + associated_models=None, + relative_session_names=None, + specific_perms=None, + own_table_cols=None, + relation_types_prefix=None, + do_not_deduplicate=False, + model_for_perms=None, + alt_query_own=None, + search_form=None, +): """ Generic treatment of tables @@ -1415,26 +1503,34 @@ def get_item(model, func_name, default_name, extra_request_keys=None, :param search_form: associated search form to manage JSON query keys :return: """ - def func(request, data_type='json', full=False, force_own=False, - col_names=None, no_link=False, no_limit=False, return_query=False, - **dct): + + def func( + request, + data_type="json", + full=False, + force_own=False, + col_names=None, + no_link=False, + no_limit=False, + return_query=False, + **dct + ): available_perms = [] if specific_perms: available_perms = specific_perms[:] - EMPTY = '' - if 'type' in dct: - data_type = dct.pop('type') + EMPTY = "" + if "type" in dct: + data_type = dct.pop("type") if not data_type: - data_type = 'json' + data_type = "json" if "json" in data_type: - EMPTY = '[]' + EMPTY = "[]" - if data_type not in ('json', 'csv', 'json-image', 'json-map', - 'json-stats'): - return HttpResponse(EMPTY, content_type='text/plain') + if data_type not in ("json", "csv", "json-image", "json-map", "json-stats"): + return HttpResponse(EMPTY, content_type="text/plain") - if data_type == 'json-stats' and len(model.STATISTIC_MODALITIES) < 2: - return HttpResponse(EMPTY, content_type='text/plain') + if data_type == "json-stats" and len(model.STATISTIC_MODALITIES) < 2: + return HttpResponse(EMPTY, content_type="text/plain") model_to_check = model if model_for_perms: @@ -1443,22 +1539,26 @@ def get_item(model, func_name, default_name, extra_request_keys=None, if return_query: allowed, own = True, False else: - allowed, own = check_model_access_control(request, model_to_check, - available_perms) + allowed, own = check_model_access_control( + request, model_to_check, available_perms + ) if not allowed: - return HttpResponse(EMPTY, content_type='text/plain') + return HttpResponse(EMPTY, content_type="text/plain") if force_own: own = True - if full == 'shortcut' and 'SHORTCUT_SEARCH' in request.session and \ - request.session['SHORTCUT_SEARCH'] == 'own': + if ( + full == "shortcut" + and "SHORTCUT_SEARCH" in request.session + and request.session["SHORTCUT_SEARCH"] == "own" + ): own = True query_own = None if own: q = models.IshtarUser.objects.filter(user_ptr=request.user) if not q.count(): - return HttpResponse(EMPTY, content_type='text/plain') + return HttpResponse(EMPTY, content_type="text/plain") if alt_query_own: query_own = getattr(model, alt_query_own)(q.all()[0]) else: @@ -1466,7 +1566,7 @@ def get_item(model, func_name, default_name, extra_request_keys=None, query_parameters = {} - if hasattr(model, 'get_query_parameters'): + if hasattr(model, "get_query_parameters"): query_parameters = model.get_query_parameters() # get defaults from model @@ -1476,7 +1576,7 @@ def get_item(model, func_name, default_name, extra_request_keys=None, my_extra_request_keys[key] = query_parameters[key].search_query else: my_extra_request_keys = copy(extra_request_keys or {}) - if base_request is None and hasattr(model, 'BASE_REQUEST'): + if base_request is None and hasattr(model, "BASE_REQUEST"): if callable(model.BASE_REQUEST): my_base_request = model.BASE_REQUEST(request) else: @@ -1485,50 +1585,55 @@ def get_item(model, func_name, default_name, extra_request_keys=None, my_base_request = copy(base_request) else: my_base_request = {} - if not bool_fields and hasattr(model, 'BOOL_FIELDS'): + if not bool_fields and hasattr(model, "BOOL_FIELDS"): my_bool_fields = model.BOOL_FIELDS[:] else: my_bool_fields = bool_fields[:] if bool_fields else [] - if not reversed_bool_fields and hasattr(model, 'REVERSED_BOOL_FIELDS'): + if not reversed_bool_fields and hasattr(model, "REVERSED_BOOL_FIELDS"): my_reversed_bool_fields = model.REVERSED_BOOL_FIELDS[:] else: - my_reversed_bool_fields = reversed_bool_fields[:] \ - if reversed_bool_fields else [] + my_reversed_bool_fields = ( + reversed_bool_fields[:] if reversed_bool_fields else [] + ) many_counted_fields = getattr(model, "MANY_COUNTED_FIELDS", None) reversed_many_counted_fields = getattr( - model, "REVERSED_MANY_COUNTED_FIELDS", None) + model, "REVERSED_MANY_COUNTED_FIELDS", None + ) - if not dated_fields and hasattr(model, 'DATED_FIELDS'): + if not dated_fields and hasattr(model, "DATED_FIELDS"): my_dated_fields = model.DATED_FIELDS[:] else: my_dated_fields = dated_fields[:] if dated_fields else [] my_dated_fields += BASE_DATED_FIELDS - if not associated_models and hasattr(model, 'ASSOCIATED_MODELS'): + if not associated_models and hasattr(model, "ASSOCIATED_MODELS"): my_associated_models = model.ASSOCIATED_MODELS[:] else: - my_associated_models = associated_models[:] \ - if associated_models else [] - if not relative_session_names and hasattr(model, - 'RELATIVE_SESSION_NAMES'): + my_associated_models = associated_models[:] if associated_models else [] + if not relative_session_names and hasattr(model, "RELATIVE_SESSION_NAMES"): my_relative_session_names = model.RELATIVE_SESSION_NAMES[:] else: - my_relative_session_names = relative_session_names[:] \ - if relative_session_names else [] - if not relation_types_prefix and hasattr(model, - 'RELATION_TYPES_PREFIX'): + my_relative_session_names = ( + relative_session_names[:] if relative_session_names else [] + ) + if not relation_types_prefix and hasattr(model, "RELATION_TYPES_PREFIX"): my_relation_types_prefix = copy(model.RELATION_TYPES_PREFIX) else: - my_relation_types_prefix = copy(relation_types_prefix) \ - if relation_types_prefix else {} + my_relation_types_prefix = ( + copy(relation_types_prefix) if relation_types_prefix else {} + ) fields = [model._meta.get_field(k) for k in get_all_field_names(model)] - request_keys = dict([ - (field.name, - field.name + (hasattr(field, 'rel') and field.rel and '__pk' - or '')) - for field in fields]) + request_keys = dict( + [ + ( + field.name, + field.name + (hasattr(field, "rel") and field.rel and "__pk" or ""), + ) + for field in fields + ] + ) # add keys of associated models to available request key for associated_model, key in my_associated_models: @@ -1538,19 +1643,34 @@ def get_item(model, func_name, default_name, extra_request_keys=None, associated_model = globals()[associated_model] associated_fields = [ associated_model._meta.get_field(k) - for k in get_all_field_names(associated_model)] + for k in get_all_field_names(associated_model) + ] request_keys.update( - dict([(key + "__" + field.name, - key + "__" + field.name + - (hasattr(field, 'rel') and field.rel and '__pk' or '')) - for field in associated_fields])) + dict( + [ + ( + key + "__" + field.name, + key + + "__" + + field.name + + (hasattr(field, "rel") and field.rel and "__pk" or ""), + ) + for field in associated_fields + ] + ) + ) request_keys.update(my_extra_request_keys) # manage search on json fields and excluded fields - if search_form and request and request.user and getattr( - request.user, 'ishtaruser', None): - available, excluded_fields, json_fields = \ - search_form.check_custom_form(request.user.ishtaruser) + if ( + search_form + and request + and request.user + and getattr(request.user, "ishtaruser", None) + ): + available, excluded_fields, json_fields = search_form.check_custom_form( + request.user.ishtaruser + ) # for now no manage on excluded_fields: should we prevent search on # some fields regarding the user concerned? if available: @@ -1561,23 +1681,24 @@ def get_item(model, func_name, default_name, extra_request_keys=None, if "query" in dct: request_items = dct["query"] request_items["submited"] = True - elif request.method == 'POST': + elif request.method == "POST": request_items = request.POST else: request_items = request.GET - count = dct.get('count', False) + count = dct.get("count", False) # pager try: - row_nb = int(request_items.get('length')) + row_nb = int(request_items.get("length")) except (ValueError, TypeError): row_nb = DEFAULT_ROW_NUMBER - if data_type == 'json-map': # other limit for map + if data_type == "json-map": # other limit for map row_nb = settings.ISHTAR_MAP_MAX_ITEMS - if no_limit or (data_type == 'json-map' and - request_items.get('no_limit', False)): + if no_limit or ( + data_type == "json-map" and request_items.get("no_limit", False) + ): row_nb = None dct_request_items = {} @@ -1587,8 +1708,8 @@ def get_item(model, func_name, default_name, extra_request_keys=None, if k in EXCLUDED_FIELDS: continue key = k[:] - if key.startswith('searchprefix_'): - key = key[len('searchprefix_'):] + if key.startswith("searchprefix_"): + key = key[len("searchprefix_") :] dct_request_items[key] = request_items[k] request_items = dct_request_items @@ -1603,19 +1724,19 @@ def get_item(model, func_name, default_name, extra_request_keys=None, and_reqs, or_reqs = [], [] exc_and_reqs, exc_or_reqs = [], [] distinct_queries = [] - dct['extras'], dct['and_reqs'], dct['exc_and_reqs'] = [], [], [] + dct["extras"], dct["and_reqs"], dct["exc_and_reqs"] = [], [], [] - if full == 'shortcut': + if full == "shortcut": if model.SLUG == "warehouse": - key = 'name__icontains' + key = "name__icontains" else: - key = 'cached_label__icontains' - dct[key] = request.GET.get('term', None) + key = "cached_label__icontains" + dct[key] = request.GET.get("term", None) try: - old = 'old' in request_items and int(request_items['old']) + old = "old" in request_items and int(request_items["old"]) except ValueError: - return HttpResponse('[]', content_type='text/plain') + return HttpResponse("[]", content_type="text/plain") for k in request_keys: val = request_items.get(k) @@ -1644,21 +1765,28 @@ def get_item(model, func_name, default_name, extra_request_keys=None, and_reqs.append(reqs) pinned_search = "" - base_keys = ['extras', 'and_reqs', 'exc_and_reqs'] + base_keys = ["extras", "and_reqs", "exc_and_reqs"] if my_base_request: base_keys += list(my_base_request) - has_a_search = any( - k for k in dct.keys() if k not in my_base_request) + has_a_search = any(k for k in dct.keys() if k not in my_base_request) # manage default and pinned search and not bookmark - if not has_a_search and not request_items.get("search_vector", "") \ - and full != 'shortcut': - if data_type == 'csv' and func_name in request.session: + if ( + not has_a_search + and not request_items.get("search_vector", "") + and full != "shortcut" + ): + if data_type == "csv" and func_name in request.session: dct = request.session[func_name] else: # default search dct, pinned_search = _manage_default_search( - dct, request, model, default_name, my_base_request, - my_relative_session_names) + dct, + request, + model, + default_name, + my_base_request, + my_relative_session_names, + ) elif func_name and request: request.session[func_name] = dct @@ -1667,16 +1795,20 @@ def get_item(model, func_name, default_name, extra_request_keys=None, query_parameters[k] = SearchAltName(k, request_keys[k]) dct, excluded_dct, distinct_queries = _search_manage_search_vector( - model, dct, excluded_dct, distinct_queries, query_parameters, + model, + dct, + excluded_dct, + distinct_queries, + query_parameters, ) search_vector = "" - if 'search_vector' in dct: - search_vector = dct.pop('search_vector') + if "search_vector" in dct: + search_vector = dct.pop("search_vector") # manage relations types - if 'relation_types' not in my_relation_types_prefix: - my_relation_types_prefix['relation_types'] = '' + if "relation_types" not in my_relation_types_prefix: + my_relation_types_prefix["relation_types"] = "" relation_types = {} for rtype_key in my_relation_types_prefix: relation_types[my_relation_types_prefix[rtype_key]] = set() @@ -1690,18 +1822,20 @@ def get_item(model, func_name, default_name, extra_request_keys=None, dct.pop(k) ) - _manage_bool_fields(model, my_bool_fields, my_reversed_bool_fields, - dct, or_reqs) - _manage_bool_fields(model, my_bool_fields, my_reversed_bool_fields, - excluded_dct, exc_or_reqs) + _manage_bool_fields( + model, my_bool_fields, my_reversed_bool_fields, dct, or_reqs + ) + _manage_bool_fields( + model, my_bool_fields, my_reversed_bool_fields, excluded_dct, exc_or_reqs + ) tmp_excluded = {} _manage_many_counted_fields( - many_counted_fields, reversed_many_counted_fields, - dct, tmp_excluded) + many_counted_fields, reversed_many_counted_fields, dct, tmp_excluded + ) _manage_many_counted_fields( - many_counted_fields, reversed_many_counted_fields, - excluded_dct, dct) + many_counted_fields, reversed_many_counted_fields, excluded_dct, dct + ) if tmp_excluded: excluded_dct.update(tmp_excluded) @@ -1715,12 +1849,12 @@ def get_item(model, func_name, default_name, extra_request_keys=None, _manage_facet_search(model, excluded_dct, exc_and_reqs) extras = [] - if 'extras' in dct: - extras = dct.pop('extras') - if 'and_reqs' in dct: - and_reqs += dct.pop('and_reqs') - if 'exc_and_reqs' in dct: - exc_and_reqs += dct.pop('exc_and_reqs') + if "extras" in dct: + extras = dct.pop("extras") + if "and_reqs" in dct: + and_reqs += dct.pop("and_reqs") + if "exc_and_reqs" in dct: + exc_and_reqs += dct.pop("exc_and_reqs") _manage_clean_search_field(dct, excluded_dct) _manage_clean_search_field(excluded_dct, dct) @@ -1729,23 +1863,23 @@ def get_item(model, func_name, default_name, extra_request_keys=None, exc_query = None if excluded_dct or exc_and_reqs or exc_or_reqs: exc_query = _construct_query( - relation_types, excluded_dct, exc_or_reqs, exc_and_reqs) + relation_types, excluded_dct, exc_or_reqs, exc_and_reqs + ) if query_own: query = query & query_own # manage hierarchic in shortcut menu - if full == 'shortcut': + if full == "shortcut": ASSOCIATED_ITEMS = { - Operation: (File, 'associated_file__pk'), - ContextRecord: (Operation, 'operation__pk'), - Find: (ContextRecord, 'base_finds__context_record__pk'), + Operation: (File, "associated_file__pk"), + ContextRecord: (Operation, "operation__pk"), + Find: (ContextRecord, "base_finds__context_record__pk"), } if model in ASSOCIATED_ITEMS: upper_model, upper_key = ASSOCIATED_ITEMS[model] model_name = upper_model.SLUG - current = model_name in request.session \ - and request.session[model_name] + current = model_name in request.session and request.session[model_name] if current: dct = {upper_key: current} query &= Q(**dct) @@ -1768,7 +1902,7 @@ def get_item(model, func_name, default_name, extra_request_keys=None, items = items.distinct() try: - items_nb = items.values('pk').aggregate(Count('pk'))['pk__count'] + items_nb = items.values("pk").aggregate(Count("pk"))["pk__count"] except ProgrammingError: items_nb = 0 if count: @@ -1776,21 +1910,27 @@ def get_item(model, func_name, default_name, extra_request_keys=None, # print(str(items.values("id").query).encode('utf-8')) if search_vector: # for serialization - dct['search_vector'] = search_vector + dct["search_vector"] = search_vector # table cols if own_table_cols: table_cols = own_table_cols else: if full: - table_cols = [field.name for field in model._meta.fields - if field.name not in PRIVATE_FIELDS] - table_cols += [field.name for field in model._meta.many_to_many - if field.name not in PRIVATE_FIELDS] - if hasattr(model, 'EXTRA_FULL_FIELDS'): + table_cols = [ + field.name + for field in model._meta.fields + if field.name not in PRIVATE_FIELDS + ] + table_cols += [ + field.name + for field in model._meta.many_to_many + if field.name not in PRIVATE_FIELDS + ] + if hasattr(model, "EXTRA_FULL_FIELDS"): table_cols += model.EXTRA_FULL_FIELDS else: - tb_key = (getattr(model, 'SLUG', None), 'TABLE_COLS') + tb_key = (getattr(model, "SLUG", None), "TABLE_COLS") if tb_key in settings.TABLE_COLS: table_cols = settings.TABLE_COLS[tb_key] else: @@ -1803,20 +1943,27 @@ def get_item(model, func_name, default_name, extra_request_keys=None, elif data_type == "json-stats": stats_modality_1 = request_items.get("stats_modality_1", None) stats_modality_2 = request_items.get("stats_modality_2", None) - if not stats_modality_1 or \ - stats_modality_1 not in model.STATISTIC_MODALITIES: + if ( + not stats_modality_1 + or stats_modality_1 not in model.STATISTIC_MODALITIES + ): stats_modality_1 = model.STATISTIC_MODALITIES[0] if stats_modality_2 not in model.STATISTIC_MODALITIES: stats_modality_2 = None - stats_sum_variable = request_items.get('stats_sum_variable', None) + stats_sum_variable = request_items.get("stats_sum_variable", None) stats_sum_variable_keys = list(model.STATISTIC_SUM_VARIABLE.keys()) - if not stats_sum_variable or \ - stats_sum_variable not in stats_sum_variable_keys: + if ( + not stats_sum_variable + or stats_sum_variable not in stats_sum_variable_keys + ): stats_sum_variable = stats_sum_variable_keys[0] multiply = model.STATISTIC_SUM_VARIABLE[stats_sum_variable][1] return _get_json_stats( - items, stats_sum_variable, stats_modality_1, stats_modality_2, - multiply=multiply + items, + stats_sum_variable, + stats_modality_1, + stats_modality_2, + multiply=multiply, ) query_table_cols = [] @@ -1824,52 +1971,53 @@ def get_item(model, func_name, default_name, extra_request_keys=None, if type(cols) not in (list, tuple): cols = [cols] for col in cols: - query_table_cols += col.split('|') + query_table_cols += col.split("|") # contextual (full, simple, etc.) col - contxt = full and 'full' or 'simple' - if hasattr(model, 'CONTEXTUAL_TABLE_COLS') and \ - contxt in model.CONTEXTUAL_TABLE_COLS: + contxt = full and "full" or "simple" + if ( + hasattr(model, "CONTEXTUAL_TABLE_COLS") + and contxt in model.CONTEXTUAL_TABLE_COLS + ): for idx, col in enumerate(table_cols): if col in model.CONTEXTUAL_TABLE_COLS[contxt]: - query_table_cols[idx] = \ - model.CONTEXTUAL_TABLE_COLS[contxt][col] + query_table_cols[idx] = model.CONTEXTUAL_TABLE_COLS[contxt][col] - if data_type in ('json-image', 'json-map') or full == 'shortcut': + if data_type in ("json-image", "json-map") or full == "shortcut": if model.SLUG == "warehouse": - query_table_cols.append('name') - table_cols.append('name') + query_table_cols.append("name") + table_cols.append("name") else: - query_table_cols.append('cached_label') - table_cols.append('cached_label') - if data_type == 'json-image': - query_table_cols.append('main_image__thumbnail') - table_cols.append('main_image__thumbnail') - query_table_cols.append('main_image__image') - table_cols.append('main_image__image') - elif data_type == 'json-map': + query_table_cols.append("cached_label") + table_cols.append("cached_label") + if data_type == "json-image": + query_table_cols.append("main_image__thumbnail") + table_cols.append("main_image__thumbnail") + query_table_cols.append("main_image__image") + table_cols.append("main_image__image") + elif data_type == "json-map": if model.SLUG == "find": - query_table_cols.append('base_finds__point_2d') - table_cols.append('base_finds__point_2d') + query_table_cols.append("base_finds__point_2d") + table_cols.append("base_finds__point_2d") else: - query_table_cols.append('point_2d') - table_cols.append('point_2d') + query_table_cols.append("point_2d") + table_cols.append("point_2d") # manage sort tables manual_sort_key = None sorts = {} for k in request_items: - if not k.startswith('order['): + if not k.startswith("order["): continue - num = int(k.split(']')[0][len("order["):]) + num = int(k.split("]")[0][len("order[") :]) if num not in sorts: - sorts[num] = ['', ''] # sign, col_num - if k.endswith('[dir]'): + sorts[num] = ["", ""] # sign, col_num + if k.endswith("[dir]"): order = request_items[k] - sign = order and order == 'desc' and "-" or '' + sign = order and order == "desc" and "-" or "" sorts[num][0] = sign - if k.endswith('[column]'): + if k.endswith("[column]"): sorts[num][1] = request_items[k] sign = "" if not sorts and model._meta.ordering: @@ -1890,14 +2038,16 @@ def get_item(model, func_name, default_name, extra_request_keys=None, ks = [ks] for k in ks: if k.endswith("__pk"): - k = k[:-len("__pk")] + "__label" + k = k[: -len("__pk")] + "__label" if k.endswith("towns"): k = k + "__cached_label" - if k.endswith("__icontains") or \ - k.endswith("__contains") or \ - k.endswith("__iexact") or \ - k.endswith("__exact"): - k = '__'.join(k.split('__')[:-1]) + if ( + k.endswith("__icontains") + or k.endswith("__contains") + or k.endswith("__iexact") + or k.endswith("__exact") + ): + k = "__".join(k.split("__")[:-1]) # if '__' in k: # k = k.split('__')[0] orders.append(signe + k) @@ -1909,7 +2059,9 @@ def get_item(model, func_name, default_name, extra_request_keys=None, manual_sort_key = k logger.warning( "**WARN get_item - {}**: manual sort key '{}'".format( - func_name, k)) + func_name, k + ) + ) break if not manual_sort_key: items = items.order_by(*orders) @@ -1919,14 +2071,14 @@ def get_item(model, func_name, default_name, extra_request_keys=None, page_nb = 1 if row_nb and data_type.startswith("json"): try: - start = int(request_items.get('start')) + start = int(request_items.get("start")) page_nb = start // row_nb + 1 assert page_nb >= 1 except (TypeError, ValueError, AssertionError): start = 0 page_nb = 1 end = int(page_nb * row_nb) - if full == 'shortcut': + if full == "shortcut": start = 0 end = 20 @@ -1938,19 +2090,21 @@ def get_item(model, func_name, default_name, extra_request_keys=None, if old: items = [item.get_previous(old) for item in items] - if data_type == 'json-map': + if data_type == "json-map": point_field = query_table_cols.pop() datas = _get_data_from_query( - items, query_table_cols, my_extra_request_keys, - point_field=point_field) - elif data_type != "csv" and getattr( - model, "NEW_QUERY_ENGINE", False): - datas = _get_data_from_query( - items, query_table_cols, my_extra_request_keys) + items, query_table_cols, my_extra_request_keys, point_field=point_field + ) + elif data_type != "csv" and getattr(model, "NEW_QUERY_ENGINE", False): + datas = _get_data_from_query(items, query_table_cols, my_extra_request_keys) else: datas = _get_data_from_query_old( - items, query_table_cols, request, my_extra_request_keys, - do_not_deduplicate) + items, + query_table_cols, + request, + my_extra_request_keys, + do_not_deduplicate, + ) if manual_sort_key: # +1 because the id is added as a first col @@ -1959,56 +2113,60 @@ def get_item(model, func_name, default_name, extra_request_keys=None, idx_col = query_table_cols.index(manual_sort_key) + 1 else: for idx, col in enumerate(query_table_cols): - if type(col) in (list, tuple) and \ - manual_sort_key in col: + if type(col) in (list, tuple) and manual_sort_key in col: idx_col = idx + 1 if idx_col is not None: datas = sorted(datas, key=lambda x: x[idx_col]) - if sign == '-': + if sign == "-": datas = reversed(datas) datas = list(datas)[start:end] - link_template = \ - "<a class='display_details' href='#' " \ - "onclick='load_window(\"{}\")'>" \ - "<i class=\"fa fa-info-circle\" aria-hidden=\"true\"></i><lock></a>" + link_template = ( + "<a class='display_details' href='#' " + "onclick='load_window(\"{}\")'>" + '<i class="fa fa-info-circle" aria-hidden="true"></i><lock></a>' + ) link_ext_template = '<a href="{}" target="_blank">{}</a>' lock = ' <i class="fa fa-lock text-danger" aria-hidden="true"></i>' - own_lock = ' <i class="fa fa-lock text-success" ' \ - 'aria-hidden="true"></i>' + own_lock = ' <i class="fa fa-lock text-success" ' 'aria-hidden="true"></i>' has_locks = hasattr(model, "locked") current_user_id = request.user and request.user.id if data_type.startswith("json"): rows = [] - if data_type == 'json-map': + if data_type == "json-map": lnk = link_template.format( - reverse('show-' + default_name, args=[999999, '']), + reverse("show-" + default_name, args=[999999, ""]), ) - lnk = lnk.replace('999999', "<pk>") + lnk = lnk.replace("999999", "<pk>") if not has_locks: - lnk = lnk.replace('<lock>', "") + lnk = lnk.replace("<lock>", "") data = json.dumps(_format_geojson(datas, lnk)) - return HttpResponse(data, content_type='application/json') + return HttpResponse(data, content_type="application/json") for data in datas: res = { - 'id': data[0], + "id": data[0], } if not no_link: try: lnk_template = link_template lnk = lnk_template.format( - reverse('show-' + default_name, args=[data[0], ''])) + reverse("show-" + default_name, args=[data[0], ""]) + ) if has_locks and data[-2]: if data[-1] == current_user_id: - lnk = lnk.replace('<lock>', own_lock) + lnk = lnk.replace("<lock>", own_lock) else: - lnk = lnk.replace('<lock>', lock) + lnk = lnk.replace("<lock>", lock) else: - lnk = lnk.replace('<lock>', "") + lnk = lnk.replace("<lock>", "") except NoReverseMatch: logger.warning( - '**WARN "show-' + default_name + '" args (' - + str(data[0]) + ") url not available") - lnk = '' + '**WARN "show-' + + default_name + + '" args (' + + str(data[0]) + + ") url not available" + ) + lnk = "" res["link"] = lnk for idx, value in enumerate(data[1:]): if not value or idx >= len(table_cols): @@ -2019,55 +2177,59 @@ def get_item(model, func_name, default_name, extra_request_keys=None, tab_cols = [] # foreign key may be divided by "." or "__" for tc in table_col: - if '.' in tc: - tab_cols += tc.split('.') - elif '__' in tc: - tab_cols += tc.split('__') + if "." in tc: + tab_cols += tc.split(".") + elif "__" in tc: + tab_cols += tc.split("__") else: tab_cols.append(tc) k = "__".join(tab_cols) if k.endswith("__image") or k.endswith("__thumbnail"): - if not value.startswith(settings.MEDIA_ROOT) and not \ - value.startswith("http://") and not \ - value.startswith("https://"): + if ( + not value.startswith(settings.MEDIA_ROOT) + and not value.startswith("http://") + and not value.startswith("https://") + ): value = settings.MEDIA_URL + value - if hasattr(model, 'COL_LINK') and k in model.COL_LINK: + if hasattr(model, "COL_LINK") and k in model.COL_LINK: value = link_ext_template.format(value, value) if isinstance(value, datetime.date): - value = value.strftime('%Y-%m-%d') + value = value.strftime("%Y-%m-%d") if isinstance(value, datetime.datetime): - value = value.strftime('%Y-%m-%d %H:%M:%S') + value = value.strftime("%Y-%m-%d %H:%M:%S") res[k] = value - if full == 'shortcut': - if 'cached_label' in res: - res['value'] = res.pop('cached_label') - elif 'name' in res: - res['value'] = res.pop('name') + if full == "shortcut": + if "cached_label" in res: + res["value"] = res.pop("cached_label") + elif "name" in res: + res["value"] = res.pop("name") rows.append(res) - if full == 'shortcut': + if full == "shortcut": data = json.dumps(rows) else: total = ( - items_nb // row_nb + (1 if items_nb % row_nb else 0) - ) if row_nb else items_nb - data = json.dumps({ - "recordsTotal": items_nb, - "recordsFiltered": items_nb, - "rows": rows, - "table-cols": table_cols, - "pinned-search": pinned_search, - "page": page_nb, - "total": total, - }) - return HttpResponse(data, content_type='application/json') + (items_nb // row_nb + (1 if items_nb % row_nb else 0)) + if row_nb + else items_nb + ) + data = json.dumps( + { + "recordsTotal": items_nb, + "recordsFiltered": items_nb, + "rows": rows, + "table-cols": table_cols, + "pinned-search": pinned_search, + "page": page_nb, + "total": total, + } + ) + return HttpResponse(data, content_type="application/json") elif data_type == "csv": - response = HttpResponse(content_type='text/csv', charset=ENCODING) + response = HttpResponse(content_type="text/csv", charset=ENCODING) n = datetime.datetime.now() - filename = '%s_%s.csv' % ( - default_name, n.strftime('%Y%m%d-%H%M%S')) - response['Content-Disposition'] = 'attachment; filename=%s' \ - % filename + filename = "%s_%s.csv" % (default_name, n.strftime("%Y%m%d-%H%M%S")) + response["Content-Disposition"] = "attachment; filename=%s" % filename writer = csv.writer(response, **CSV_OPTIONS) if col_names: col_names = [name for name in col_names] @@ -2076,8 +2238,7 @@ def get_item(model, func_name, default_name, extra_request_keys=None, for field_name in table_cols: if type(field_name) in (list, tuple): field_name = " & ".join(field_name) - if hasattr(model, 'COL_LABELS') and \ - field_name in model.COL_LABELS: + if hasattr(model, "COL_LABELS") and field_name in model.COL_LABELS: field = model.COL_LABELS[field_name] col_names.append(str(field)) continue @@ -2090,7 +2251,8 @@ def get_item(model, func_name, default_name, extra_request_keys=None, "**WARN get_item - csv export**: no col name " "for {}\nadd explicit label to " "COL_LABELS attribute of " - "{}".format(field_name, model)) + "{}".format(field_name, model) + ) continue col_names.append(str(field.verbose_name)) writer.writerow(col_names) @@ -2102,8 +2264,7 @@ def get_item(model, func_name, default_name, extra_request_keys=None, break val = data[1:][idx + delta] if col_name and "|" in col_name[0]: - for delta_idx in range( - len(col_name[0].split('|')) - 1): + for delta_idx in range(len(col_name[0].split("|")) - 1): delta += 1 val += data[1:][idx + delta] row.append(val) @@ -2115,10 +2276,9 @@ def get_item(model, func_name, default_name, extra_request_keys=None, try: vals.append(v.encode(ENCODING).decode(ENCODING)) except UnicodeEncodeError: - vals.append(unidecode(v).encode(ENCODING).decode( - ENCODING)) + vals.append(unidecode(v).encode(ENCODING).decode(ENCODING)) writer.writerow(vals) return response - return HttpResponse('{}', content_type='text/plain') + return HttpResponse("{}", content_type="text/plain") return func diff --git a/ishtar_common/widgets.py b/ishtar_common/widgets.py index 67a16e4b7..8f95f4521 100644 --- a/ishtar_common/widgets.py +++ b/ishtar_common/widgets.py @@ -29,8 +29,10 @@ from django.core.urlresolvers import reverse, NoReverseMatch from django.db.models import fields from django.forms import ClearableFileInput from django.forms.utils import flatatt -from django.forms.widgets import CheckboxSelectMultiple as \ - CheckboxSelectMultipleBase, NumberInput +from django.forms.widgets import ( + CheckboxSelectMultiple as CheckboxSelectMultipleBase, + NumberInput, +) from django.template import loader from django.template.defaultfilters import slugify from django.utils.encoding import smart_text @@ -48,8 +50,8 @@ reverse_lazy = lazy(reverse, str) class SelectReadonly(forms.Select): - template_name = 'blocks/readonly_input.html' - option_template_name = 'blocks/readonly_input_option.html' + template_name = "blocks/readonly_input.html" + option_template_name = "blocks/readonly_input_option.html" def __init__(self, attrs=None, choices=(), model=None, available=None): super(SelectReadonly, self).__init__(attrs, choices) @@ -63,7 +65,7 @@ class SelectReadonly(forms.Select): if value: q = q.filter(pk=value) for i in q.all(): - if hasattr(self.model, 'verbose_name'): + if hasattr(self.model, "verbose_name"): label = i.verbose_name else: label = str(i) @@ -77,18 +79,27 @@ class SelectReadonly(forms.Select): class SelectReadonlyField(forms.ChoiceField): - def __init__(self, choices=(), required=True, widget=None, label=None, - initial=None, help_text='', *args, **kwargs): + def __init__( + self, + choices=(), + required=True, + widget=None, + label=None, + initial=None, + help_text="", + *args, + **kwargs + ): self.available = False self.model = None - if 'model' in kwargs: - self.model = kwargs.pop('model') - if 'available' in kwargs: - self.available = kwargs.pop('available') + if "model" in kwargs: + self.model = kwargs.pop("model") + if "available" in kwargs: + self.available = kwargs.pop("available") widget = SelectReadonly(model=self.model, available=self.available) super(SelectReadonlyField, self).__init__( - choices, required, widget, label, initial, help_text, *args, - **kwargs) + choices, required, widget, label, initial, help_text, *args, **kwargs + ) def get_q(self): q = self.model.objects @@ -106,12 +117,10 @@ class Select2Media(object): @property def media(self): media = super(Select2Media, self).media - css = { - 'all': ('select2/css/select2.css',) - } - js = ['select2/js/select2.full.min.js'] + css = {"all": ("select2/css/select2.css",)} + js = ["select2/js/select2.full.min.js"] for lang_code, lang in settings.LANGUAGES: - js.append('select2/js/i18n/{}.js'.format(lang_code)) + js.append("select2/js/i18n/{}.js".format(lang_code)) media.add_css(css) media.add_js(js) return media @@ -123,20 +132,20 @@ class Select2Dynamic(Select2Media, forms.Select): """ def render(self, name, value, attrs=None, choices=()): - choices = choices or getattr(self, 'choices', []) + choices = choices or getattr(self, "choices", []) if value and value not in [key for key, v in choices]: choices.insert(1, (value, value)) self.choices = choices - klass = attrs and attrs.get('class') or '' - klass += ' ' if klass else '' + 'js-select2' + klass = attrs and attrs.get("class") or "" + klass += " " if klass else "" + "js-select2" if not attrs: attrs = {} - attrs['class'] = klass - if 'style' not in attrs: - if attrs.get('full-width', None): - attrs['style'] = "width: calc(100% - 60px)" + attrs["class"] = klass + if "style" not in attrs: + if attrs.get("full-width", None): + attrs["style"] = "width: calc(100% - 60px)" else: - attrs['style'] = "width: 370px" + attrs["style"] = "width: 370px" options = [ "tags: true", ] @@ -149,7 +158,7 @@ class Select2Dynamic(Select2Media, forms.Select): return confirm("{}"); }}""".format(msg)) ''' - if attrs.get('full-width', None): + if attrs.get("full-width", None): options.append("containerCssClass: 'full-width'") html = super(Select2Dynamic, self).render(name, value, attrs) @@ -157,7 +166,9 @@ class Select2Dynamic(Select2Media, forms.Select): $(document).ready(function() {{ $("#id_{}").select2({{ {} }}); }});</script> - """.format(name, ", ".join(options)) + """.format( + name, ", ".join(options) + ) return mark_safe(html) @@ -170,9 +181,7 @@ class Select2DynamicField(forms.ChoiceField): used. """ if value and '"' in value: - raise ValidationError( - _("The character \" is not accepted.") - ) + raise ValidationError(_('The character " is not accepted.')) def to_python(self, value): """ @@ -182,8 +191,9 @@ class Select2DynamicField(forms.ChoiceField): class Select2Base(Select2Media): - def __init__(self, attrs=None, choices=(), remote=None, model=None, - new=None, available=None): + def __init__( + self, attrs=None, choices=(), remote=None, model=None, new=None, available=None + ): self.remote = remote self.available = available self.model = model @@ -202,35 +212,36 @@ class Select2Base(Select2Media): def render(self, name, value, attrs=None, choices=()): self.remote = str(self.remote) - if self.remote in ('None', 'false'): + if self.remote in ("None", "false"): # test on lazy object is buggy... so we have this ugly test self.remote = None if not choices: if not self.remote and self.model: choices = self.get_choices() - if hasattr(self, 'choices') and self.choices: + if hasattr(self, "choices") and self.choices: choices = self.choices new_attrs = self.attrs.copy() new_attrs.update(attrs) attrs = new_attrs - klass = attrs and attrs.get('class') or '' - klass += ' ' if klass else '' + 'js-select2' + klass = attrs and attrs.get("class") or "" + klass += " " if klass else "" + "js-select2" if not attrs: attrs = {} - attrs['class'] = klass - if 'style' not in attrs: - if attrs.get('full-width', None): - attrs['style'] = "width: calc(100% - 60px)" + attrs["class"] = klass + if "style" not in attrs: + if attrs.get("full-width", None): + attrs["style"] = "width: calc(100% - 60px)" else: - attrs['style'] = "width: 370px" + attrs["style"] = "width: 370px" if value: if type(value) not in (list, tuple): - value = value.split(',') + value = value.split(",") options = "" if self.remote: - options = """{ + options = ( + """{ ajax: { url: '%s', delay: 250, @@ -249,7 +260,9 @@ class Select2Base(Select2Media): } } } - }""" % self.remote + }""" + % self.remote + ) if value: choices = [] for v in value: @@ -258,7 +271,7 @@ class Select2Base(Select2Media): except (self.model.DoesNotExist, ValueError): # an old reference? it should not happen pass - if attrs.get('full-width', None): + if attrs.get("full-width", None): if options: options = options[:-1] + ", " else: @@ -269,14 +282,16 @@ class Select2Base(Select2Media): new, html = "", "" if self.new: html = "<div class='input-group'>" - url_new = 'new-' + self.model.SLUG + url_new = "new-" + self.model.SLUG url_new = reverse(url_new, args=["id_" + name]) # WARNING: the modal for the form must be in the main template # "extra_form_modals" list is used for that in form or view - new = """<span class="input-group-append">"""\ - """<a href="#" class="add-button input-group-text" """\ - """onclick="dt_qa_open('{}', 'modal-dynamic-form-{}');">"""\ - """+</a></span></div>""".format(url_new, self.model.SLUG) + new = ( + """<span class="input-group-append">""" + """<a href="#" class="add-button input-group-text" """ + """onclick="dt_qa_open('{}', 'modal-dynamic-form-{}');">""" + """+</a></span></div>""".format(url_new, self.model.SLUG) + ) html += super(Select2Base, self).render(name, value, attrs) html += new @@ -284,7 +299,9 @@ class Select2Base(Select2Media): $(document).ready(function() {{ $("#id_{}").select2({}); }});</script> - """.format(name, options) + """.format( + name, options + ) return mark_safe(html) @@ -302,9 +319,10 @@ class CheckboxSelectMultiple(CheckboxSelectMultipleBase): Should be corrected on recent Django version. TODO: test and remove (test case: treatment type not keep on modif) """ + def render(self, name, value, attrs=None, choices=()): if type(value) in (str, str): - value = value.split(',') + value = value.split(",") if not isinstance(value, (list, tuple)): value = [value] return super(CheckboxSelectMultiple, self).render(name, value, attrs) @@ -315,36 +333,40 @@ class Select2BaseField(object): def __init__(self, *args, **kwargs): new = None - if 'new' in kwargs: - new = kwargs.pop('new') + if "new" in kwargs: + new = kwargs.pop("new") remote = None - if 'remote' in kwargs: - remote = kwargs.pop('remote') + if "remote" in kwargs: + remote = kwargs.pop("remote") self.model, self.remote = None, None - if 'model' in kwargs: - self.model = kwargs.pop('model') + if "model" in kwargs: + self.model = kwargs.pop("model") if remote: self.remote = reverse_lazy( - 'autocomplete-' + self.model.__name__.lower()) + "autocomplete-" + self.model.__name__.lower() + ) long_widget = False - if 'long_widget' in kwargs: - long_widget = kwargs.pop('long_widget') + if "long_widget" in kwargs: + long_widget = kwargs.pop("long_widget") self.available = False - if 'available' in kwargs: - self.available = kwargs.pop('available') + if "available" in kwargs: + self.available = kwargs.pop("available") attrs = {} if long_widget: - attrs['cols'] = True - attrs['full-width'] = True + attrs["cols"] = True + attrs["full-width"] = True if self.multiple: widget = Select2Multiple else: widget = Select2Simple if kwargs.get("style", None): attrs["style"] = kwargs.pop("style") - kwargs['widget'] = widget( - model=self.model, available=self.available, remote=self.remote, - new=new, attrs=attrs + kwargs["widget"] = widget( + model=self.model, + available=self.available, + remote=self.remote, + new=new, + attrs=attrs, ) super(Select2BaseField, self).__init__(*args, **kwargs) @@ -366,7 +388,7 @@ class Select2MultipleField(Select2BaseField, forms.MultipleChoiceField): def to_python(self, value): if not isinstance(value, (list, tuple)): if value: - value = value.split(',') + value = value.split(",") else: value = [] return super(Select2MultipleField, self).to_python(value) @@ -380,8 +402,8 @@ class DeleteWidget(forms.CheckboxInput): def render(self, name, value, attrs=None, renderer=None): final_attrs = flatatt( self.build_attrs( - attrs, {"name": name, "value": '1', - 'class': "btn btn-danger"}) + attrs, {"name": name, "value": "1", "class": "btn btn-danger"} + ) ) output = "<button%s>%s</button>" % (final_attrs, _("Delete")) @@ -394,22 +416,26 @@ class SwitchWidget(forms.CheckboxInput): def render(self, name, value, attrs=None, renderer=None): extra_class = (" " + self.extra_class) if self.extra_class else "" - default = {"name": name, "value": "1", - 'class': "switch" + extra_class, - 'type': 'checkbox'} + default = { + "name": name, + "value": "1", + "class": "switch" + extra_class, + "type": "checkbox", + } if value: - default['checked'] = 'checked' - attrs = self.build_attrs( - attrs, default - ) + default["checked"] = "checked" + attrs = self.build_attrs(attrs, default) final_attrs = flatatt(attrs) extra_label = "" if self.extra_label: - extra_label = '<label for="{}">{}</label>'.format(attrs['id'], - self.extra_label) + extra_label = '<label for="{}">{}</label>'.format( + attrs["id"], self.extra_label + ) output = """<span class="switch{}"> <input{}>{} -</span>""".format(extra_class, final_attrs, extra_label) +</span>""".format( + extra_class, final_attrs, extra_label + ) return mark_safe(output) @@ -419,7 +445,7 @@ class DeleteSwitchWidget(SwitchWidget): class ImageFileInput(ClearableFileInput): - template_name = 'widgets/image_input.html' + template_name = "widgets/image_input.html" NO_FORM_CONTROL = True def format_value(self, value): @@ -427,15 +453,16 @@ class ImageFileInput(ClearableFileInput): return value # try to display posted images try: - has_file = hasattr(value, 'file') + has_file = hasattr(value, "file") except ValueError: has_file = False if has_file: - if hasattr(value, 'file'): + if hasattr(value, "file"): full_path = str(value.file) if full_path.startswith(settings.MEDIA_ROOT): - value.url = settings.MEDIA_URL + full_path[ - len(settings.MEDIA_ROOT):] + value.url = ( + settings.MEDIA_URL + full_path[len(settings.MEDIA_ROOT) :] + ) elif value: full_path = settings.MEDIA_ROOT + str(value) try: @@ -450,16 +477,15 @@ class ImageFileInput(ClearableFileInput): def get_context(self, name, value, attrs): context = super(ImageFileInput, self).get_context(name, value, attrs) - if getattr(self, 'hidden', None): - context['hidden_value'] = self.hidden + if getattr(self, "hidden", None): + context["hidden_value"] = self.hidden # on post memory file is used: display the name - if getattr(self, 'hidden_name', None): - context['hidden_name_value'] = self.hidden_name + if getattr(self, "hidden_name", None): + context["hidden_name_value"] = self.hidden_name return context def value_from_datadict(self, data, files, name): - value = super(ImageFileInput, self).value_from_datadict( - data, files, name) + value = super(ImageFileInput, self).value_from_datadict(data, files, name) hidden_name = name + "-hidden" hidden_name_value = name + "-hidden-name" self.hidden, self.hidden_name = None, None @@ -482,11 +508,12 @@ class CustomWidget(forms.TextInput): def render(self, name, value, attrs=None, renderer=None): if not value: value = "" - final_attrs = flatatt( - self.build_attrs(attrs, {"name": name, "value": value})) - dct = {'final_attrs': final_attrs, - 'id': attrs['id'], - "safe_id": attrs['id'].replace('-', '_')} + final_attrs = flatatt(self.build_attrs(attrs, {"name": name, "value": value})) + dct = { + "final_attrs": final_attrs, + "id": attrs["id"], + "safe_id": attrs["id"].replace("-", "_"), + } dct.update(self.EXTRA_DCT) t = loader.get_template(self.TEMPLATE) rendered = t.render(dct) @@ -494,33 +521,33 @@ class CustomWidget(forms.TextInput): class SquareMeterWidget(CustomWidget): - TEMPLATE = 'widgets/SquareMeterWidget.html' - EXTRA_DCT = {'unit': settings.SURFACE_UNIT_LABEL} + TEMPLATE = "widgets/SquareMeterWidget.html" + EXTRA_DCT = {"unit": settings.SURFACE_UNIT_LABEL} class GramKilogramWidget(CustomWidget): - TEMPLATE = 'widgets/GramKilogramWidget.html' - EXTRA_DCT = {'unit': "g"} + TEMPLATE = "widgets/GramKilogramWidget.html" + EXTRA_DCT = {"unit": "g"} class CentimeterMeterWidget(CustomWidget): - TEMPLATE = 'widgets/CentimeterMeterWidget.html' - EXTRA_DCT = {'unit': "cm"} + TEMPLATE = "widgets/CentimeterMeterWidget.html" + EXTRA_DCT = {"unit": "cm"} AreaWidget = forms.TextInput -if settings.SURFACE_UNIT == 'square-metre': +if settings.SURFACE_UNIT == "square-metre": AreaWidget = SquareMeterWidget class ISBNWidget(CustomWidget): - TEMPLATE = 'widgets/CheckTextWidget.html' - EXTRA_DCT = {'validator': "is_valid_isbn"} + TEMPLATE = "widgets/CheckTextWidget.html" + EXTRA_DCT = {"validator": "is_valid_isbn"} class ISSNWidget(CustomWidget): - TEMPLATE = 'widgets/CheckTextWidget.html' - EXTRA_DCT = {'validator': "is_valid_issn"} + TEMPLATE = "widgets/CheckTextWidget.html" + EXTRA_DCT = {"validator": "is_valid_issn"} class CheckboxInput(forms.CheckboxInput): @@ -528,7 +555,7 @@ class CheckboxInput(forms.CheckboxInput): class SearchWidget(forms.TextInput): - template_name = 'widgets/search_input.html' + template_name = "widgets/search_input.html" def __init__(self, app_name=None, model=None, pin_model=None, attrs=None): super(SearchWidget, self).__init__(attrs) @@ -540,9 +567,9 @@ class SearchWidget(forms.TextInput): def get_context(self, name, value, attrs): context = super(SearchWidget, self).get_context(name, value, attrs) - context['app_name'] = self.app_name - context['model'] = self.model - context['pin_model'] = self.pin_model + context["app_name"] = self.app_name + context["model"] = self.model + context["pin_model"] = self.pin_model return context @@ -560,9 +587,7 @@ class ModelFieldMixin(object): values.append(self.model.objects.get(pk=v)) except self.model.DoesNotExist: raise ValidationError( - str( - _("{} is not a valid key for {}") - ).format(v, self.model) + str(_("{} is not a valid key for {}")).format(v, self.model) ) if not self.multiple: return values[0] @@ -576,32 +601,47 @@ class ModelChoiceField(ModelFieldMixin, forms.ChoiceField): super(ModelFieldMixin, self).__init__(*args, **kwargs) def valid_value(self, value): - if value and getattr(value, 'pk', None) in [v for v, l in self.choices]: + if value and getattr(value, "pk", None) in [v for v, l in self.choices]: return True return super(ModelChoiceField, self).valid_value(value) class ModelJQueryAutocompleteField(ModelFieldMixin, forms.CharField): - def __init__(self, model, multiple=False, new=False, long_widget=False, - *args, **kwargs): + def __init__( + self, model, multiple=False, new=False, long_widget=False, *args, **kwargs + ): self.model = model self.multiple = multiple attrs = {} if long_widget: - attrs['cols'] = True - attrs['full-width'] = True - kwargs['widget'] = JQueryAutoComplete( - reverse_lazy('autocomplete-' + self.model.SLUG), - associated_model=self.model, new=new, multiple=multiple, - attrs=attrs + attrs["cols"] = True + attrs["full-width"] = True + kwargs["widget"] = JQueryAutoComplete( + reverse_lazy("autocomplete-" + self.model.SLUG), + associated_model=self.model, + new=new, + multiple=multiple, + attrs=attrs, ) super(ModelJQueryAutocompleteField, self).__init__(*args, **kwargs) class JQueryAutoComplete(forms.TextInput): - def __init__(self, source, associated_model=None, options=None, attrs=None, - new=False, url_new='', multiple=False, limit=None, - dynamic_limit=None, detail=False, modify=False, tips=""): + def __init__( + self, + source, + associated_model=None, + options=None, + attrs=None, + new=False, + url_new="", + multiple=False, + limit=None, + dynamic_limit=None, + detail=False, + modify=False, + tips="", + ): """ Source can be a list containing the autocomplete values or a string containing the url used for the request. @@ -631,7 +671,7 @@ class JQueryAutoComplete(forms.TextInput): if not self.multiple: return data.get(name, None) if type(v) == str and "," in v: - return [item.strip() for item in v.split(',') if item.strip()] + return [item.strip() for item in v.split(",") if item.strip()] return data.getlist(name, None) def render_js(self, field_id, current_pk): @@ -643,36 +683,42 @@ class JQueryAutoComplete(forms.TextInput): try: source = "'" + str(self.source) + "'" except: - raise ValueError('{} source type is not valid'.format( - self.source)) + raise ValueError("{} source type is not valid".format(self.source)) dynamic_limit = [] for lim in self.dynamic_limit: - field_ids = field_id.split('-') + field_ids = field_id.split("-") if field_ids[1:-1]: dynamic_limit.append( - 'id_' + lim.replace('_', '') + '-' + - '-'.join(field_ids[1:-1]) + '-' + lim) + "id_" + + lim.replace("_", "") + + "-" + + "-".join(field_ids[1:-1]) + + "-" + + lim + ) else: - dynamic_limit.append('id_' + lim.replace('_', '')) - - dct = {'source': mark_safe(source), - 'field_id': field_id, - 'safe_field_id': field_id.replace("-", "_"), - "modify": self.modify, - 'dynamic_limit': dynamic_limit} + dynamic_limit.append("id_" + lim.replace("_", "")) + + dct = { + "source": mark_safe(source), + "field_id": field_id, + "safe_field_id": field_id.replace("-", "_"), + "modify": self.modify, + "dynamic_limit": dynamic_limit, + } if self.associated_model: model_name = self.associated_model._meta.object_name.lower() dct["model_name"] = model_name if self.detail: model_name = self.associated_model._meta.object_name.lower() - url_detail = '/detail-{}/'.format(model_name) + url_detail = "/detail-{}/".format(model_name) dct["detail"] = url_detail if self.options: - dct['options'] = mark_safe('%s' % self.options) + dct["options"] = mark_safe("%s" % self.options) - tpl = 'blocks/JQueryAutocomplete.js' + tpl = "blocks/JQueryAutocomplete.js" if self.multiple: - tpl = 'blocks/JQueryAutocompleteMultiple.js' + tpl = "blocks/JQueryAutocompleteMultiple.js" t = loader.get_template(tpl) js = t.render(dct) return js @@ -680,19 +726,19 @@ class JQueryAutoComplete(forms.TextInput): def render(self, name, value, attrs=None, renderer=None): attrs_hidden = self.build_attrs(attrs, {"name": name}) attrs_select = self.build_attrs(attrs) - attrs_select['placeholder'] = _("Search...") + attrs_select["placeholder"] = _("Search...") values = [] if value: hiddens = [] selects = [] if type(value) not in (list, tuple): values = str(escape(smart_text(value))) - values = values.replace('[', '').replace(']', '') - values = values.split(',') + values = values.replace("[", "").replace("]", "") + values = values.split(",") else: values = [] for v in value: - values += v.split(',') + values += v.split(",") for v in values: if not v: continue @@ -700,28 +746,27 @@ class JQueryAutoComplete(forms.TextInput): selects.append(v) if self.associated_model: try: - selects[-1] = str( - self.associated_model.objects.get(pk=v)) + selects[-1] = str(self.associated_model.objects.get(pk=v)) except (self.associated_model.DoesNotExist, ValueError): selects.pop() hiddens.pop() if self.multiple: - attrs_hidden['value'] = ", ".join(hiddens) + attrs_hidden["value"] = ", ".join(hiddens) if selects: selects.append("") - attrs_select['value'] = ", ".join(selects) + attrs_select["value"] = ", ".join(selects) else: if hiddens and selects: - attrs_hidden['value'] = hiddens[0] - attrs_select['value'] = selects[0] - if 'id' not in self.attrs: - attrs_hidden['id'] = 'id_%s' % name - attrs_select['id'] = 'id_select_%s' % name - if 'class' not in attrs_select: - attrs_select['class'] = 'autocomplete' - - has_previous_value = 'value' in attrs_select and attrs_select['value'] - attrs_select['class'] += ' form-control' + attrs_hidden["value"] = hiddens[0] + attrs_select["value"] = selects[0] + if "id" not in self.attrs: + attrs_hidden["id"] = "id_%s" % name + attrs_select["id"] = "id_select_%s" % name + if "class" not in attrs_select: + attrs_select["class"] = "autocomplete" + + has_previous_value = "value" in attrs_select and attrs_select["value"] + attrs_select["class"] += " form-control" new = "" html = "" if self.tips or self.new or self.modify: @@ -740,7 +785,9 @@ class JQueryAutoComplete(forms.TextInput): <span class="add-button input-group-text"> <em id="{}-tips">{}</em> </span></span> - """.format(attrs_hidden['id'], tips) + """.format( + attrs_hidden["id"], tips + ) if self.modify: new += """ <span class="input-group-append"> @@ -748,17 +795,16 @@ class JQueryAutoComplete(forms.TextInput): onclick="{}_modify();"> <i class="fa fa-pencil"></i></a> </span>""".format( - attrs_hidden['id'], - name.replace("-", "_")) + attrs_hidden["id"], name.replace("-", "_") + ) if self.new: limits = [] for k in self.limit: - limits.append(k + "__" + "-".join( - [str(v) for v in self.limit[k]])) - args = [attrs_select['id']] + limits.append(k + "__" + "-".join([str(v) for v in self.limit[k]])) + args = [attrs_select["id"]] if limits: - args.append(';'.join(limits)) - url_new = 'new-' + model_name + args.append(";".join(limits)) + url_new = "new-" + model_name if self.url_new: url_new = self.url_new url_new = reverse(url_new, args=args) @@ -767,12 +813,16 @@ class JQueryAutoComplete(forms.TextInput): <a href="#" class="add-button input-group-text" onclick="dt_qa_open('{}', 'modal-dynamic-form-{}');">+</a> </span> - """.format(url_new, model_name, model_name) + """.format( + url_new, model_name, model_name + ) new += "</div>" detail = "" if self.detail: detail = """<div class="form-control detail-value" id="{}-detail"> - </div>""".format(attrs_hidden['id']) + </div>""".format( + attrs_hidden["id"] + ) old_value = "" if has_previous_value: @@ -790,16 +840,17 @@ class JQueryAutoComplete(forms.TextInput): </span> </div>""".format( _("Prev.:"), - attrs_hidden['id'] + "_previous_label", - attrs_select['value'], - attrs_hidden['id'] + "_previous_button", - _("Restore previous") + attrs_hidden["id"] + "_previous_label", + attrs_select["value"], + attrs_hidden["id"] + "_previous_button", + _("Restore previous"), ) attrs_hidden_previous = attrs_hidden.copy() - attrs_hidden_previous['name'] += "_previous" - attrs_hidden_previous['id'] += "_previous" + attrs_hidden_previous["name"] += "_previous" + attrs_hidden_previous["id"] += "_previous" old_value += "<input type='hidden'{}>".format( - flatatt(attrs_hidden_previous)) + flatatt(attrs_hidden_previous) + ) pk = None if values: pk = values[0] @@ -813,8 +864,9 @@ class JQueryAutoComplete(forms.TextInput): old_value=old_value, attrs_select=flatatt(attrs_select), attrs_hidden=flatatt(attrs_hidden), - js=self.render_js(name, pk), new=new, - detail=detail + js=self.render_js(name, pk), + new=new, + detail=detail, ) return html @@ -824,8 +876,7 @@ class JQueryTown(forms.TextInput): Town fields with state and department pre-selections """ - def __init__(self, source, options={}, - attrs={}, new=False, limit={}): + def __init__(self, source, options={}, attrs={}, new=False, limit={}): self.options = None self.attrs = {} self.source = source @@ -841,37 +892,37 @@ class JQueryTown(forms.TextInput): encoded_src = JSONEncoder().encode(source) elif isinstance(source, str): src = escape(source) - if not src.endswith('/'): + if not src.endswith("/"): src += "/" encoded_src = "'%s'" % src else: try: src = str(source) - if not src.endswith('/'): + if not src.endswith("/"): src += "/" encoded_src = "'%s'" % src except: - raise ValueError('source type is not valid') + raise ValueError("source type is not valid") return encoded_src def render(self, name, value, attrs=None, renderer=None): attrs_hidden = self.build_attrs(attrs, {"name": name}) attrs_select = self.build_attrs(attrs) - attrs_select['placeholder'] = _("Search...") - selected = '' - selected_state = '' - selected_department = '' + attrs_select["placeholder"] = _("Search...") + selected = "" + selected_state = "" + selected_department = "" if value: hiddens = [] selects = [] if type(value) not in (list, tuple): values = str(escape(smart_text(value))) - values = values.replace('[', '').replace(']', '') - values = values.split(',') + values = values.replace("[", "").replace("]", "") + values = values.split(",") else: values = [] for v in value: - values += v.split(',') + values += v.split(",") for v in values: if not v: continue @@ -889,31 +940,35 @@ class JQueryTown(forms.TextInput): selects.pop() hiddens.pop() if hiddens and selects: - attrs_hidden['value'] = hiddens[0] - attrs_select['value'] = selects[0] - if 'id' not in self.attrs: - attrs_hidden['id'] = 'id_%s' % name - attrs_select['id'] = 'id_select_%s' % name - if 'class' not in attrs_select: - attrs_select['class'] = 'autocomplete' + attrs_hidden["value"] = hiddens[0] + attrs_select["value"] = selects[0] + if "id" not in self.attrs: + attrs_hidden["id"] = "id_%s" % name + attrs_select["id"] = "id_select_%s" % name + if "class" not in attrs_select: + attrs_select["class"] = "autocomplete" source = self.encode_source(self.source) - dct = {'source': mark_safe(source), - 'selected': selected, - 'safe_field_id': slugify(name).replace('-', '_'), - 'field_id': name} + dct = { + "source": mark_safe(source), + "selected": selected, + "safe_field_id": slugify(name).replace("-", "_"), + "field_id": name, + } if self.options: - dct['options'] = mark_safe('%s' % self.options) - - dct.update({'attrs_select': mark_safe(flatatt(attrs_select)), - 'attrs_hidden': mark_safe(flatatt(attrs_hidden)), - 'name': name, - 'states': models.State.objects.all().order_by('label'), - 'selected_department': selected_department, - 'selected_state': selected_state} - ) - html = loader.get_template('blocks/JQueryAdvancedTown.html')\ - .render(dct) + dct["options"] = mark_safe("%s" % self.options) + + dct.update( + { + "attrs_select": mark_safe(flatatt(attrs_select)), + "attrs_hidden": mark_safe(flatatt(attrs_hidden)), + "name": name, + "states": models.State.objects.all().order_by("label"), + "selected_department": selected_department, + "selected_state": selected_state, + } + ) + html = loader.get_template("blocks/JQueryAdvancedTown.html").render(dct) return html @@ -925,10 +980,18 @@ class JQueryPersonOrganization(forms.TextInput): * create new person and new organization """ - def __init__(self, source, edit_source, model, options={}, - attrs={}, new=False, limit={}, - html_template='blocks/PersonOrganization.html', - js_template='blocks/JQueryPersonOrganization.js'): + def __init__( + self, + source, + edit_source, + model, + options={}, + attrs={}, + new=False, + limit={}, + html_template="blocks/PersonOrganization.html", + js_template="blocks/JQueryPersonOrganization.js", + ): self.options = None self.attrs = {} self.model = model @@ -946,45 +1009,46 @@ class JQueryPersonOrganization(forms.TextInput): def encode_source(cls, source): if isinstance(source, list): encoded_src = JSONEncoder().encode(source) - elif isinstance(source, str) \ - or isinstance(source, str): + elif isinstance(source, str) or isinstance(source, str): encoded_src = "'%s'" % escape(source) else: try: encoded_src = "'" + str(source) + "'" except: - raise ValueError('source type is not valid') + raise ValueError("source type is not valid") return encoded_src - def render_js(self, field_id, selected=''): + def render_js(self, field_id, selected=""): source = self.encode_source(self.source) edit_source = self.encode_source(self.edit_source) - dct = {'source': mark_safe(source), - 'edit_source': mark_safe(edit_source), - 'selected': selected, - 'safe_field_id': slugify(field_id).replace('-', '_'), - 'field_id': field_id} + dct = { + "source": mark_safe(source), + "edit_source": mark_safe(edit_source), + "selected": selected, + "safe_field_id": slugify(field_id).replace("-", "_"), + "field_id": field_id, + } if self.options: - dct['options'] = mark_safe('%s' % self.options) + dct["options"] = mark_safe("%s" % self.options) js = loader.get_template(self.js_template).render(dct) return js def render(self, name, value, attrs=None, renderer=None): - attrs_hidden = self.build_attrs(attrs, {'name': name}) + attrs_hidden = self.build_attrs(attrs, {"name": name}) attrs_select = self.build_attrs(attrs) - attrs_select['placeholder'] = _("Search...") - selected = '' + attrs_select["placeholder"] = _("Search...") + selected = "" if value: hiddens = [] selects = [] if type(value) not in (list, tuple): values = str(escape(smart_text(value))) - values = values.replace('[', '').replace(']', '') - values = values.split(',') + values = values.replace("[", "").replace("]", "") + values = values.split(",") else: values = [] for v in value: - values += v.split(',') + values += v.split(",") for v in values: if not v: continue @@ -999,29 +1063,44 @@ class JQueryPersonOrganization(forms.TextInput): selects.pop() hiddens.pop() if hiddens and selects: - attrs_hidden['value'] = hiddens[0] - attrs_select['value'] = selects[0] - if 'id' not in self.attrs: - attrs_hidden['id'] = 'id_%s' % name - attrs_select['id'] = 'id_select_%s' % name - if 'class' not in attrs_select: - attrs_select['class'] = 'autocomplete' - new = '' - dct = {'attrs_select': mark_safe(flatatt(attrs_select)), - 'attrs_hidden': mark_safe(flatatt(attrs_hidden)), - 'name': name, - 'js': self.render_js(name, selected), - 'new': mark_safe(new)} + attrs_hidden["value"] = hiddens[0] + attrs_select["value"] = selects[0] + if "id" not in self.attrs: + attrs_hidden["id"] = "id_%s" % name + attrs_select["id"] = "id_select_%s" % name + if "class" not in attrs_select: + attrs_select["class"] = "autocomplete" + new = "" + dct = { + "attrs_select": mark_safe(flatatt(attrs_select)), + "attrs_hidden": mark_safe(flatatt(attrs_hidden)), + "name": name, + "js": self.render_js(name, selected), + "new": mark_safe(new), + } html = loader.get_template(self.html_template).render(dct) return html class DataTable(Select2Media, forms.RadioSelect): - def __init__(self, source, form, associated_model, attrs=None, - table_cols='TABLE_COLS', multiple=False, multiple_cols=None, - new=False, new_message="", source_full=None, - multiple_select=False, sortname="__default__", - col_prefix='', gallery=False, map=False): + def __init__( + self, + source, + form, + associated_model, + attrs=None, + table_cols="TABLE_COLS", + multiple=False, + multiple_cols=None, + new=False, + new_message="", + source_full=None, + multiple_select=False, + sortname="__default__", + col_prefix="", + gallery=False, + map=False, + ): """ DataTable widget init. @@ -1061,14 +1140,14 @@ class DataTable(Select2Media, forms.RadioSelect): self.user = None self.gallery = gallery self.map = map - if self.col_prefix and not self.col_prefix.endswith('__'): + if self.col_prefix and not self.col_prefix.endswith("__"): self.col_prefix += "__" def get_cols(self, python=False): jq_col_names, extra_cols = [], [] col_labels = {} - slug = getattr(self.associated_model, 'SLUG', None) - if hasattr(self.associated_model, 'COL_LABELS'): + slug = getattr(self.associated_model, "SLUG", None) + if hasattr(self.associated_model, "COL_LABELS"): col_labels = self.associated_model.COL_LABELS if slug in settings.TABLE_COLS: col_labels.update(settings.TABLE_COLS[slug]) @@ -1088,24 +1167,24 @@ class DataTable(Select2Media, forms.RadioSelect): col_names = [col_names] for col_name in col_names: field = self.associated_model - keys = col_name.split('__') - if '.' in col_name: - keys = col_name.split('.') + keys = col_name.split("__") + if "." in col_name: + keys = col_name.split(".") for key in keys: - if hasattr(field, 'rel') and field.rel: + if hasattr(field, "rel") and field.rel: field = field.rel.to try: field = field._meta.get_field(key) field_verbose_name = field.verbose_name except (fields.FieldDoesNotExist, AttributeError): - if hasattr(field, key + '_lbl'): - field_verbose_name = getattr(field, key + '_lbl') + if hasattr(field, key + "_lbl"): + field_verbose_name = getattr(field, key + "_lbl") else: continue if field_name: field_name += "__" if col_name.startswith(self.col_prefix): - field_name += col_name[len(self.col_prefix):] + field_name += col_name[len(self.col_prefix) :] else: field_name += col_name field_verbose_names.append(str(field_verbose_name)) @@ -1119,27 +1198,27 @@ class DataTable(Select2Media, forms.RadioSelect): elif col_names and col_names[0] in col_labels: jq_col_names.append(str(col_labels[col_names[0]])) else: - jq_col_names.append(settings.JOINT.join( - [f for f in field_verbose_names if f])) + jq_col_names.append( + settings.JOINT.join([f for f in field_verbose_names if f]) + ) extra_cols.append(field_name) return jq_col_names, extra_cols def render(self, name, value, attrs=None, renderer=None): # t = loader.get_template('blocks/form_flex_snippet.html') - t = loader.get_template('blocks/bs_form_snippet.html') + t = loader.get_template("blocks/bs_form_snippet.html") if self.user: form = self.form(user=self.user) - if self.user.ishtaruser and \ - self.user.ishtaruser.show_field_number(): + if self.user.ishtaruser and self.user.ishtaruser.show_field_number(): form.show_field_number = True else: form = self.form() - rendered = t.render({'form': form, 'search': True}) + rendered = t.render({"form": form, "search": True}) dct = {} if self.new: model_name = self.associated_model._meta.object_name.lower() - dct['url_new'] = reverse('new-' + model_name, args=['0']) - dct['new_message'] = self.new_message + dct["url_new"] = reverse("new-" + model_name, args=["0"]) + dct["new_message"] = self.new_message col_names, extra_cols = self.get_cols() @@ -1148,53 +1227,58 @@ class DataTable(Select2Media, forms.RadioSelect): col_idx.append('"%s"' % k) col_idx = col_idx and ", ".join(col_idx) or "" - dct['encoding'] = settings.ENCODING or 'utf-8' + dct["encoding"] = settings.ENCODING or "utf-8" try: - dct['source'] = str(self.source) + dct["source"] = str(self.source) except NoReverseMatch: - logger.warning('Cannot resolve source for {} widget'.format( - self.form)) + logger.warning("Cannot resolve source for {} widget".format(self.form)) # full CSV export currently disabled - #if str(self.source_full) and str(self.source_full) != 'None': + # if str(self.source_full) and str(self.source_full) != 'None': # dct['source_full'] = str(self.source_full) - dct['extra_sources'] = [] - dct['quick_actions'] = [] + dct["extra_sources"] = [] + dct["quick_actions"] = [] if self.associated_model: - dct['current_model'] = self.associated_model + dct["current_model"] = self.associated_model model_name = "{}.{}".format( - self.associated_model.__module__, - self.associated_model.__name__) + self.associated_model.__module__, self.associated_model.__name__ + ) for imp in models.ImporterType.objects.filter( - slug__isnull=False, associated_models__klass=model_name, - is_template=True).all(): - dct['extra_sources'].append(( - imp.slug, imp.name, - reverse('get-by-importer', args=[imp.slug]))) + slug__isnull=False, + associated_models__klass=model_name, + is_template=True, + ).all(): + dct["extra_sources"].append( + (imp.slug, imp.name, reverse("get-by-importer", args=[imp.slug])) + ) if hasattr(self.associated_model, "QUICK_ACTIONS"): - dct['quick_actions'] = \ - self.associated_model.get_quick_actions(user=self.user) + dct["quick_actions"] = self.associated_model.get_quick_actions( + user=self.user + ) source = str(self.source) - dct.update({'name': name, - 'col_names': col_names, - 'extra_cols': extra_cols, - 'source': source, - 'col_idx': col_idx, - 'no_result': str(_("No results")), - 'loading': str(_("Loading...")), - 'remove': str(_("Remove")), - 'sname': name.replace('-', ''), - 'gallery': self.gallery, - 'use_map': self.map() if callable(self.map) else self.map, - 'multiple': self.multiple, - 'multiple_select': self.multiple_select, - 'multi_cols': ",".join(('"%d"' % col - for col in self.multiple_cols))}) - t = loader.get_template('blocks/DataTables.html') + dct.update( + { + "name": name, + "col_names": col_names, + "extra_cols": extra_cols, + "source": source, + "col_idx": col_idx, + "no_result": str(_("No results")), + "loading": str(_("Loading...")), + "remove": str(_("Remove")), + "sname": name.replace("-", ""), + "gallery": self.gallery, + "use_map": self.map() if callable(self.map) else self.map, + "multiple": self.multiple, + "multiple_select": self.multiple_select, + "multi_cols": ",".join(('"%d"' % col for col in self.multiple_cols)), + } + ) + t = loader.get_template("blocks/DataTables.html") rendered += t.render(dct) return mark_safe(rendered) class RangeInput(NumberInput): - input_type = 'range' + input_type = "range" diff --git a/ishtar_common/wizards.py b/ishtar_common/wizards.py index e72ed2cb1..7b636538d 100644 --- a/ishtar_common/wizards.py +++ b/ishtar_common/wizards.py @@ -20,12 +20,17 @@ import datetime import logging import os + # from functools import wraps from django.conf import settings from django.contrib import messages -from formtools.wizard.views import NamedUrlWizardView, normalize_name, \ - get_storage, StepsHelper +from formtools.wizard.views import ( + NamedUrlWizardView, + normalize_name, + get_storage, + StepsHelper, +) from django.contrib.sites.models import Site from django.core.exceptions import ObjectDoesNotExist @@ -45,8 +50,7 @@ from django.utils.safestring import mark_safe from ishtar_common import models from ishtar_common.forms import CustomForm, reverse_lazy -from ishtar_common.utils import get_all_field_names, MultiValueDict, \ - put_session_message +from ishtar_common.utils import get_all_field_names, MultiValueDict, put_session_message logger = logging.getLogger(__name__) @@ -72,20 +76,23 @@ def _check_right(step, condition=True): def filter_no_fields_form(form, other_check=None): def func(self): - if not hasattr(self.request.user, 'ishtaruser'): + if not hasattr(self.request.user, "ishtaruser"): return False if issubclass(form, CustomForm): enabled, excluded, json_fields = form.check_custom_form( - self.request.user.ishtaruser) - if not hasattr(self, 'json_fields'): + self.request.user.ishtaruser + ) + if not hasattr(self, "json_fields"): self.json_fields = {} self.json_fields[form.form_slug] = [ - key for order, key, field in json_fields] + key for order, key, field in json_fields + ] if not enabled: return False if other_check: return other_check(self) return True + return func @@ -95,20 +102,24 @@ EXTRA_FORM_MODALS = ["container", "warehouse", "person", "organization"] class IshtarWizard(NamedUrlWizardView): def get_form_kwargs(self, step=None): kwargs = super(IshtarWizard, self).get_form_kwargs(step) - if hasattr(self.form_list[step], 'need_user_for_initialization') and \ - self.form_list[step].need_user_for_initialization: - kwargs['user'] = self.request.user + if ( + hasattr(self.form_list[step], "need_user_for_initialization") + and self.form_list[step].need_user_for_initialization + ): + kwargs["user"] = self.request.user return kwargs def get_context_data(self, form, **kwargs): context = super(IshtarWizard, self).get_context_data(form, **kwargs) - context["extra_form_modals"] = form.extra_form_modals \ - if hasattr(form, "extra_form_modals") else EXTRA_FORM_MODALS + context["extra_form_modals"] = ( + form.extra_form_modals + if hasattr(form, "extra_form_modals") + else EXTRA_FORM_MODALS + ) - open_item_id = self.request.GET.get('open_item', None) - if open_item_id and self.model and \ - getattr(self.model, "SHOW_URL", None): + open_item_id = self.request.GET.get("open_item", None) + if open_item_id and self.model and getattr(self.model, "SHOW_URL", None): url = reverse(self.model.SHOW_URL, args=[open_item_id]) if not url.endswith("/"): url += "/" @@ -119,24 +130,22 @@ class IshtarWizard(NamedUrlWizardView): class Wizard(IshtarWizard): model = None - label = '' + label = "" translated_keys = [] modification = None # True when the wizard modify an item - storage_name = 'formtools.wizard.storage.session.SessionStorage' - wizard_done_template = 'ishtar/wizard/wizard_done.html' - wizard_done_window = '' + storage_name = "formtools.wizard.storage.session.SessionStorage" + wizard_done_template = "ishtar/wizard/wizard_done.html" + wizard_done_window = "" redirect_url = None open_created_in_redirect = True - wizard_confirm = 'ishtar/wizard/confirm_wizard.html' + wizard_confirm = "ishtar/wizard/confirm_wizard.html" wizard_templates = {} filter_owns = {} - current_obj_slug = '' - current_object_key = 'pk' + current_obj_slug = "" + current_object_key = "pk" ignore_init_steps = [] - file_storage = FileSystemStorage( - location=os.path.join(settings.MEDIA_ROOT, 'tmp') - ) - main_item_select_keys = ('selec-',) + file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, "tmp")) + main_item_select_keys = ("selec-",) formset_pop_deleted = True alt_is_own_method = None # alternate method name for "is_own" check @@ -160,13 +169,14 @@ class Wizard(IshtarWizard): def get_initkwargs(cls, *args, **kwargs): kwargs = super(Wizard, cls).get_initkwargs(*args, **kwargs) # remove - for form_key in kwargs['form_list']: - form = kwargs['form_list'][form_key] + for form_key in kwargs["form_list"]: + form = kwargs["form_list"][form_key] other_check = None - if form_key in kwargs['condition_dict']: - other_check = kwargs['condition_dict'][form_key] - kwargs['condition_dict'][form_key] = filter_no_fields_form( - form, other_check) + if form_key in kwargs["condition_dict"]: + other_check = kwargs["condition_dict"][form_key] + kwargs["condition_dict"][form_key] = filter_no_fields_form( + form, other_check + ) return kwargs def check_own_permissions(self, request, step=None, *args, **kwargs): @@ -175,34 +185,37 @@ class Wizard(IshtarWizard): self.session = request.session self.prefix = self.get_prefix(request, *args, **kwargs) self.storage = get_storage( - self.storage_name, self.prefix, request, - getattr(self, 'file_storage', None)) + self.storage_name, self.prefix, request, getattr(self, "file_storage", None) + ) self.steps = StepsHelper(self) current_object = self.get_current_object() - ishtaruser = request.user.ishtaruser \ - if hasattr(request.user, 'ishtaruser') else None + ishtaruser = ( + request.user.ishtaruser if hasattr(request.user, "ishtaruser") else None + ) # not the first step and current object is not owned if self.steps and self.steps.first != step and current_object: is_own = current_object.is_own( - ishtaruser, alt_query_own=self.alt_is_own_method) + ishtaruser, alt_query_own=self.alt_is_own_method + ) if not is_own: messages.add_message( - request, messages.WARNING, - _("Permission error: you cannot do this action.") + request, + messages.WARNING, + _("Permission error: you cannot do this action."), ) self.session_reset(request, self.url_name) return return True def dispatch(self, request, *args, **kwargs): - self.current_right = kwargs.get('current_right', None) - step = kwargs.get('step', None) + self.current_right = kwargs.get("current_right", None) + step = kwargs.get("step", None) # check that the current object is really owned by the current user - if step and self.current_right and '_own_' in self.current_right: + if step and self.current_right and "_own_" in self.current_right: if not self.check_own_permissions(request, *args, **kwargs): - return HttpResponseRedirect('/') + return HttpResponseRedirect("/") # extra filter on forms self.filter_owns_items = True else: @@ -211,22 +224,22 @@ class Wizard(IshtarWizard): return super(Wizard, self).dispatch(request, *args, **kwargs) def get_prefix(self, request, *args, **kwargs): - """As the class name can interfere when reused prefix with the url_name - """ - return self.url_name + super(Wizard, self).get_prefix(request, *args, - **kwargs) + """As the class name can interfere when reused prefix with the url_name""" + return self.url_name + super(Wizard, self).get_prefix(request, *args, **kwargs) def get_wizard_name(self): """As the class name can interfere when reused, use the url_name""" return self.url_name def get_template_names(self): - templates = ['ishtar/wizard/default_wizard.html'] + templates = ["ishtar/wizard/default_wizard.html"] current_step = self.steps.current - wizard_templates = dict([ - (key % {'url_name': self.url_name}, self.wizard_templates[key]) - for key in self.wizard_templates - ]) + wizard_templates = dict( + [ + (key % {"url_name": self.url_name}, self.wizard_templates[key]) + for key in self.wizard_templates + ] + ) if current_step in wizard_templates: templates = [wizard_templates[current_step]] + templates elif current_step == self.steps.last: @@ -234,25 +247,27 @@ class Wizard(IshtarWizard): return templates def get_ignore_init_steps(self): - return ['{}-{}'.format(step, self.url_name) for step in - self.ignore_init_steps] + return ["{}-{}".format(step, self.url_name) for step in self.ignore_init_steps] def get_context_data(self, form, **kwargs): """ Add previous, next and current steps to manage the wizard path """ context = super(Wizard, self).get_context_data(form) - self.request.session['CURRENT_ACTION'] = self.get_wizard_name() + self.request.session["CURRENT_ACTION"] = self.get_wizard_name() step = self.steps.first current_step = self.steps.current - dct = {'current_step_label': self.form_list[current_step].form_label, - 'wizard_label': self.get_label(), - 'current_object': self.get_current_object(), - 'is_search': bool( - [k for k in self.main_item_select_keys - if current_step.startswith(k)]) if current_step else False - } + dct = { + "current_step_label": self.form_list[current_step].form_label, + "wizard_label": self.get_label(), + "current_object": self.get_current_object(), + "is_search": bool( + [k for k in self.main_item_select_keys if current_step.startswith(k)] + ) + if current_step + else False, + } context.update(dct) if step == current_step: @@ -260,9 +275,9 @@ class Wizard(IshtarWizard): previous_steps, next_steps, previous_step_counter = [], [], 0 while step: - if step == current_step \ - or (previous_steps and - previous_steps[-1] == self.form_list[step]): + if step == current_step or ( + previous_steps and previous_steps[-1] == self.form_list[step] + ): break previous_steps.append(self.form_list[step].form_label) previous_step_counter += 1 @@ -270,8 +285,12 @@ class Wizard(IshtarWizard): break step = self.steps.all[previous_step_counter] - context.update({'previous_steps': previous_steps, - 'previous_step_counter': previous_step_counter}) + context.update( + { + "previous_steps": previous_steps, + "previous_step_counter": previous_step_counter, + } + ) storage = self.storage # if modification: show the next steps # if self.modification or True: @@ -292,10 +311,9 @@ class Wizard(IshtarWizard): if not isinstance(values, list): # simple form for key in values: - form_key = next_step + '-' + key + form_key = next_step + "-" + key if isinstance(values, MultiValueDict): - prefixed_values.setlist(form_key, - values.getlist(key)) + prefixed_values.setlist(form_key, values.getlist(key)) else: prefixed_values[form_key] = values[key] else: @@ -305,16 +323,17 @@ class Wizard(IshtarWizard): for key in v: form_key = next_step + prefix + key if isinstance(v, MultiValueDict): - prefixed_values.setlist(form_key, - v.getlist(key)) + prefixed_values.setlist(form_key, v.getlist(key)) else: prefixed_values[form_key] = v[key] - if not prefixed_values and \ - next_step not in self.get_ignore_init_steps(): + if ( + not prefixed_values + and next_step not in self.get_ignore_init_steps() + ): # simulate a non empty data for form that might be # valid when empty - prefixed_values['__non_empty_data'] = '' + prefixed_values["__non_empty_data"] = "" self.prepare_serialization(prefixed_values) storage.set_step_data(next_step, prefixed_values) @@ -329,10 +348,13 @@ class Wizard(IshtarWizard): if current_step_passed: initialise_data = False # formsets are considered not required - if hasattr(form_obj, 'fields'): + if hasattr(form_obj, "fields"): # display next step until a required field is met - if [field_key for field_key in form_obj.fields - if form_obj.fields[field_key].required]: + if [ + field_key + for field_key in form_obj.fields + if form_obj.fields[field_key].required + ]: no_next = True elif next_step not in self.get_ignore_init_steps(): initialise_data = True @@ -342,15 +364,16 @@ class Wizard(IshtarWizard): # simulate a non empty data for form that might be # valid when empty prefixed_values = MultiValueDict() - prefixed_values['__non_empty_data'] = '' + prefixed_values["__non_empty_data"] = "" storage.set_step_data(next_step, prefixed_values) next_step = self.get_next_step(next_step) if no_next: last_step_is_available = False break - context.update({'next_steps': next_steps, - 'last_step_is_available': last_step_is_available}) + context.update( + {"next_steps": next_steps, "last_step_is_available": last_step_is_available} + ) # not last step: validation if current_step != self.steps.last: return context @@ -360,56 +383,69 @@ class Wizard(IshtarWizard): form_obj = self.get_form( step=form_key, data=self.storage.get_step_data(form_key), - files=self.storage.get_step_files(form_key)) + files=self.storage.get_step_files(form_key), + ) form_obj.is_valid() final_form_list.append(form_obj) last_form = final_form_list[-1] - context.update({'datas': self.get_formated_datas(final_form_list)}) - if hasattr(last_form, 'confirm_msg'): - context.update({'confirm_msg': last_form.confirm_msg}) - if hasattr(last_form, 'confirm_end_msg'): - context.update({'confirm_end_msg': last_form.confirm_end_msg}) + context.update({"datas": self.get_formated_datas(final_form_list)}) + if hasattr(last_form, "confirm_msg"): + context.update({"confirm_msg": last_form.confirm_msg}) + if hasattr(last_form, "confirm_end_msg"): + context.update({"confirm_end_msg": last_form.confirm_end_msg}) return context def get_formated_datas(self, forms): """Get the data to present in the last page""" datas = [] for form in forms: - is_formset = hasattr(form, 'forms') + is_formset = hasattr(form, "forms") base_form = is_formset and form.forms[0] or form - associated_models = hasattr(base_form, 'associated_models') and \ - base_form.associated_models or {} - if not hasattr(form, 'cleaned_data') and hasattr(form, 'forms'): - cleaned_datas = [frm.cleaned_data for frm in form.forms - if frm.is_valid()] + associated_models = ( + hasattr(base_form, "associated_models") + and base_form.associated_models + or {} + ) + if not hasattr(form, "cleaned_data") and hasattr(form, "forms"): + cleaned_datas = [ + frm.cleaned_data for frm in form.forms if frm.is_valid() + ] if not cleaned_datas: continue - elif not hasattr(form, 'cleaned_data'): + elif not hasattr(form, "cleaned_data"): continue else: - cleaned_datas = type(form.cleaned_data) == list and \ - form.cleaned_data or [form.cleaned_data] - if hasattr(base_form, 'get_formated_datas'): - datas.append((form.form_label, - base_form.get_formated_datas(cleaned_datas))) + cleaned_datas = ( + type(form.cleaned_data) == list + and form.cleaned_data + or [form.cleaned_data] + ) + if hasattr(base_form, "get_formated_datas"): + datas.append( + (form.form_label, base_form.get_formated_datas(cleaned_datas)) + ) continue form_datas = [] for cleaned_data in cleaned_datas: if not cleaned_data: continue current_form_data = [] - items = hasattr(base_form, 'fields') and \ - base_form.fields.keys() or cleaned_data.keys() + items = ( + hasattr(base_form, "fields") + and base_form.fields.keys() + or cleaned_data.keys() + ) is_deleted = False for key in items: lbl = None - if key.startswith('hidden_'): + if key.startswith("hidden_"): continue - if hasattr(base_form, 'fields') \ - and key in base_form.fields: + if hasattr(base_form, "fields") and key in base_form.fields: lbl = base_form.fields[key].label - if hasattr(base_form, 'associated_labels') \ - and key in base_form.associated_labels: + if ( + hasattr(base_form, "associated_labels") + and key in base_form.associated_labels + ): lbl = base_form.associated_labels[key] if key not in cleaned_data: # no value continue @@ -417,8 +453,7 @@ class Wizard(IshtarWizard): if key == "DELETE" and value: is_deleted = True # no display when no label or no value - if not lbl or not lbl.strip() or value is None \ - or value == '': + if not lbl or not lbl.strip() or value is None or value == "": continue if key in self.translated_keys: value = _(value) @@ -432,20 +467,19 @@ class Wizard(IshtarWizard): if type(value) in (tuple, list): values = value elif "," in str(value): - values = str( - value).strip('[').strip(']').split(",") + values = str(value).strip("[").strip("]").split(",") else: values = [value] rendered_values = [] for val in values: item = associated_models[key].objects.get(pk=val) - if hasattr(item, 'short_label'): + if hasattr(item, "short_label"): value = str(item.short_label) else: value = str(item) rendered_values.append(value) value = " ; ".join(rendered_values) - current_form_data.append((lbl, value, '')) + current_form_data.append((lbl, value, "")) if is_formset: # regroup each line @@ -454,17 +488,19 @@ class Wizard(IshtarWizard): displayed_value = "" if lbl.strip(): displayed_value = "<strong>{}{}</strong> ".format( - lbl, _(":")) + lbl, _(":") + ) displayed_value += str(value) displayed_values.append(displayed_value) value = " ; ".join(displayed_values) if is_deleted: - value = "<span class='text-danger'>{}</span>".format( - value) - deleted = "<span class='text-danger'>{}</span>".format( - _("Deleted")) if is_deleted else "" - form_datas.append((mark_safe(deleted), mark_safe(value), - '')) + value = "<span class='text-danger'>{}</span>".format(value) + deleted = ( + "<span class='text-danger'>{}</span>".format(_("Deleted")) + if is_deleted + else "" + ) + form_datas.append((mark_safe(deleted), mark_safe(value), "")) else: form_datas += current_form_data @@ -474,7 +510,7 @@ class Wizard(IshtarWizard): return datas def get_extra_model(self, dct, m2m, form_list): - dct['history_modifier'] = self.request.user + dct["history_modifier"] = self.request.user return dct def done(self, form_list, return_object=False, **kwargs): @@ -484,30 +520,32 @@ class Wizard(IshtarWizard): for form in form_list: if not form.is_valid(): return self.render(form) - if hasattr(form, 'readonly') and form.readonly: + if hasattr(form, "readonly") and form.readonly: continue - base_form = hasattr(form, 'forms') and form.forms[0] or form - associated_models = hasattr(base_form, 'associated_models') and \ - base_form.associated_models or {} - if hasattr(form, 'forms'): + base_form = hasattr(form, "forms") and form.forms[0] or form + associated_models = ( + hasattr(base_form, "associated_models") + and base_form.associated_models + or {} + ) + if hasattr(form, "forms"): multi = False if form.forms: frm = form.forms[0] - if hasattr(frm, 'base_model') and frm.base_model: + if hasattr(frm, "base_model") and frm.base_model: whole_associated_models.append(frm.base_model) - elif hasattr(frm, 'base_models') and frm.base_models: + elif hasattr(frm, "base_models") and frm.base_models: whole_associated_models += frm.base_models else: whole_associated_models += associated_models.keys() fields = frm.fields.copy() - if 'DELETE' in fields: - fields.pop('DELETE') + if "DELETE" in fields: + fields.pop("DELETE") multi = len(fields) > 1 if multi: - assert hasattr(frm, 'base_model') or \ - hasattr(frm, 'base_models'), \ - "Must define a base_model(s) for " + \ - str(frm.__class__) + assert hasattr(frm, "base_model") or hasattr( + frm, "base_models" + ), "Must define a base_model(s) for " + str(frm.__class__) for frm in form.forms: if not frm.is_valid(): continue @@ -515,11 +553,11 @@ class Wizard(IshtarWizard): if "DELETE" in frm.cleaned_data: if frm.cleaned_data["DELETE"]: continue - frm.cleaned_data.pop('DELETE') + frm.cleaned_data.pop("DELETE") for key in frm.cleaned_data: value = frm.cleaned_data[key] dct_key = key - value_is_empty = value is None or value in [''] + value_is_empty = value is None or value in [""] if value_is_empty: if multi: dct_key = "__empty-" + key @@ -532,21 +570,20 @@ class Wizard(IshtarWizard): for v in value ] else: - value = associated_models[key].objects.get( - pk=value) + value = associated_models[key].objects.get(pk=value) if multi: vals[dct_key] = value else: m2m.append((key, value)) if multi and vals: - if hasattr(frm, 'base_models'): + if hasattr(frm, "base_models"): for m in frm.base_models: m2m.append((frm.base_model, m)) else: m2m.append((frm.base_model, vals)) elif type(form.cleaned_data) == dict: for key in form.cleaned_data: - if key.startswith('hidden_'): + if key.startswith("hidden_"): continue value = form.cleaned_data[key] if key in associated_models: @@ -554,20 +591,21 @@ class Wizard(IshtarWizard): model = associated_models[key] if isinstance(value, str) and "," in value: value = value.split(",") - if isinstance(value, list) \ - or isinstance(value, tuple): - value = [model.objects.get(pk=val) - for val in value if val] + if isinstance(value, list) or isinstance(value, tuple): + value = [ + model.objects.get(pk=val) for val in value if val + ] if len(value) == 1: value = value[0] else: value = model.objects.get(pk=value) else: value = None - if (hasattr(form, 'base_model') and form.base_model and - form.base_model == key) or ( - hasattr(form, 'base_models') and - key in form.base_models): + if ( + hasattr(form, "base_model") + and form.base_model + and form.base_model == key + ) or (hasattr(form, "base_models") and key in form.base_models): whole_associated_models.append(key) if value: vals = value @@ -577,8 +615,9 @@ class Wizard(IshtarWizard): m2m.append((key, val)) else: dct[key] = value - return self.save_model(dct, m2m, whole_associated_models, form_list, - return_object) + return self.save_model( + dct, m2m, whole_associated_models, form_list, return_object + ) def get_saved_model(self): "Permit a distinguo when saved model is not the base selected model" @@ -588,22 +627,21 @@ class Wizard(IshtarWizard): "Permit a distinguo when saved model is not the base selected model" return self.get_current_object() - def save_model(self, dct, m2m, whole_associated_models, form_list, - return_object): + def save_model(self, dct, m2m, whole_associated_models, form_list, return_object): dct = self.get_extra_model(dct, m2m, form_list) obj = self.get_current_saved_object() data = {} - if obj and hasattr(obj, 'data'): + if obj and hasattr(obj, "data"): data = obj.data # manage dependant items and json fields other_objs = {} for k in list(dct.keys()): - if '__' not in k: + if "__" not in k: continue # manage json field - if k.startswith('data__'): - data_keys = k[len('data__'):].split('__') + if k.startswith("data__"): + data_keys = k[len("data__") :].split("__") # tree current_data = data for data_key in data_keys[:-1]: @@ -612,53 +650,50 @@ class Wizard(IshtarWizard): current_data = current_data[data_key] value = dct.pop(k) if isinstance(value, datetime.datetime): - value = value.strftime('%Y-%m-%dT%H:%M:%S') + value = value.strftime("%Y-%m-%dT%H:%M:%S") elif isinstance(value, datetime.date): - value = value.strftime('%Y-%m-%d') + value = value.strftime("%Y-%m-%d") elif value is None: - value = '' + value = "" current_data[data_keys[-1]] = value continue - vals = k.split('__') - assert len(vals) == 2, \ - "Only one level of dependant item is managed" + vals = k.split("__") + assert len(vals) == 2, "Only one level of dependant item is managed" dependant_item, key = vals if dependant_item not in other_objs: other_objs[dependant_item] = {} other_objs[dependant_item][key] = dct.pop(k) if obj: for k in list(dct.keys()): - if k.startswith('pk'): + if k.startswith("pk"): continue if k not in get_all_field_names(obj.__class__): continue # False set to None for images and files - if not k.endswith('_id') and ( - isinstance(obj.__class__._meta.get_field(k), FileField) or - isinstance(obj.__class__._meta.get_field(k), ImageFile)): + if not k.endswith("_id") and ( + isinstance(obj.__class__._meta.get_field(k), FileField) + or isinstance(obj.__class__._meta.get_field(k), ImageFile) + ): if not dct[k]: dct[k] = None - if not k.endswith('_id') and ( - isinstance(obj.__class__._meta.get_field(k), - ManyToManyField)): + if not k.endswith("_id") and ( + isinstance(obj.__class__._meta.get_field(k), ManyToManyField) + ): if not dct[k]: dct[k] = [] elif type(dct[k]) not in (list, tuple): dct[k] = [dct[k]] setattr(obj, k, dct[k]) - if hasattr(obj, 'data'): + if hasattr(obj, "data"): obj.data = data - if hasattr(obj, 'pre_save'): + if hasattr(obj, "pre_save"): obj.pre_save() try: obj.full_clean() except ValidationError as e: logger.warning(str(e)) - put_session_message( - self.request.session.session_key, - str(e), 'error' - ) + put_session_message(self.request.session.session_key, str(e), "error") return self.render(list(form_list)[-1]) for dependant_item in other_objs: c_item = getattr(obj, dependant_item) @@ -679,7 +714,7 @@ class Wizard(IshtarWizard): m = getattr(self.model, dependant_item) if callable(m): m = m() - if hasattr(m, 'related'): + if hasattr(m, "related"): c_item = m.related.model(**other_objs[dependant_item]) setattr(obj, dependant_item, c_item) obj.save() @@ -687,19 +722,19 @@ class Wizard(IshtarWizard): else: adds = {} # manage attributes relations - if hasattr(self.model, 'ATTRS_EQUIV'): + if hasattr(self.model, "ATTRS_EQUIV"): for k in list(other_objs.keys()): if k in self.model.ATTRS_EQUIV: new_k = self.model.ATTRS_EQUIV[k] if new_k in other_objs: - other_objs[new_k].update( - other_objs[k]) + other_objs[new_k].update(other_objs[k]) else: - other_objs[new_k] = \ - other_objs[k].copy() + other_objs[new_k] = other_objs[k].copy() for dependant_item in other_objs: - if hasattr(self.model, 'ATTRS_EQUIV') and \ - dependant_item in self.model.ATTRS_EQUIV: + if ( + hasattr(self.model, "ATTRS_EQUIV") + and dependant_item in self.model.ATTRS_EQUIV + ): continue m = getattr(self.model, dependant_item) if callable(m): @@ -707,41 +742,41 @@ class Wizard(IshtarWizard): model = m.field.rel.to c_dct = other_objs[dependant_item].copy() if issubclass(model, models.BaseHistorizedItem): - c_dct['history_modifier'] = self.request.user + c_dct["history_modifier"] = self.request.user c_item = model(**c_dct) c_item.save() - if hasattr(m, 'through'): + if hasattr(m, "through"): adds[dependant_item] = c_item - elif hasattr(m, 'field'): + elif hasattr(m, "field"): dct[dependant_item] = c_item - if 'pk' in dct: - dct.pop('pk') + if "pk" in dct: + dct.pop("pk") # remove non relevant fields all_field_names = get_all_field_names(self.get_saved_model()) for k in dct.copy(): - if not (k.endswith('_id') and k[:-3] in all_field_names) \ - and k not in all_field_names and \ - (not hasattr(self.get_saved_model(), - 'EXTRA_SAVED_KEYS') or - k not in self.get_saved_model().EXTRA_SAVED_KEYS): + if ( + not (k.endswith("_id") and k[:-3] in all_field_names) + and k not in all_field_names + and ( + not hasattr(self.get_saved_model(), "EXTRA_SAVED_KEYS") + or k not in self.get_saved_model().EXTRA_SAVED_KEYS + ) + ): dct.pop(k) saved_args = self.saved_args.copy() for k in saved_args: if k in dct: saved_args[k] = dct.pop(k) obj = self.get_saved_model()(**dct) - if hasattr(obj, 'pre_save'): + if hasattr(obj, "pre_save"): obj.pre_save() try: obj.full_clean() except ValidationError as e: logger.warning(str(e)) - put_session_message( - self.request.session.session_key, - str(e), 'error' - ) + put_session_message(self.request.session.session_key, str(e), "error") return self.render(list(form_list)[-1]) - if hasattr(obj, 'data'): + if hasattr(obj, "data"): obj.data = data obj.save(**saved_args) for k in adds: @@ -755,18 +790,19 @@ class Wizard(IshtarWizard): # TODO! perf - to be really optimized old_m2ms = {} for model in whole_associated_models: - related_model = getattr(obj, model + 's') + related_model = getattr(obj, model + "s") # manage through - if hasattr(related_model, 'through') and related_model.through: + if hasattr(related_model, "through") and related_model.through: if hasattr(related_model.through, "RELATED_SET_NAME"): related_set_name = related_model.through.RELATED_SET_NAME else: related_set_name = str( - related_model.through.__name__ + '_set').lower() + related_model.through.__name__ + "_set" + ).lower() if hasattr(obj, related_set_name): related_model = getattr(obj, related_set_name) # clear real m2m - if hasattr(related_model, 'clear'): + if hasattr(related_model, "clear"): old_m2ms[model] = [] # stock items in order to not recreate them for old_item in related_model.all(): @@ -776,20 +812,24 @@ class Wizard(IshtarWizard): for r in related_model.all(): r.delete() for key, value in m2m: - related_model = getattr(obj, key + 's') + related_model = getattr(obj, key + "s") related_data = {} # used for intermediary models # an intermediary model is used - if hasattr(related_model, 'through') and \ - not related_model.through._meta.auto_created: + if ( + hasattr(related_model, "through") + and not related_model.through._meta.auto_created + ): for field in related_model.through._meta.get_fields(): # is used for the obj or target - if getattr(field, 'related_model', None) and \ - (field.related_model == related_model.model or - isinstance(obj, field.related_model)): + if getattr(field, "related_model", None) and ( + field.related_model == related_model.model + or isinstance(obj, field.related_model) + ): continue if field.name in getattr( - related_model.through, 'RELATED_ATTRS', []): + related_model.through, "RELATED_ATTRS", [] + ): continue related_data[field.name] = None @@ -807,19 +847,21 @@ class Wizard(IshtarWizard): if value not in m2m_items[key]: if type(value) == dict: model = related_model.model - if hasattr(related_model, 'through') and \ - related_model.through and \ - hasattr(related_model.through, 'RELATIVE_MODELS') \ - and self.get_saved_model() in \ - related_model.through.RELATIVE_MODELS: + if ( + hasattr(related_model, "through") + and related_model.through + and hasattr(related_model.through, "RELATIVE_MODELS") + and self.get_saved_model() + in related_model.through.RELATIVE_MODELS + ): # the form is dealing with the through parameter model = related_model.through # not m2m -> foreign key - if not hasattr(related_model, 'clear'): - assert hasattr(model, 'MAIN_ATTR'), \ - "Must define a MAIN_ATTR for " + \ - str(model.__class__) - value[getattr(model, 'MAIN_ATTR')] = obj + if not hasattr(related_model, "clear"): + assert hasattr( + model, "MAIN_ATTR" + ), "Must define a MAIN_ATTR for " + str(model.__class__) + value[getattr(model, "MAIN_ATTR")] = obj # check old links my_old_item = None @@ -831,13 +873,13 @@ class Wizard(IshtarWizard): val_empty = False if k.startswith("__empty-"): val_empty = True - k = k[len("__empty-"):] + k = k[len("__empty-") :] if not hasattr(old_item, k): continue old_val = getattr(old_item, k) - if (val_empty and (old_val is None - or old_val == "") - ) or old_val != new_val: + if ( + val_empty and (old_val is None or old_val == "") + ) or old_val != new_val: is_ok = False break if is_ok: @@ -847,14 +889,14 @@ class Wizard(IshtarWizard): value = my_old_item else: if issubclass(model, models.BaseHistorizedItem): - value['history_modifier'] = self.request.user + value["history_modifier"] = self.request.user get_or_create = False - if hasattr(model, 'RELATIVE_MODELS') and \ - self.get_saved_model() in \ - model.RELATIVE_MODELS: - value[model.RELATIVE_MODELS[ - self.get_saved_model()]] = obj + if ( + hasattr(model, "RELATIVE_MODELS") + and self.get_saved_model() in model.RELATIVE_MODELS + ): + value[model.RELATIVE_MODELS[self.get_saved_model()]] = obj get_or_create = True # check if there is no missing fields @@ -863,22 +905,25 @@ class Wizard(IshtarWizard): has_problematic_null = False for field in fields: - if (field.name not in value - or not value[field.name]) \ - and (hasattr(field, 'null') - and not field.null) \ - and (hasattr(field, 'blank') - and not field.blank) \ - and (hasattr(field, 'default') - and (not field.default - or field.default == NOT_PROVIDED)): - has_problematic_null = True - break + if ( + (field.name not in value or not value[field.name]) + and (hasattr(field, "null") and not field.null) + and (hasattr(field, "blank") and not field.blank) + and ( + hasattr(field, "default") + and ( + not field.default + or field.default == NOT_PROVIDED + ) + ) + ): + has_problematic_null = True + break if has_problematic_null: continue - if hasattr(model, 'data') and 'data' not in value: - value['data'] = {} + if hasattr(model, "data") and "data" not in value: + value["data"] = {} # remove intermediary model keys for k in list(related_data.keys()): @@ -891,16 +936,19 @@ class Wizard(IshtarWizard): if type(value[k]) in (list, tuple): m2m_values[k] = value.pop(k) - value = dict([(k, value[k]) for k in value - if not k.startswith("__empty-")]) + value = dict( + [ + (k, value[k]) + for k in value + if not k.startswith("__empty-") + ] + ) if get_or_create: - value, created = model.objects.get_or_create( - **value) + value, created = model.objects.get_or_create(**value) else: - if 'pk' in value and value['pk']: + if "pk" in value and value["pk"]: try: - instance = model.objects.get( - pk=value.pop('pk')) + instance = model.objects.get(pk=value.pop("pk")) except model.DoesNotExist: continue for k in value: @@ -913,33 +961,31 @@ class Wizard(IshtarWizard): setattr(value, k, m2m_values[k]) value.save() # force post_save # check that an item is not add multiple times (forged forms) - if value not in related_model.all() and \ - (not hasattr(related_model, 'through') or - not isinstance(value, related_model.through)): + if value not in related_model.all() and ( + not hasattr(related_model, "through") + or not isinstance(value, related_model.through) + ): # many to many and the value have been already managed # an intermediary model is used - if hasattr(related_model, 'through') and \ - not related_model.through._meta.auto_created: + if ( + hasattr(related_model, "through") + and not related_model.through._meta.auto_created + ): for field in related_model.through._meta.get_fields(): - if hasattr(field, 'related_model') \ - and field.related_model: + if hasattr(field, "related_model") and field.related_model: # assume that one foreign key is used for obj # table and value table - more complex schema # are not managed - if isinstance(value, - field.related_model): + if isinstance(value, field.related_model): related_data[field.name] = value - elif isinstance(obj, - field.related_model): + elif isinstance(obj, field.related_model): related_data[field.name] = obj # let default value if is none for k in list(related_data.keys()): if related_data[k] is None: related_data.pop(k) - related_model.through.objects.create( - **related_data - ) + related_model.through.objects.create(**related_data) else: related_model.add(value) # necessary to manage interaction between models like @@ -959,19 +1005,25 @@ class Wizard(IshtarWizard): item.skip_history_when_saving = True item.save() - if hasattr(obj, 'fix'): + if hasattr(obj, "fix"): # post save/m2m specific fix obj.fix() - ishtaruser = self.request.user.ishtaruser \ - if hasattr(self.request.user, 'ishtaruser') else None - if ishtaruser and ishtaruser.current_profile \ - and ishtaruser.current_profile.auto_pin: + ishtaruser = ( + self.request.user.ishtaruser + if hasattr(self.request.user, "ishtaruser") + else None + ) + if ( + ishtaruser + and ishtaruser.current_profile + and ishtaruser.current_profile.auto_pin + ): # make the new object a default if self.current_obj_slug: self.request.session[self.current_obj_slug] = str(obj.pk) self.request.session[self.get_object_name(obj)] = str(obj.pk) - dct = {'item': obj} + dct = {"item": obj} self.current_object = obj self.post_save() @@ -986,7 +1038,7 @@ class Wizard(IshtarWizard): # force evaluation of lazy urls wizard_done_window = str(self.wizard_done_window) if wizard_done_window: - dct['wizard_done_window'] = wizard_done_window + dct["wizard_done_window"] = wizard_done_window res = render(self.request, self.wizard_done_template, dct) return return_object and (obj, res) or res @@ -999,7 +1051,7 @@ class Wizard(IshtarWizard): """ not_to_delete, to_delete = set(), set() for key in keys: - items = key.split('-') + items = key.split("-") if len(items) < 2 or items[-2] in to_delete: continue idx = items[-2] @@ -1007,7 +1059,7 @@ class Wizard(IshtarWizard): int(idx) except: continue - if items[-1] == 'DELETE': + if items[-1] == "DELETE": to_delete.add(idx) if idx in not_to_delete: not_to_delete.remove(idx) @@ -1025,29 +1077,29 @@ class Wizard(IshtarWizard): form = self.get_form_list()[step] except KeyError: raise Http404() - if hasattr(form, 'management_form'): + if hasattr(form, "management_form"): # manage deletion - to_delete, not_to_delete = self.get_deleted( - list(data.keys())) + to_delete, not_to_delete = self.get_deleted(list(data.keys())) # raz deleted fields if self.formset_pop_deleted: for key in list(data.keys()): - items = key.split('-') + items = key.split("-") if len(items) < 2 or items[-2] not in to_delete: continue data.pop(key) if to_delete: # reorganize for idx, number in enumerate( - sorted(not_to_delete, key=lambda x: int(x))): + sorted(not_to_delete, key=lambda x: int(x)) + ): idx = str(idx) if idx == number: continue for key in list(data.keys()): - items = key.split('-') + items = key.split("-") if len(items) > 2 and number == items[-2]: items[-2] = str(idx) - k = '-'.join(items) + k = "-".join(items) data[k] = data.pop(key)[0] # get a form key frm = form.form @@ -1055,27 +1107,37 @@ class Wizard(IshtarWizard): frm = frm(self.get_form_kwargs(step)) total_field = 0 - if hasattr(frm, 'count_valid_fields'): + if hasattr(frm, "count_valid_fields"): total_field = frm.count_valid_fields(data) else: - required_fields = [ki for ki in frm.fields - if frm.fields[ki].required] + required_fields = [ + ki for ki in frm.fields if frm.fields[ki].required + ] base_key = None if required_fields: base_key = required_fields[-1] elif frm.fields.keys(): base_key = list(frm.fields.keys())[-1] if base_key: - total_field = len([key for key in data.keys() - if base_key in key.split('-') - and data[key]]) + total_field = len( + [ + key + for key in data.keys() + if base_key in key.split("-") and data[key] + ] + ) init = self.get_form_initial(step, data=data) - if init and not to_delete and ( - not hasattr(self, 'form_initialized') or - not self.form_initialized): + if ( + init + and not to_delete + and ( + not hasattr(self, "form_initialized") + or not self.form_initialized + ) + ): total_field = max((total_field, len(init))) - data[step + '-INITIAL_FORMS'] = str(total_field) - data[step + '-TOTAL_FORMS'] = str(total_field + 1) + data[step + "-INITIAL_FORMS"] = str(total_field) + data[step + "-TOTAL_FORMS"] = str(total_field + 1) # TODO:remove form_initialized? # update initialization # if request.POST and init and hasattr(self, @@ -1089,30 +1151,33 @@ class Wizard(IshtarWizard): form = super(Wizard, self).get_form(step, data, files) # add autofocus to first field frm = None - if hasattr(form, 'fields') and form.fields.keys(): + if hasattr(form, "fields") and form.fields.keys(): frm = form - elif hasattr(form, 'extra_form') and hasattr(form.extra_form, 'fields')\ - and form.extra_form.fields.keys(): + elif ( + hasattr(form, "extra_form") + and hasattr(form.extra_form, "fields") + and form.extra_form.fields.keys() + ): frm = form.extra_form - elif hasattr(form, 'forms') and form.forms \ - and form.forms[0].fields.keys(): + elif hasattr(form, "forms") and form.forms and form.forms[0].fields.keys(): frm = form.forms[0] if frm: # autofocus on first field first_field = frm.fields[list(frm.fields.keys())[0]] attrs = first_field.widget.attrs - attrs.update({'autofocus': "autofocus"}) + attrs.update({"autofocus": "autofocus"}) first_field.widget.attrs = attrs if not step: step = self.steps.current - if self.filter_owns_items and self.filter_owns \ - and step in self.filter_owns: + if self.filter_owns_items and self.filter_owns and step in self.filter_owns: for key in self.filter_owns[step]: - frm.fields[key].widget.source = str( - frm.fields[key].widget.source) + "own/" + frm.fields[key].widget.source = ( + str(frm.fields[key].widget.source) + "own/" + ) if frm.fields[key].widget.source_full is not None: - frm.fields[key].widget.source_full = str( - frm.fields[key].widget.source_full) + "own/" + frm.fields[key].widget.source_full = ( + str(frm.fields[key].widget.source_full) + "own/" + ) return form def render_next_step(self, form, **kwargs): @@ -1122,31 +1187,38 @@ class Wizard(IshtarWizard): - validate and end: nextstep = last step """ request = self.request - if request.POST.get('formset_modify') \ - or request.POST.get('formset_add') \ - or (self.formset_pop_deleted and [ - key for key in request.POST.keys() - if key.endswith('DELETE') and request.POST[key]]): + if ( + request.POST.get("formset_modify") + or request.POST.get("formset_add") + or ( + self.formset_pop_deleted + and [ + key + for key in request.POST.keys() + if key.endswith("DELETE") and request.POST[key] + ] + ) + ): return self.render(form) - elif 'validate_and_end' in request.POST \ - and request.POST['validate_and_end']: + elif "validate_and_end" in request.POST and request.POST["validate_and_end"]: last_step = self.steps.last new_form = self.get_form( last_step, data=self.storage.get_step_data(last_step), - files=self.storage.get_step_files(last_step)) + files=self.storage.get_step_files(last_step), + ) self.storage.current_step = last_step return self.render(new_form) return super(Wizard, self).render_next_step(form, **kwargs) def post(self, *args, **kwargs): # manage previous (or next) step - form_prev_step = self.request.POST.get('form_prev_step', None) + form_prev_step = self.request.POST.get("form_prev_step", None) if not form_prev_step: return super(Wizard, self).post(*args, **kwargs) try: # convert numerical step number to step name - step_number = int(self.request.POST['form_prev_step']) + step_number = int(self.request.POST["form_prev_step"]) wizard_goto_step = list(self.get_form_list().keys())[step_number] except (ValueError, IndexError): return super(Wizard, self).post(*args, **kwargs) @@ -1154,16 +1226,17 @@ class Wizard(IshtarWizard): return redirect(self.get_step_url(wizard_goto_step)) def session_get_keys(self, form_key): - """Get list of available keys for a specific form - """ + """Get list of available keys for a specific form""" request = self.request storage = self.storage - test = storage.prefix in request.session \ - and 'step_data' in request.session[storage.prefix] \ - and form_key in request.session[storage.prefix]['step_data'] + test = ( + storage.prefix in request.session + and "step_data" in request.session[storage.prefix] + and form_key in request.session[storage.prefix]["step_data"] + ) if not test: return [] - return request.session[storage.prefix]['step_data'][form_key].keys() + return request.session[storage.prefix]["step_data"][form_key].keys() def session_has_key(self, form_key, key=None, multi=None): """Check if the session has value of a specific form and (if provided) @@ -1171,39 +1244,41 @@ class Wizard(IshtarWizard): """ request = self.request storage = self.storage - test = storage.prefix in request.session \ - and 'step_data' in request.session[storage.prefix] \ - and form_key in request.session[storage.prefix]['step_data'] + test = ( + storage.prefix in request.session + and "step_data" in request.session[storage.prefix] + and form_key in request.session[storage.prefix]["step_data"] + ) if not key or not test: return test if multi: # only check if the first field is available - key = key.startswith(form_key) and key or \ - form_key + '-0-' + key - if key in request.session[storage.prefix]['step_data'][form_key]: + key = key.startswith(form_key) and key or form_key + "-0-" + key + if key in request.session[storage.prefix]["step_data"][form_key]: return True - key = key.startswith(form_key) and key or \ - form_key + '-' + key - return key in request.session[storage.prefix]['step_data'][form_key] + key = key.startswith(form_key) and key or form_key + "-" + key + return key in request.session[storage.prefix]["step_data"][form_key] @classmethod def session_reset(cls, request, url_name): prefix = url_name + normalize_name(cls.__name__) - storage = get_storage(cls.storage_name, prefix, request, - getattr(cls, 'file_storage', None)) + storage = get_storage( + cls.storage_name, prefix, request, getattr(cls, "file_storage", None) + ) storage.reset() @classmethod def session_set_value(cls, request, form_key, key, value, reset=False): - prefix = form_key.split('-')[1] + normalize_name(cls.__name__) - storage = get_storage(cls.storage_name, prefix, request, - getattr(cls, 'file_storage', None)) + prefix = form_key.split("-")[1] + normalize_name(cls.__name__) + storage = get_storage( + cls.storage_name, prefix, request, getattr(cls, "file_storage", None) + ) if reset: storage.reset() data = storage.get_step_data(form_key) if not data: data = MultiValueDict() - key = key if key.startswith(form_key) else form_key + '-' + key + key = key if key.startswith(form_key) else form_key + "-" + key data[key] = value storage.set_step_data(form_key, data) @@ -1213,8 +1288,7 @@ class Wizard(IshtarWizard): Image and file cannot be passed as object """ for k in data: - if isinstance(data[k], ImageFieldFile) \ - or isinstance(data[k], FileField): + if isinstance(data[k], ImageFieldFile) or isinstance(data[k], FileField): try: data[k] = data[k].path except ValueError: @@ -1227,8 +1301,8 @@ class Wizard(IshtarWizard): request = self.request storage = self.storage if not multi: - key = key.startswith(form_key) and key or form_key + '-' + key - val = request.session[storage.prefix]['step_data'][form_key][key] + key = key.startswith(form_key) and key or form_key + "-" + key + val = request.session[storage.prefix]["step_data"][form_key][key] if type(val) in (list, tuple) and val: if multi_value: return val @@ -1239,11 +1313,14 @@ class Wizard(IshtarWizard): return [] return val vals = [] - for k in request.session[storage.prefix]['step_data'][form_key]: - if k.startswith(form_key) and k.endswith(key) and \ - request.session[storage.prefix]['step_data'][form_key][k]: - val = request.session[storage.prefix]['step_data'][form_key][k] - number = int(k[len(form_key):-len(key)].strip('-')) + for k in request.session[storage.prefix]["step_data"][form_key]: + if ( + k.startswith(form_key) + and k.endswith(key) + and request.session[storage.prefix]["step_data"][form_key][k] + ): + val = request.session[storage.prefix]["step_data"][form_key][k] + number = int(k[len(form_key) : -len(key)].strip("-")) if type(val) in (list, tuple): val = val[0] vals.append((number, val)) @@ -1257,11 +1334,12 @@ class Wizard(IshtarWizard): for key in self.main_item_select_keys: main_form_key = key + self.url_name try: - idx = int(self.session_get_value(main_form_key, - self.current_object_key)) + idx = int( + self.session_get_value(main_form_key, self.current_object_key) + ) current_obj = self.model.objects.get(pk=idx) break - except(TypeError, ValueError, ObjectDoesNotExist): + except (TypeError, ValueError, ObjectDoesNotExist): pass return current_obj @@ -1270,27 +1348,29 @@ class Wizard(IshtarWizard): current_step = self.steps.current request = self.request - step_is_main_select = bool([k for k in self.main_item_select_keys - if step.startswith(k)]) - if step_is_main_select and step in self.form_list \ - and 'pk' in self.form_list[step].associated_models: - model_name = self.form_list[step]\ - .associated_models['pk'].__name__.lower() + step_is_main_select = bool( + [k for k in self.main_item_select_keys if step.startswith(k)] + ) + if ( + step_is_main_select + and step in self.form_list + and "pk" in self.form_list[step].associated_models + ): + model_name = self.form_list[step].associated_models["pk"].__name__.lower() if step == current_step: self.storage.reset() val = model_name in request.session and request.session[model_name] if val: - return MultiValueDict({'pk': val}) + return MultiValueDict({"pk": val}) elif current_obj: return self.get_instanced_init(current_obj, step) current_form = self.form_list[current_step] initial = MultiValueDict() - if hasattr(current_form, 'currents'): + if hasattr(current_form, "currents"): for key in current_form.currents: model_name = current_form.currents[key].__name__.lower() - val = model_name in request.session and \ - request.session[model_name] + val = model_name in request.session and request.session[model_name] if val: initial[key] = val if not initial and hasattr(current_form, "base_fields"): @@ -1304,7 +1384,7 @@ class Wizard(IshtarWizard): def get_object_name(self, obj): obj_name = obj.__class__.__name__.lower() # prefer a specialized name if available - prefixes = self.storage.prefix.split('_') + prefixes = self.storage.prefix.split("_") if len(prefixes) > 1 and prefixes[-2].startswith(obj_name): obj_name = prefixes[-2] return obj_name @@ -1316,14 +1396,18 @@ class Wizard(IshtarWizard): initial = MultiValueDict() # manage json field - if hasattr(obj, 'data') and obj.data and hasattr(self, 'json_fields') \ - and getattr(c_form, 'form_slug', None) \ - and c_form.form_slug in self.json_fields \ - and obj.data: + if ( + hasattr(obj, "data") + and obj.data + and hasattr(self, "json_fields") + and getattr(c_form, "form_slug", None) + and c_form.form_slug in self.json_fields + and obj.data + ): for key in self.json_fields[c_form.form_slug]: - if not key.startswith('data__'): + if not key.startswith("data__"): continue - json_keys = key[len('data__'):].split('__') + json_keys = key[len("data__") :].split("__") value = obj.data for json_key in json_keys: if json_key not in value: @@ -1333,44 +1417,43 @@ class Wizard(IshtarWizard): if value: initial[key] = value elif value is False: - initial[key] = 'False' + initial[key] = "False" for base_field in c_form.base_fields.keys(): value = obj base_model = None - if hasattr(c_form, 'base_model') and \ - base_field == c_form.base_model: + if hasattr(c_form, "base_model") and base_field == c_form.base_model: base_model = base_field - if hasattr(c_form, 'base_models') and \ - base_field in c_form.base_models: + if hasattr(c_form, "base_models") and base_field in c_form.base_models: base_model = base_field if base_model: - key = base_model + 's' - initial.setlist(base_field, [ - str(val.pk) for val in getattr(value, key).all()]) + key = base_model + "s" + initial.setlist( + base_field, [str(val.pk) for val in getattr(value, key).all()] + ) else: - fields = base_field.split('__') + fields = base_field.split("__") for field in fields: if callable(value): value = value() - if not hasattr(value, field) or \ - getattr(value, field) is None: + if not hasattr(value, field) or getattr(value, field) is None: value = obj break value = getattr(value, field) - if hasattr(value, 'all') and callable(value.all): + if hasattr(value, "all") and callable(value.all): if not value.count(): continue - initial.setlist(base_field, - [str(v.pk) for v in value.all()]) + initial.setlist(base_field, [str(v.pk) for v in value.all()]) continue if value == obj: continue - if hasattr(value, 'pk'): + if hasattr(value, "pk"): value = value.pk - if value in (True, False) \ - or isinstance(value, ImageFieldFile) \ - or isinstance(value, FileField): + if ( + value in (True, False) + or isinstance(value, ImageFieldFile) + or isinstance(value, FileField) + ): initial[base_field] = value elif value is not None: initial[base_field] = str(value) @@ -1380,22 +1463,20 @@ class Wizard(IshtarWizard): def _get_vals_for_instanced_init_for_formset(field, child_obj, vals): if hasattr(child_obj, field): value = getattr(child_obj, field) - if hasattr(value, 'pk'): + if hasattr(value, "pk"): value = value.pk - elif hasattr(value, 'all'): - vals.setlist(field, [ - str(v.pk) - for v in getattr(child_obj, field).all() - ]) + elif hasattr(value, "all"): + vals.setlist( + field, [str(v.pk) for v in getattr(child_obj, field).all()] + ) return vals if value is not None: vals[field] = str(value) elif hasattr(child_obj, field + "s"): # M2M - vals.setlist(field, [ - str(v.pk) - for v in getattr(child_obj, field + "s").all() - ]) + vals.setlist( + field, [str(v.pk) for v in getattr(child_obj, field + "s").all()] + ) return vals def _get_instanced_init_for_formset(self, obj, current_step, c_form): @@ -1403,29 +1484,28 @@ class Wizard(IshtarWizard): Get initial data from an object: formset """ initial = [] - if hasattr(c_form.form, 'base_model'): - key = c_form.form.base_model + 's' + if hasattr(c_form.form, "base_model"): + key = c_form.form.base_model + "s" else: - key = current_step.split('-')[0] + key = current_step.split("-")[0] if not hasattr(obj, key): return initial keys = list(c_form.form.base_fields.keys()) related = getattr(obj, key) # manage through through = False - if hasattr(related, 'through') and related.through: + if hasattr(related, "through") and related.through: if hasattr(related.through, "RELATED_SET_NAME"): related_set_name = related.through.RELATED_SET_NAME else: - related_set_name = str( - related.through.__name__ + '_set').lower() + related_set_name = str(related.through.__name__ + "_set").lower() if hasattr(obj, related_set_name): through = True related = getattr(obj, related_set_name) query = related if not through and not obj._meta.ordering: - query = query.order_by('pk') + query = query.order_by("pk") # an intermediary model is used through_fields = [] if through and not related.model._meta.auto_created: @@ -1434,7 +1514,7 @@ class Wizard(IshtarWizard): related_model = target_field.field.related_model for field in related_model._meta.get_fields(): through_fields.append(field.name) - through_fields.append('pk') + through_fields.append("pk") for child_obj in query.all(): if not keys: @@ -1446,11 +1526,13 @@ class Wizard(IshtarWizard): else: for field in keys: vals = self._get_vals_for_instanced_init_for_formset( - field, child_obj, vals) + field, child_obj, vals + ) for field in through_fields: related_obj = getattr(child_obj, c_form.form.base_model) vals = self._get_vals_for_instanced_init_for_formset( - field, related_obj, vals) + field, related_obj, vals + ) if vals: initial.append(vals) return initial @@ -1462,33 +1544,39 @@ class Wizard(IshtarWizard): current_step = step or self.steps.current c_form = self.form_list[current_step] - ishtaruser = self.request.user.ishtaruser \ - if hasattr(self.request.user, 'ishtaruser') else None - if ishtaruser and ishtaruser.current_profile \ - and ishtaruser.current_profile.auto_pin: + ishtaruser = ( + self.request.user.ishtaruser + if hasattr(self.request.user, "ishtaruser") + else None + ) + if ( + ishtaruser + and ishtaruser.current_profile + and ishtaruser.current_profile.auto_pin + ): # make the current object the default item for the session self.request.session[self.get_object_name(obj)] = str(obj.pk) initial = MultiValueDict() # posted data or already in session - if self.request.POST or \ - (step in self.request.session[self.storage.prefix] and - self.request.session[self.storage.prefix]['step_data'][step]): + if self.request.POST or ( + step in self.request.session[self.storage.prefix] + and self.request.session[self.storage.prefix]["step_data"][step] + ): return initial - if hasattr(c_form, 'base_fields'): + if hasattr(c_form, "base_fields"): return self._get_instanced_init_for_form(obj, c_form) - elif hasattr(c_form, 'management_form'): - return self._get_instanced_init_for_formset(obj, current_step, - c_form) + elif hasattr(c_form, "management_form"): + return self._get_instanced_init_for_formset(obj, current_step, c_form) return initial class SearchWizard(IshtarWizard): model = None - label = '' + label = "" modification = None # True when the wizard modify an item - storage_name = 'formtools.wizard.storage.session.SessionStorage' + storage_name = "formtools.wizard.storage.session.SessionStorage" def get_wizard_name(self): """ @@ -1497,13 +1585,13 @@ class SearchWizard(IshtarWizard): return self.url_name def get_prefix(self, request, *args, **kwargs): - """As the class name can interfere when reused prefix with the url_name - """ + """As the class name can interfere when reused prefix with the url_name""" return self.url_name + super(SearchWizard, self).get_prefix( - request, *args, **kwargs) + request, *args, **kwargs + ) def get_template_names(self): - templates = ['ishtar/wizard/search.html'] + templates = ["ishtar/wizard/search.html"] return templates def get_label(self): @@ -1511,31 +1599,34 @@ class SearchWizard(IshtarWizard): def get_context_data(self, form, **kwargs): context = super(SearchWizard, self).get_context_data(form) - self.request.session['CURRENT_ACTION'] = self.get_wizard_name() + self.request.session["CURRENT_ACTION"] = self.get_wizard_name() current_step = self.steps.current - bookmark = self.request.GET.get('bookmark', None) + bookmark = self.request.GET.get("bookmark", None) default_search_vector = None if bookmark and self.model: slug = self.model.SLUG if slug == "site": slug = "archaeologicalsite" - app_label = self.model.__module__.split('.')[0] + app_label = self.model.__module__.split(".")[0] try: - app_label = self.model.__module__.split('.')[0] + app_label = self.model.__module__.split(".")[0] sq = models.SearchQuery.objects.get( pk=bookmark, content_type__app_label=app_label, content_type__model=slug, - profile__person__ishtaruser__user_ptr=self.request.user + profile__person__ishtaruser__user_ptr=self.request.user, ) default_search_vector = sq.query.replace('"', "''") except models.SearchQuery.DoesNotExist: pass - context.update({ - 'current_step': self.form_list[current_step], - 'is_search': True, 'wizard_label': self.get_label(), - 'default_search_vector': default_search_vector - }) + context.update( + { + "current_step": self.form_list[current_step], + "is_search": True, + "wizard_label": self.get_label(), + "default_search_vector": default_search_vector, + } + ) return context @@ -1553,8 +1644,9 @@ class DocumentSearch(SearchWizard): class DeletionWizard(Wizard): def __init__(self, *args, **kwargs): - if (not hasattr(self, 'fields') or not self.fields) and \ - (hasattr(self, 'model') and hasattr(self.model, 'TABLE_COLS')): + if (not hasattr(self, "fields") or not self.fields) and ( + hasattr(self, "model") and hasattr(self.model, "TABLE_COLS") + ): self.fields = self.model.TABLE_COLS assert self.model super(DeletionWizard, self).__init__(*args, **kwargs) @@ -1566,10 +1658,9 @@ class DeletionWizard(Wizard): if not hasattr(form, "cleaned_data"): continue for key in form.cleaned_data: - if key == 'pk': - model = form.associated_models['pk'] - self.current_obj = model.objects.get( - pk=form.cleaned_data['pk']) + if key == "pk": + model = form.associated_models["pk"] + self.current_obj = model.objects.get(pk=form.cleaned_data["pk"]) if not self.current_obj: return datas res = {} @@ -1580,19 +1671,19 @@ class DeletionWizard(Wizard): if not value: continue label = "" - if hasattr(field, 'verbose_name'): + if hasattr(field, "verbose_name"): label = field.verbose_name - if hasattr(value, 'all'): - if not label and hasattr(field, 'related_model'): + if hasattr(value, "all"): + if not label and hasattr(field, "related_model"): label = field.related_model._meta.verbose_name_plural value = ", ".join([str(item) for item in value.all()]) if not value: continue else: value = str(value) - res[field.name] = (label, value, '') + res[field.name] = (label, value, "") if not datas and self.fields: - datas = [['', []]] + datas = [["", []]] for field in self.fields: if field in res: datas[0][1].append(res[field]) @@ -1605,12 +1696,11 @@ class DeletionWizard(Wizard): obj.delete() except ObjectDoesNotExist: pass - return render( - self.request, 'ishtar/wizard/wizard_delete_done.html', {}) + return render(self.request, "ishtar/wizard/wizard_delete_done.html", {}) class MultipleItemWizard(Wizard): - main_item_select_keys = ('selec-',) + main_item_select_keys = ("selec-",) current_object_key = "pks" def get_current_objects(self): @@ -1618,28 +1708,30 @@ class MultipleItemWizard(Wizard): for key in self.main_item_select_keys: main_form_key = key + self.url_name try: - pks = self.session_get_value(main_form_key, - self.current_object_key) + pks = self.session_get_value(main_form_key, self.current_object_key) if pks: for pk in pks.split(","): current_objs.append(self.model.objects.get(pk=int(pk))) - except(TypeError, ValueError, ObjectDoesNotExist): + except (TypeError, ValueError, ObjectDoesNotExist): pass return current_objs class MultipleDeletionWizard(MultipleItemWizard): def __init__(self, *args, **kwargs): - if (not hasattr(self, 'fields') or not self.fields) and \ - (hasattr(self, 'model') and hasattr(self.model, 'TABLE_COLS')): + if (not hasattr(self, "fields") or not self.fields) and ( + hasattr(self, "model") and hasattr(self.model, "TABLE_COLS") + ): self.fields = self.model.TABLE_COLS assert self.model super(MultipleDeletionWizard, self).__init__(*args, **kwargs) def get_template_names(self): current_step = self.steps.current - if current_step.startswith("final-") and \ - current_step not in self.wizard_templates: + if ( + current_step.startswith("final-") + and current_step not in self.wizard_templates + ): return ["ishtar/wizard/delete_wizard.html"] return super(MultipleDeletionWizard, self).get_template_names() @@ -1650,8 +1742,8 @@ class MultipleDeletionWizard(MultipleItemWizard): if not hasattr(form, "cleaned_data"): continue for key in form.cleaned_data: - if key == 'pks': - model = form.associated_models['pks'] + if key == "pks": + model = form.associated_models["pks"] pks = form.cleaned_data["pks"].split(",") for pk in pks: try: @@ -1670,20 +1762,20 @@ class MultipleDeletionWizard(MultipleItemWizard): if not value: continue label = "" - if hasattr(field, 'verbose_name'): + if hasattr(field, "verbose_name"): label = field.verbose_name - if hasattr(value, 'all'): - if not label and hasattr(field, 'related_model'): + if hasattr(value, "all"): + if not label and hasattr(field, "related_model"): label = field.related_model._meta.verbose_name_plural value = ", ".join([str(item) for item in value.all()]) if not value: continue else: value = str(value) - res[field.name] = (label, value, '') + res[field.name] = (label, value, "") full_res.append(res) if not datas and self.fields: - datas = [['', []]] + datas = [["", []]] datas = [] for idx, res in enumerate(full_res): data = [] @@ -1694,8 +1786,7 @@ class MultipleDeletionWizard(MultipleItemWizard): return datas def get_context_data(self, form, **kwargs): - data = super(MultipleDeletionWizard, self).get_context_data(form, - **kwargs) + data = super(MultipleDeletionWizard, self).get_context_data(form, **kwargs) data["current_objs"] = self.get_current_objects() return data @@ -1713,8 +1804,7 @@ class MultipleDeletionWizard(MultipleItemWizard): messages.add_message(self.request, messages.INFO, msg) if self.redirect_url: return HttpResponseRedirect(reverse(self.redirect_url)) - return render( - self.request, 'ishtar/wizard/wizard_delete_done.html', {}) + return render(self.request, "ishtar/wizard/wizard_delete_done.html", {}) class ClosingWizard(Wizard): @@ -1730,10 +1820,9 @@ class ClosingWizard(Wizard): if not hasattr(form, "cleaned_data"): continue for key in form.cleaned_data: - if key == 'pk': - model = form.associated_models['pk'] - self.current_obj = model.objects.get( - pk=form.cleaned_data['pk']) + if key == "pk": + model = form.associated_models["pk"] + self.current_obj = model.objects.get(pk=form.cleaned_data["pk"]) if not self.current_obj: return datas res = {} @@ -1743,15 +1832,15 @@ class ClosingWizard(Wizard): value = getattr(self.current_obj, field.name) if not value: continue - if hasattr(value, 'all'): + if hasattr(value, "all"): value = ", ".join([str(item) for item in value.all()]) if not value: continue else: value = str(value) - res[field.name] = (field.verbose_name, value, '') + res[field.name] = (field.verbose_name, value, "") if not datas and self.fields: - datas = [['', []]] + datas = [["", []]] for field in self.fields: if field in res: datas[0][1].append(res[field]) @@ -1761,26 +1850,24 @@ class ClosingWizard(Wizard): obj = self.get_current_object() for form in form_list: if form.is_valid(): - if 'end_date' in form.cleaned_data \ - and hasattr(obj, 'end_date'): - obj.end_date = form.cleaned_data['end_date'] + if "end_date" in form.cleaned_data and hasattr(obj, "end_date"): + obj.end_date = form.cleaned_data["end_date"] obj.save() - return render( - self.request, 'ishtar/wizard/wizard_closing_done.html', {}) + return render(self.request, "ishtar/wizard/wizard_closing_done.html", {}) class PersonWizard(Wizard): model = models.Person - wizard_templates = { - 'identity-person_creation': "ishtar/wizard/wizard_person.html"} - wizard_done_window = reverse_lazy('show-person') + wizard_templates = {"identity-person_creation": "ishtar/wizard/wizard_person.html"} + wizard_done_window = reverse_lazy("show-person") redirect_url = "person_modification" class PersonModifWizard(PersonWizard): modification = True wizard_templates = { - 'identity-person_modification': "ishtar/wizard/wizard_person.html"} + "identity-person_modification": "ishtar/wizard/wizard_person.html" + } class PersonDeletionWizard(MultipleDeletionWizard): @@ -1788,7 +1875,8 @@ class PersonDeletionWizard(MultipleDeletionWizard): fields = model.TABLE_COLS redirect_url = "person_deletion" wizard_templates = { - 'final-person_deletion': 'ishtar/wizard/wizard_person_deletion.html'} + "final-person_deletion": "ishtar/wizard/wizard_person_deletion.html" + } class IshtarUserDeletionWizard(DeletionWizard): @@ -1798,7 +1886,7 @@ class IshtarUserDeletionWizard(DeletionWizard): class OrganizationWizard(Wizard): model = models.Organization - wizard_done_window = reverse_lazy('show-organization') + wizard_done_window = reverse_lazy("show-organization") redirect_url = "organization_modification" @@ -1811,14 +1899,14 @@ class OrganizationDeletionWizard(MultipleDeletionWizard): fields = model.TABLE_COLS redirect_url = "organization_deletion" wizard_templates = { - 'final-organization_deletion': - 'ishtar/wizard/wizard_organization_deletion.html'} + "final-organization_deletion": "ishtar/wizard/wizard_organization_deletion.html" + } class AccountWizard(Wizard): model = models.Person formset_pop_deleted = False - wizard_done_window = reverse_lazy('show-person') + wizard_done_window = reverse_lazy("show-person") def get_formated_datas(self, forms): datas = super(AccountWizard, self).get_formated_datas(forms) @@ -1826,7 +1914,7 @@ class AccountWizard(Wizard): if not hasattr(form, "cleaned_data"): continue for key in form.cleaned_data: - if key == 'hidden_password' and form.cleaned_data[key]: + if key == "hidden_password" and form.cleaned_data[key]: datas[-1][1].append((_("New password"), "*" * 8)) return datas @@ -1834,9 +1922,9 @@ class AccountWizard(Wizard): """ Save the account """ - form_dict = kwargs['form_dict'] + form_dict = kwargs["form_dict"] - main_form = form_dict['account-account_management'] + main_form = form_dict["account-account_management"] if not main_form.is_valid(): return self.render(main_form) @@ -1844,7 +1932,7 @@ class AccountWizard(Wizard): associated_models = main_form.associated_models if type(main_form.cleaned_data) == dict: for key in main_form.cleaned_data: - if key == 'pk': + if key == "pk": continue value = main_form.cleaned_data[key] if key in associated_models and value: @@ -1852,23 +1940,27 @@ class AccountWizard(Wizard): dct[key] = value person = self.get_current_object() if not person: - return self.render(form_dict['selec-account_management']) + return self.render(form_dict["selec-account_management"]) for key in list(dct.keys()): - if key.startswith('hidden_password'): - dct['password'] = dct.pop(key) + if key.startswith("hidden_password"): + dct["password"] = dct.pop(key) try: account = models.IshtarUser.objects.get(person=person).user_ptr - account.username = dct['username'] - account.email = dct['email'] + account.username = dct["username"] + account.email = dct["email"] except ObjectDoesNotExist: now = datetime.datetime.now() account = models.User.objects.create( - username=dct['username'], email=dct['email'], - first_name=person.surname or '***', - last_name=person.name or '***', - is_staff=False, is_active=True, is_superuser=False, - last_login=now, date_joined=now + username=dct["username"], + email=dct["email"], + first_name=person.surname or "***", + last_name=person.name or "***", + is_staff=False, + is_active=True, + is_superuser=False, + last_login=now, + date_joined=now, ) ishtaruser = account.ishtaruser old_person_pk = ishtaruser.person.pk @@ -1876,35 +1968,34 @@ class AccountWizard(Wizard): ishtaruser.save() models.Person.objects.get(pk=old_person_pk).delete() - if dct['password']: - account.set_password(dct['password']) + if dct["password"]: + account.set_password(dct["password"]) account.save() - profile_form = form_dict['profile-account_management'] + profile_form = form_dict["profile-account_management"] for form in profile_form: data = form.cleaned_data profile = None - if data.get('pk', None): + if data.get("pk", None): try: profile = models.UserProfile.objects.get( - pk=data['pk'], person=person) + pk=data["pk"], person=person + ) except models.UserProfile.DoesNotExist: continue - if data.get('DELETE', None): + if data.get("DELETE", None): profile.delete() continue - elif data.get('DELETE', None): + elif data.get("DELETE", None): continue - name = data.get('name', None) + name = data.get("name", None) - profile_type_id = data.get('profile_type', None) + profile_type_id = data.get("profile_type", None) if not profile_type_id: continue try: - profile_type = models.ProfileType.objects.get( - pk=profile_type_id - ) + profile_type = models.ProfileType.objects.get(pk=profile_type_id) except models.ProfileType.DoesNotExist: continue @@ -1917,8 +2008,9 @@ class AccountWizard(Wizard): profile.save() else: profile = models.UserProfile.objects.create( - profile_type=profile_type, person=person, name=name) - area_pks = data.get('area', None) + profile_type=profile_type, person=person, name=name + ) + area_pks = data.get("area", None) areas = [] if area_pks: try: @@ -1930,36 +2022,40 @@ class AccountWizard(Wizard): for area in areas: profile.areas.add(area) - final_form = form_dict['final-account_management'] - if settings.ADMINS and type(final_form.cleaned_data) == dict and \ - final_form.cleaned_data.get('send_password', None): + final_form = form_dict["final-account_management"] + if ( + settings.ADMINS + and type(final_form.cleaned_data) == dict + and final_form.cleaned_data.get("send_password", None) + ): site = Site.objects.get_current() - app_name = site and ("Ishtar - " + site.name) \ - or "Ishtar" + app_name = site and ("Ishtar - " + site.name) or "Ishtar" context = { - 'login': dct['username'], - 'password': dct['password'], - 'app_name': app_name, - 'site': site and site.domain or "", - "scheme": self.request.scheme + "login": dct["username"], + "password": dct["password"], + "app_name": app_name, + "site": site and site.domain or "", + "scheme": self.request.scheme, } - t = loader.get_template('account_activation_email.txt') + t = loader.get_template("account_activation_email.txt") msg = t.render(context, self.request) subject = _("[%(app_name)s] Account creation/modification") % { - "app_name": app_name} - send_mail(subject, msg, settings.ADMINS[0][1], - [dct['email']], fail_silently=True) + "app_name": app_name + } + send_mail( + subject, msg, settings.ADMINS[0][1], [dct["email"]], fail_silently=True + ) res = render( - self.request, self.wizard_done_template, - {'wizard_done_window': str(self.wizard_done_window), - 'item': person}, + self.request, + self.wizard_done_template, + {"wizard_done_window": str(self.wizard_done_window), "item": person}, ) return res def get_form_kwargs(self, step=None): kwargs = super(AccountWizard, self).get_form_kwargs(step) - if step == 'account-account_management': - kwargs['person'] = self.get_current_object() + if step == "account-account_management": + kwargs["person"] = self.get_current_object() return kwargs def get_form(self, step=None, data=None, files=None): @@ -1967,10 +2063,9 @@ class AccountWizard(Wizard): Display the "Send email" field if necessary """ form = super(AccountWizard, self).get_form(step, data, files) - if not hasattr(form, 'is_hidden'): + if not hasattr(form, "is_hidden"): return form - if self.session_get_value('account-account_management', - 'hidden_password'): + if self.session_get_value("account-account_management", "hidden_password"): form.is_hidden = False return form @@ -1980,22 +2075,29 @@ class SourceWizard(Wizard): def get_extra_model(self, dct, m2m, form_list): dct = super(SourceWizard, self).get_extra_model(dct, m2m, form_list) - if 'history_modifier' in dct: - dct.pop('history_modifier') + if "history_modifier" in dct: + dct.pop("history_modifier") return dct DOCUMENT_EXCLUDED = models.Document.RELATED_MODELS + [ - "id", "history_creator", "history_modifier", "search_vector", "imports", - "last_modified", "document" + "id", + "history_creator", + "history_modifier", + "search_vector", + "imports", + "last_modified", + "document", ] class DocumentDeletionWizard(MultipleDeletionWizard): model = models.Document fields = [ - f.name for f in models.Document._meta.get_fields() - if f.name not in DOCUMENT_EXCLUDED] + f.name + for f in models.Document._meta.get_fields() + if f.name not in DOCUMENT_EXCLUDED + ] fields += models.Document.RELATED_MODELS - filter_owns = {'selec-document_deletion': ['pks']} + filter_owns = {"selec-document_deletion": ["pks"]} redirect_url = "document_deletion" |