Ticket #1330: django_edit_inline.diff

File django_edit_inline.diff, 23.4 KB (added by Christopher Lenz <cmlenz@…>, 9 years ago)

Patch that restores edit_inline functionality to state in trunk

  • django/db/models/manipulators.py

     
    139139
    140140            if child_follow:
    141141                obj_list = expanded_data[related.var_name].items()
     142                if not obj_list:
     143                    continue
     144
    142145                obj_list.sort(lambda x, y: cmp(int(x[0]), int(y[0])))
    143146
    144147                # For each related item...
     
    187190                        if param != None:
    188191                            params[f.attname] = param
    189192
    190                         # Related links are a special case, because we have to
    191                         # manually set the "content_type_id" and "object_id" fields.
    192                         if self.opts.has_related_links and related.opts.module_name == 'relatedlinks':
    193                             contenttypes_mod = get_module('core', 'contenttypes')
    194                             params['content_type_id'] = contenttypes_mod.get_object(package__label__exact=self.opts.app_label, python_module_name__exact=self.opts.module_name).id
    195                             params['object_id'] = new_object.id
    196 
    197193                    # Create the related item.
    198                     new_rel_obj = related.opts.get_model_module().Klass(**params)
     194                    new_rel_obj = related.model(**params)
    199195
    200196                    # If all the core fields were provided (non-empty), save the item.
    201197                    if all_cores_given:
  • django/db/models/fields/__init__.py

     
    7474        self.primary_key = primary_key
    7575        self.maxlength, self.unique = maxlength, unique
    7676        self.blank, self.null = blank, null
    77         self.rel, self.default = rel, default
     77        self.core, self.rel, self.default = core, rel, default
    7878        self.editable = editable
    7979        self.validator_list = validator_list or []
    8080        self.prepopulate_from = prepopulate_from
     
    8888        # Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
    8989        self.db_index = db_index
    9090
    91         self.deprecated_args = []
    92         if core:
    93             self.deprecated_args.append('core')
    94 
    9591        # Increase the creation counter, and save our local copy.
    9692        self.creation_counter = Field.creation_counter
    9793        Field.creation_counter += 1
     
    219215            params['validator_list'].append(curry(manipulator_validator_unique, self, opts, manipulator))
    220216
    221217        # Only add is_required=True if the field cannot be blank. Primary keys
    222         # are a special case.
    223         params['is_required'] = not self.blank and not self.primary_key
     218        # are a special case, and fields in a related context should set this
     219        # as False, because they'll be caught by a separate validator --
     220        # RequiredIfOtherFieldGiven.
     221        params['is_required'] = not self.blank and not self.primary_key and not rel
    224222
    225223        # BooleanFields (CheckboxFields) are a special case. They don't take
    226224        # is_required or validator_list.
    227225        if isinstance(self, BooleanField):
    228226            del params['validator_list'], params['is_required']
    229227
     228        # If this field is in a related context, check whether any other fields
     229        # in the related object have core=True. If so, add a validator --
     230        # RequiredIfOtherFieldsGiven -- to this FormField.
     231        if rel and not self.blank and not isinstance(self, AutoField) and not isinstance(self, FileField):
     232            # First, get the core fields, if any.
     233            core_field_names = []
     234            for f in opts.fields:
     235                if f.core and f != self:
     236                    core_field_names.extend(f.get_manipulator_field_names(name_prefix))
     237            # Now, if there are any, add the validator to this FormField.
     238            if core_field_names:
     239                params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, gettext_lazy("This field is required.")))
     240
    230241        # Finally, add the field_names.
    231242        field_names = self.get_manipulator_field_names(name_prefix)
    232243        return [man(field_name=field_names[i], **params) for i, man in enumerate(field_objs)]
     
    239250        Given the full new_data dictionary (from the manipulator), returns this
    240251        field's data.
    241252        """
    242         #if rel:
    243         #    return new_data.get(self.name, [self.get_default()])[0]
    244         #else:
     253        if rel:
     254            return new_data.get(self.name, [self.get_default()])[0]
    245255        val = new_data.get(self.name, self.get_default())
    246256        if not self.empty_strings_allowed and val == '' and self.null:
    247257            val = None
     
    397407
    398408    def get_manipulator_new_data(self, new_data, rel=False):
    399409        date_field, time_field = self.get_manipulator_field_names('')
    400         #if rel:
    401         #    d = new_data.get(date_field, [None])[0]
    402         #    t = new_data.get(time_field, [None])[0]
    403         #else:
    404         d = new_data.get(date_field, None)
    405         t = new_data.get(time_field, None)
     410        if rel:
     411            d = new_data.get(date_field, [None])[0]
     412            t = new_data.get(time_field, [None])[0]
     413        else:
     414            d = new_data.get(date_field, None)
     415            t = new_data.get(time_field, None)
    406416        if d is not None and t is not None:
    407417            return datetime.datetime.combine(d, t)
    408418        return self.get_default()
     
    492502        upload_field_name = self.get_manipulator_field_names('')[0]
    493503        if new_data.get(upload_field_name, False):
    494504            func = getattr(new_object, 'save_%s_file' % self.name)
    495             func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"])
     505            if rel:
     506                func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"])
     507            else:
     508                func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"])
    496509
    497510    def get_directory_name(self):
    498511        return os.path.normpath(datetime.datetime.now().strftime(self.upload_to))
  • django/db/models/fields/related.py

     
    418418            kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
    419419
    420420        kwargs['rel'] = ManyToOne(to, to_field,
     421            num_in_admin=kwargs.pop('num_in_admin', 3),
     422            min_num_in_admin=kwargs.pop('min_num_in_admin', None),
     423            max_num_in_admin=kwargs.pop('max_num_in_admin', None),
     424            num_extra_on_change=kwargs.pop('num_extra_on_change', 1),
    421425            edit_inline=kwargs.pop('edit_inline', False),
    422426            related_name=kwargs.pop('related_name', None),
    423427            limit_choices_to=kwargs.pop('limit_choices_to', None),
     
    427431
    428432        self.db_index = True
    429433
    430         for name in ('num_in_admin', 'min_num_in_admin', 'max_num_in_admin', 'num_extra_on_change'):
    431             if name in kwargs:
    432                 self.deprecated_args.append(name)
    433 
    434434    def get_attname(self):
    435435        return '%s_id' % self.name
    436436
     
    501501            kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
    502502
    503503        kwargs['rel'] = OneToOne(to, to_field,
     504            num_in_admin=kwargs.pop('num_in_admin', 0),
    504505            edit_inline=kwargs.pop('edit_inline', False),
    505506            related_name=kwargs.pop('related_name', None),
    506507            limit_choices_to=kwargs.pop('limit_choices_to', None),
     
    511512
    512513        self.db_index = True
    513514
    514         for name in ('num_in_admin',):
    515             if name in kwargs:
    516                 self.deprecated_args.append(name)
    517 
    518515    def get_attname(self):
    519516        return '%s_id' % self.name
    520517
     
    534531    def __init__(self, to, **kwargs):
    535532        kwargs['verbose_name'] = kwargs.get('verbose_name', None)
    536533        kwargs['rel'] = ManyToMany(to, kwargs.pop('singular', None),
     534            num_in_admin=kwargs.pop('num_in_admin', 0),
    537535            related_name=kwargs.pop('related_name', None),
    538536            filter_interface=kwargs.pop('filter_interface', None),
    539537            limit_choices_to=kwargs.pop('limit_choices_to', None),
     
    542540        if kwargs["rel"].raw_id_admin:
    543541            kwargs.setdefault("validator_list", []).append(self.isValidIDList)
    544542        Field.__init__(self, **kwargs)
    545         for name in ('num_in_admin'):
    546             if name in kwargs:
    547                 self.deprecated_args.append(name)
    548543
    549544        if self.rel.raw_id_admin:
    550545            msg = gettext_lazy('Separate multiple IDs with commas.')
     
    641636        pass
    642637
    643638class ManyToOne:
    644     def __init__(self, to, field_name, edit_inline=False,
     639    def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
     640        max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
    645641        related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False):
    646642        try:
    647643            to._meta
    648         except AttributeError:
     644        except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
    649645            assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
    650646        self.to, self.field_name = to, field_name
    651         self.edit_inline = edit_inline
    652         self.related_name = related_name
     647        self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
     648        self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
     649        self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
    653650        self.limit_choices_to = limit_choices_to or {}
    654651        self.lookup_overrides = lookup_overrides or {}
    655652        self.raw_id_admin = raw_id_admin
     
    660657        return self.to._meta.get_field(self.field_name)
    661658
    662659class OneToOne(ManyToOne):
    663     def __init__(self, to, field_name, edit_inline=False,
     660    def __init__(self, to, field_name, num_in_admin=0, edit_inline=False,
    664661        related_name=None, limit_choices_to=None, lookup_overrides=None,
    665662        raw_id_admin=False):
    666663        self.to, self.field_name = to, field_name
    667         self.edit_inline = edit_inline
     664        self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
    668665        self.related_name = related_name
    669666        self.limit_choices_to = limit_choices_to or {}
    670667        self.lookup_overrides = lookup_overrides or {}
    671668        self.raw_id_admin = raw_id_admin
    672669        self.multiple = False
    673        
     670
    674671class ManyToMany:
    675     def __init__(self, to, singular=None, related_name=None,
     672    def __init__(self, to, singular=None, num_in_admin=0, related_name=None,
    676673        filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True):
    677674        self.to = to
    678675        self.singular = singular or None
     676        self.num_in_admin = num_in_admin
    679677        self.related_name = related_name
    680678        self.filter_interface = filter_interface
    681679        self.limit_choices_to = limit_choices_to or {}
  • django/db/models/related.py

     
    4141        """
    4242        return data # TODO
    4343
     44    def get_list(self, parent_instance=None):
     45        "Get the list of this type of object from an instance of the parent class."
     46        if parent_instance is not None:
     47            attr = getattr(parent_instance, self.get_accessor_name())
     48            if self.field.rel.multiple:
     49                # For many-to-many relationships, return a list of objects
     50                # corresponding to the xxx_num_in_admin options of the field
     51                objects = list(attr.all())
     52
     53                count = len(objects) + self.field.rel.num_extra_on_change
     54                if self.field.rel.min_num_in_admin:
     55                   count = max(count, self.field.rel.min_num_in_admin)
     56                if self.field.rel.max_num_in_admin:
     57                   count = min(count, self.field.rel.max_num_in_admin)
     58
     59                change = count - len(objects)
     60                if change > 0:
     61                    return objects + [None for _ in range(change)]
     62                if change < 0:
     63                    return objects[:change]
     64                else: # Just right
     65                    return objects
     66            else:
     67                # A one-to-one relationship, so just return the single related
     68                # object
     69                return [attr]
     70        else:
     71            return [None for _ in range(self.field.rel.num_in_admin)]
     72
    4473    def editable_fields(self):
    4574        "Get the fields in this class that should be edited inline."
    4675        return [f for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field]
     
    6291        over[self.field.name] = False
    6392        return self.opts.get_follow(over)
    6493
     94    def get_manipulator_fields(self, opts, manipulator, change, follow):
     95        if self.field.rel.multiple:
     96            if change:
     97                attr = getattr(manipulator.original_object, self.get_accessor_name())
     98                count = attr.count()
     99                count += self.field.rel.num_extra_on_change
     100                if self.field.rel.min_num_in_admin:
     101                    count = max(count, self.field.rel.min_num_in_admin)
     102                if self.field.rel.max_num_in_admin:
     103                    count = min(count, self.field.rel.max_num_in_admin)
     104            else:
     105                count = self.field.rel.num_in_admin
     106        else:
     107            count = 1
     108
     109        fields = []
     110        for i in range(count):
     111            for f in self.opts.fields + self.opts.many_to_many:
     112                if follow.get(f.name, False):
     113                    prefix = '%s.%d.' % (self.var_name, i)
     114                    fields.extend(f.get_manipulator_fields(self.opts, manipulator, change,
     115                                                           name_prefix=prefix, rel=True))
     116        return fields
     117
    65118    def __repr__(self):
    66119        return "<RelatedObject: %s related to %s>" % (self.name, self.field.name)
    67120
  • django/forms/__init__.py

     
    131131    def fill_inline_collections(self):
    132132        if not self._inline_collections:
    133133            ic = []
    134             children = self.manipulator.children.items()
    135             for rel_obj, child_manips  in children:
     134            related_objects = self.manipulator.get_related_objects()
     135            for rel_obj in related_objects:
    136136                data = rel_obj.extract_data(self.data)
    137                 inline_collection = InlineObjectCollection(self.manipulator, rel_obj, child_manips, data, self.error_dict)
     137                inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict)
    138138                ic.append(inline_collection)
    139139            self._inline_collections = ic
    140140
     
    213213
    214214class InlineObjectCollection:
    215215    "An object that acts like a sparse list of form field collections."
    216     def __init__(self, parent_manipulator, rel_obj,child_manips, data, errors):
     216    def __init__(self, parent_manipulator, rel_obj, data, errors):
    217217        self.parent_manipulator = parent_manipulator
    218218        self.rel_obj = rel_obj
    219219        self.data = data
    220220        self.errors = errors
    221         self.child_manips = child_manips
    222221        self._collections = None
    223222        self.name = rel_obj.name
    224223
     
    240239
    241240    def __iter__(self):
    242241        self.fill()
    243         return self._collections.values().__iter__()
     242        return iter(self._collections.values())
    244243
    245244    def items(self):
    246245        self.fill()
     
    250249        if self._collections:
    251250            return
    252251        else:
    253             #var_name = self.rel_obj.opts.object_name.lower()
    254             cols = {}
    255             #orig = hasattr(self.parent_manipulator, 'original_object') and self.parent_manipulator.original_object  or None
    256             #orig_list = self.rel_obj.get_list(orig)
     252            var_name = self.rel_obj.opts.object_name.lower()
     253            collections = {}
     254            orig = None
     255            if hasattr(self.parent_manipulator, 'original_object'):
     256                orig = self.parent_manipulator.original_object
     257            orig_list = self.rel_obj.get_list(orig)
    257258
    258             for i, manip in enumerate(self.child_manips) :
    259                 if manip and not manip.needs_deletion:
    260                     collection = {'original': manip.original_object}
    261                     for field in manip.fields:
    262                         errors = self.errors.get(field.field_name, [])
     259            for i, instance in enumerate(orig_list):
     260                collection = {'original': instance}
     261                for f in self.rel_obj.editable_fields():
     262                    for field_name in f.get_manipulator_field_names(''):
     263                        full_field_name = '%s.%d.%s' % (var_name, i, field_name)
     264                        field = self.parent_manipulator[full_field_name]
    263265                        data = field.extract_data(self.data)
    264                         last_part = field.field_name[field.field_name.rindex('.') + 1:]
    265                         collection[last_part] = FormFieldWrapper(field, data, errors)
     266                        errors = self.errors.get(full_field_name, [])
     267                        collection[field_name] = FormFieldWrapper(field, data, errors)
     268                collections[i] = FormFieldCollection(collection)
     269            self._collections = collections
    266270
    267                     cols[i] = FormFieldCollection(collection)
    268             self._collections = cols
    269271
    270272class FormField:
    271273    """Abstract class representing a form field.
  • django/core/management.py

     
    956956                except models.FieldDoesNotExist:
    957957                    e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
    958958
     959        # Check core=True, if needed.
     960        for related in opts.get_followed_related_objects():
     961            try:
     962                for f in related.opts.fields:
     963                    if f.core:
     964                        raise StopIteration
     965                e.add(related.opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (related.opts.object_name, opts.module_name, opts.object_name))
     966            except StopIteration:
     967                pass
     968
    959969        # Check unique_together.
    960970        for ut in opts.unique_together:
    961971            for field_name in ut:
  • django/contrib/admin/templates/admin/edit_inline_stacked.html

     
    1212            {% admin_field_line bound_field %}
    1313         {% endif %}
    1414      {% endfor %}
    15       <div class="item actions">
    16       <button class="deletebutton" name="command" value="{{bound_related_object.relation.var_name}}.{{fcw.index}}.delete">Delete</button>
    17       </div>
    1815    {% endfor %}
    19     <div class="collection actions">
    20     <button class="addbutton" name="command" value="{{bound_related_object.relation.var_name}}.add">Add</button>
    21         </div>
    22 </fieldset>
    23  No newline at end of file
     16</fieldset>
  • django/contrib/admin/templates/admin/edit_inline_tabular.html

     
    11{% load admin_modify %}
     2<fieldset class="module">
     3   <h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}</h2><table>
     4   <thead><tr>
     5   {% for fw in bound_related_object.field_wrapper_list %}
     6      {% if fw.needs_header %}
     7         <th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst }}</th>
     8      {% endif %}
     9   {% endfor %}
     10   {% for fcw in bound_related_object.form_field_collection_wrappers %}
     11      {% if change %}{% if original_row_needed %}
     12         {% if fcw.obj.original %}
     13            <tr class="row-label {% cycle row1,row2 %}"><td colspan="{{ num_headers }}"><strong>{{ fcw.obj.original }}</strong></tr>
     14         {% endif %}
     15      {% endif %}{% endif %}
     16      {% if fcw.obj.errors %}
     17         <tr class="errorlist"><td colspan="{{ num_headers }}">
     18            {{ fcw.obj.html_combined_error_list }}
     19         </tr>
     20      {% endif %}
     21      <tr class="{% cycle row1,row2 %}">
     22      {% for bound_field in fcw.bound_fields %}
     23         {% if not bound_field.hidden %}
     24         <td {{ bound_field.cell_class_attribute }}>
     25            {% field_widget bound_field %}
     26         </td>
     27         {% endif %}
     28      {% endfor %}
     29      {% if bound_related_object.show_url %}<td>
     30         {% if fcw.obj.original %}<a href="/r/{{ fcw.obj.original.content_type_id }}/{{ fcw.obj.original.id }}/">View on site</a>{% endif %}
     31      </td>{% endif %}
     32      </tr>
    233
    3 <fieldset class="module editinline">
    4     <h2>{{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}</h2>
    5         <table>
    6                 <thead>
    7                         <tr>
    8                             {% for fw in bound_related_object.field_wrapper_list %}
    9                                 {% if fw.needs_header %}
    10                                     <th{{ fw.header_class_attribute }}>{{ fw.field.verbose_name|capfirst }}</th>
    11                                 {% endif %}
    12                             {% endfor %}
    13                                 <th>&nbsp;</th>
    14                         </tr>
    15                 </thead>
    16                 <tfoot>
    17                         <tr>
    18                                 <td colspan='{{ num_headers }}'>
    19                                     <button class="addlink" name="command" value="{{ bound_related_object.relation.var_name }}.add">
    20                                         Add another {{ bound_related_object.relation.opts.verbose_name }}
    21                                     </button>
    22                                 </td>
    23                         </tr>
    24                 </tfoot>
    25                 <tbody>
    26             {% for fcw in bound_related_object.form_field_collection_wrappers %}
    27                 <tr class="{% cycle row1,row2 %}{% if fcw.obj.errors %} error{% endif %}">
    28                     {% for bound_field in fcw.bound_fields %}
    29                         {% if not bound_field.hidden %}
    30                             <td {{ bound_field.cell_class_attribute }}>
    31                                 {{ bound_field.html_error_list }}
    32                                 {% field_widget bound_field %}
    33                             </td>
    34                         {% endif %}
    35                     {% endfor %}
    36                     <td class="controls" >
    37                         <button class="inline-deletelink" name="command" value="{{ bound_related_object.relation.var_name }}.{{ fcw.index }}.delete">
    38                             Delete {{ bound_related_object.relation.opts.verbose_name }}
    39                         </button>
    40                     </td>
    41                 </tr>
    42             {% endfor %}
    43                 </tbody>
    44         </table>
    45 </fieldset>
    46  No newline at end of file
     34   {% endfor %} </table>
     35
     36   {% for fcw in bound_related_object.form_field_collection_wrappers %}
     37      {% for bound_field in fcw.bound_fields %}
     38         {% if bound_field.hidden %}
     39            {% field_widget bound_field %}
     40         {% endif %}
     41      {% endfor %}
     42   {% endfor %}
     43</fieldset>
Back to Top