99)
1010
1111from django .db .models import QuerySet
12+ from django .db .models .fields .files import FieldFile
1213from django .http import HttpResponseRedirect
13- from django .template import Context
1414from django .utils .safestring import mark_safe
1515from django .utils .translation import gettext_lazy
1616
2323 Actions ,
2424 group_actions ,
2525)
26+ from iommi .error import Errors
2627from iommi .sort_after import LAST
2728from iommi .base import (
2829 MISSING ,
@@ -115,24 +116,26 @@ def render_cell_contents(self):
115116
116117 bind_field_from_instance (field , self .row )
117118
118- if self .table .extra_evaluated .render_inputs_only or 'hidden' in getattr (field , 'iommi_shortcut_stack' , []):
119- input_html = field .input .__html__ ()
120- else :
121- input_html = field .__html__ ()
122-
123- field .attr = orig_attr
124-
125119 # Check both edit_errors and create_errors
126120 errors = None
127121 if self .table .edit_errors :
128122 errors = self .table .edit_errors .get (path )
129123 if not errors and self .table .create_errors :
130124 errors = self .table .create_errors .get (path )
131125
132- if errors :
133- return Template (
134- '{{ input_html }}<br><span class="text-danger"><ul class="errors">{% for error in errors %}<li>{{ error }}</li>{% endfor %}</ul></span>'
135- ).render (context = Context (dict (input_html = input_html , errors = errors )))
126+ if self .table .extra_evaluated .render_inputs_only or 'hidden' in getattr (field , 'iommi_shortcut_stack' , []):
127+ input_html = field .input .__html__ ()
128+ else :
129+ if self .table .extra_evaluated .input_labels_include is False :
130+ field .label .include = False
131+ if errors :
132+ field ._errors = errors
133+
134+ input_html = field .__html__ ()
135+
136+ field .errors = Errors (parent = field )
137+
138+ field .attr = orig_attr
136139
137140 return input_html
138141 else :
@@ -143,6 +146,7 @@ def on_refine_done(self):
143146 self .value = None
144147 super (EditCell , self ).on_refine_done ()
145148
149+
146150def bind_field_from_instance (field , instance ):
147151 field .input = field .iommi_namespace .input (_name = 'input' )
148152 field .non_editable_input = field .iommi_namespace .non_editable_input (_name = 'non_editable_input' )
@@ -337,6 +341,7 @@ def save(cells_iterator, form):
337341 instance = cells .row
338342 form .instance = instance
339343 attrs_to_save = []
344+ to_save_in_second_phase = []
340345 for cell in cells .iter_editable_cells ():
341346 path = cell .get_path ()
342347 if path not in parsed_data :
@@ -345,20 +350,25 @@ def save(cells_iterator, form):
345350 field = form .fields [cell .column .iommi_name ()]
346351 field ._iommi_path_override = path
347352 if cells .is_create_template or (
348- field .invoke_callback (field .read_from_instance , instance = instance ) != value
353+ ( instance_value := field .invoke_callback (field .read_from_instance , instance = instance ) ) != value
349354 ):
350- field .invoke_callback (field .write_to_instance , instance = instance , value = value )
351- if not field .extra .get ('django_related_field' , False ):
355+ if field .extra .get ('django_related_field' , False ):
356+ if not cells .is_create_template and isinstance (instance_value , FieldFile ) and not instance_value and value is None :
357+ # instance value of FileField/ImageField is an empty FieldFile/ImageFieldFile, not None
358+ pass
359+ else :
360+ to_save_in_second_phase .append ((field , value ))
361+ else :
362+ field .invoke_callback (field .write_to_instance , instance = instance , value = value )
352363 attrs_to_save .append (field .attr )
353364
354- to_save .append ((instance , attrs_to_save ))
365+ to_save .append ((instance , attrs_to_save , to_save_in_second_phase ))
355366
356367 to_save .sort (key = lambda x : abs (x [0 ].pk ))
357- for instance , attrs_to_save in to_save :
358- if not to_save :
359- pass
368+ for instance , attrs_to_save , to_save_in_second_phase in to_save :
360369 if instance .pk is not None and instance .pk < 0 :
361370 instance .pk = None
371+
362372 if instance .pk is None :
363373 attrs_to_save = None
364374
@@ -371,6 +381,11 @@ def save(cells_iterator, form):
371381 model_object = getattr_path (model_object , prefix )
372382 model_object .save (update_fields = [strip_prefix (x , prefix = f'{ prefix } __' ) for x in attrs_to_save if x .startswith (prefix )])
373383
384+ if to_save_in_second_phase :
385+ for field , value in to_save_in_second_phase :
386+ field .invoke_callback (field .write_to_instance , instance = instance , value = value )
387+ instance .save ()
388+
374389 save (table .cells_for_rows (), table .edit_form )
375390 save (table .cells_for_rows_for_create (save = True ), table .create_form )
376391
@@ -459,7 +474,8 @@ class Meta:
459474
460475 reorderable = False
461476
462- extra_evaluated__render_inputs_only = lambda table , ** _ : table .row .layout is None
477+ extra_evaluated__render_inputs_only = False
478+ extra_evaluated__input_labels_include = lambda table , ** _ : table .row .layout is not None
463479
464480 bulk__include = True
465481
@@ -651,8 +667,9 @@ def parse_virtual_pk(k):
651667 except ValueError :
652668 return None
653669
654- post_data = self .get_request ().POST
655- pks = {parse_virtual_pk (k ) for k in keys (post_data )}
670+ request = self .get_request ()
671+ post_data = request .POST
672+ pks = {parse_virtual_pk (k ) for k in {* keys (post_data ), * keys (request .FILES )}}
656673 virtual_pks = [
657674 k for k in pks
658675 if k is not None and k < 0 and f'{ delete_prefix } { k } ' not in post_data
0 commit comments