| 99 | | class InlineOptions(object): |
|---|
| 100 | | """ |
|---|
| 101 | | Options for inline editing of ``model`` instances. |
|---|
| 102 | | |
|---|
| 103 | | Provide ``name`` to specify the attribute name of the ``ForeignKey`` from |
|---|
| 104 | | ``model`` to its parent. This is required if ``model`` has more than one |
|---|
| 105 | | ``ForeignKey`` to its parent. |
|---|
| 106 | | """ |
|---|
| 107 | | def __init__(self, model, name=None, extra=3, fields=None, template=None, formfield_callback=lambda f: f.formfield()): |
|---|
| 108 | | self.model = model |
|---|
| 109 | | self.name = name |
|---|
| 110 | | self.extra = extra |
|---|
| 111 | | self.fields = fields |
|---|
| 112 | | self.template = template or self.default_template |
|---|
| 113 | | self.verbose_name = model._meta.verbose_name |
|---|
| 114 | | self.verbose_name_plural = model._meta.verbose_name_plural |
|---|
| 115 | | self.prepopulated_fields = {} |
|---|
| 116 | | self.formfield_callback = formfield_callback |
|---|
| 117 | | |
|---|
| 118 | | class StackedInline(InlineOptions): |
|---|
| 119 | | default_template = 'admin/edit_inline_stacked.html' |
|---|
| 120 | | |
|---|
| 121 | | class TabularInline(InlineOptions): |
|---|
| 122 | | default_template = 'admin/edit_inline_tabular.html' |
|---|
| 123 | | |
|---|
| 124 | | class BoundInline(object): |
|---|
| 125 | | def __init__(self, opts, formset): |
|---|
| 126 | | self.opts = opts |
|---|
| 127 | | self.formset = formset |
|---|
| 128 | | |
|---|
| 129 | | def __iter__(self): |
|---|
| 130 | | for form, original in zip(self.formset.change_forms, self.formset.get_inline_objects()): |
|---|
| 131 | | yield BoundInlineObject(form, original, self.opts) |
|---|
| 132 | | for form in self.formset.add_forms: |
|---|
| 133 | | yield BoundInlineObject(form, None, self.opts) |
|---|
| 134 | | |
|---|
| 135 | | def fields(self): |
|---|
| 136 | | # HACK: each form instance has some extra fields. Getting those fields |
|---|
| 137 | | # from the form class will take some rearranging. Get them from the |
|---|
| 138 | | # first form instance for now. |
|---|
| 139 | | return list(self.formset.forms[0]) |
|---|
| 140 | | |
|---|
| 141 | | def verbose_name(self): |
|---|
| 142 | | return self.opts.verbose_name |
|---|
| 143 | | |
|---|
| 144 | | def verbose_name_plural(self): |
|---|
| 145 | | return self.opts.verbose_name_plural |
|---|
| 146 | | |
|---|
| 147 | | class BoundInlineObject(object): |
|---|
| 148 | | def __init__(self, form, original, opts): |
|---|
| 149 | | self.opts = opts |
|---|
| 150 | | self.base_form = form |
|---|
| 151 | | self.form = AdminForm(form, self.fieldsets(), opts.prepopulated_fields) |
|---|
| 152 | | self.original = original |
|---|
| 153 | | |
|---|
| 154 | | def fieldsets(self): |
|---|
| 155 | | """ |
|---|
| 156 | | Generator that yields Fieldset objects for use on add and change admin |
|---|
| 157 | | form pages. |
|---|
| 158 | | |
|---|
| 159 | | This default implementation looks at self.fields, but subclasses can |
|---|
| 160 | | override this implementation and do something special based on the |
|---|
| 161 | | given HttpRequest object. |
|---|
| 162 | | """ |
|---|
| 163 | | if self.opts.fields is None: |
|---|
| 164 | | default_fields = [f for f in self.base_form.fields] |
|---|
| 165 | | yield Fieldset(fields=default_fields) |
|---|
| 166 | | else: |
|---|
| 167 | | for name, options in self.opts.fields: |
|---|
| 168 | | yield Fieldset(name, options['fields'], classes=options.get('classes', '').split(' '), description=options.get('description')) |
|---|
| 169 | | |
|---|
| 170 | | class ModelAdmin(object): |
|---|
| | 99 | class BaseModelAdmin(object): |
|---|
| | 100 | """Functionality common to both ModelAdmin and InlineAdmin.""" |
|---|
| | 101 | raw_id_fields = () |
|---|
| | 102 | fields = None |
|---|
| | 103 | |
|---|
| | 104 | def formfield_for_dbfield(self, db_field, **kwargs): |
|---|
| | 105 | """ |
|---|
| | 106 | Hook for specifying the form Field instance for a given database Field |
|---|
| | 107 | instance. |
|---|
| | 108 | |
|---|
| | 109 | If kwargs are given, they're passed to the form Field's constructor. |
|---|
| | 110 | """ |
|---|
| | 111 | # For ManyToManyFields with a filter interface, use a special Widget. |
|---|
| | 112 | if isinstance(db_field, models.ManyToManyField) and db_field.name in (self.filter_vertical + self.filter_horizontal): |
|---|
| | 113 | kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical)) |
|---|
| | 114 | return db_field.formfield(**kwargs) |
|---|
| | 115 | |
|---|
| | 116 | # For DateTimeFields, use a special field and widget. |
|---|
| | 117 | if isinstance(db_field, models.DateTimeField): |
|---|
| | 118 | return forms.SplitDateTimeField(required=not db_field.blank, |
|---|
| | 119 | widget=widgets.AdminSplitDateTime(), label=capfirst(db_field.verbose_name), |
|---|
| | 120 | help_text=db_field.help_text, **kwargs) |
|---|
| | 121 | |
|---|
| | 122 | # For DateFields, add a custom CSS class. |
|---|
| | 123 | if isinstance(db_field, models.DateField): |
|---|
| | 124 | kwargs['widget'] = forms.TextInput(attrs={'class': 'vDateField', 'size': '10'}) |
|---|
| | 125 | return db_field.formfield(**kwargs) |
|---|
| | 126 | |
|---|
| | 127 | # For TimeFields, add a custom CSS class. |
|---|
| | 128 | if isinstance(db_field, models.TimeField): |
|---|
| | 129 | kwargs['widget'] = forms.TextInput(attrs={'class': 'vTimeField', 'size': '8'}) |
|---|
| | 130 | return db_field.formfield(**kwargs) |
|---|
| | 131 | |
|---|
| | 132 | # For ForeignKey or ManyToManyFields, use a special widget. |
|---|
| | 133 | if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)): |
|---|
| | 134 | if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields: |
|---|
| | 135 | kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel) |
|---|
| | 136 | return db_field.formfield(**kwargs) |
|---|
| | 137 | else: |
|---|
| | 138 | # Wrap the widget's render() method with a method that adds |
|---|
| | 139 | # extra HTML to the end of the rendered output. |
|---|
| | 140 | formfield = db_field.formfield(**kwargs) |
|---|
| | 141 | formfield.widget.render = widgets.RelatedFieldWidgetWrapper(formfield.widget.render, db_field.rel) |
|---|
| | 142 | return formfield |
|---|
| | 143 | |
|---|
| | 144 | # For any other type of field, just call its formfield() method. |
|---|
| | 145 | return db_field.formfield(**kwargs) |
|---|
| | 146 | |
|---|
| | 147 | class ModelAdmin(BaseModelAdmin): |
|---|
| 277 | | |
|---|
| 278 | | def formfield_for_dbfield(self, db_field, **kwargs): |
|---|
| 279 | | """ |
|---|
| 280 | | Hook for specifying the form Field instance for a given database Field |
|---|
| 281 | | instance. |
|---|
| 282 | | |
|---|
| 283 | | If kwargs are given, they're passed to the form Field's constructor. |
|---|
| 284 | | """ |
|---|
| 285 | | # For ManyToManyFields with a filter interface, use a special Widget. |
|---|
| 286 | | if isinstance(db_field, models.ManyToManyField) and db_field.name in (self.filter_vertical + self.filter_horizontal): |
|---|
| 287 | | kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical)) |
|---|
| 288 | | return db_field.formfield(**kwargs) |
|---|
| 289 | | |
|---|
| 290 | | # For DateTimeFields, use a special field and widget. |
|---|
| 291 | | if isinstance(db_field, models.DateTimeField): |
|---|
| 292 | | return forms.SplitDateTimeField(required=not db_field.blank, |
|---|
| 293 | | widget=widgets.AdminSplitDateTime(), label=capfirst(db_field.verbose_name), |
|---|
| 294 | | help_text=db_field.help_text, **kwargs) |
|---|
| 295 | | |
|---|
| 296 | | # For DateFields, add a custom CSS class. |
|---|
| 297 | | if isinstance(db_field, models.DateField): |
|---|
| 298 | | kwargs['widget'] = forms.TextInput(attrs={'class': 'vDateField', 'size': '10'}) |
|---|
| 299 | | return db_field.formfield(**kwargs) |
|---|
| 300 | | |
|---|
| 301 | | # For TimeFields, add a custom CSS class. |
|---|
| 302 | | if isinstance(db_field, models.TimeField): |
|---|
| 303 | | kwargs['widget'] = forms.TextInput(attrs={'class': 'vTimeField', 'size': '8'}) |
|---|
| 304 | | return db_field.formfield(**kwargs) |
|---|
| 305 | | |
|---|
| 306 | | # For ForeignKey or ManyToManyFields, use a special widget. |
|---|
| 307 | | if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)): |
|---|
| 308 | | if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields: |
|---|
| 309 | | kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel) |
|---|
| 310 | | return db_field.formfield(**kwargs) |
|---|
| 311 | | else: |
|---|
| 312 | | # Wrap the widget's render() method with a method that adds |
|---|
| 313 | | # extra HTML to the end of the rendered output. |
|---|
| 314 | | formfield = db_field.formfield(**kwargs) |
|---|
| 315 | | formfield.widget.render = widgets.RelatedFieldWidgetWrapper(formfield.widget.render, db_field.rel) |
|---|
| 316 | | return formfield |
|---|
| 317 | | |
|---|
| 318 | | # For any other type of field, just call its formfield() method. |
|---|
| 319 | | return db_field.formfield(**kwargs) |
|---|
| | 617 | |
|---|
| | 618 | class InlineModelAdmin(BaseModelAdmin): |
|---|
| | 619 | """ |
|---|
| | 620 | Options for inline editing of ``model`` instances. |
|---|
| | 621 | |
|---|
| | 622 | Provide ``name`` to specify the attribute name of the ``ForeignKey`` from |
|---|
| | 623 | ``model`` to its parent. This is required if ``model`` has more than one |
|---|
| | 624 | ``ForeignKey`` to its parent. |
|---|
| | 625 | """ |
|---|
| | 626 | def __init__(self, model, name=None, extra=3, fields=None, template=None): |
|---|
| | 627 | self.model = model |
|---|
| | 628 | self.opts = model._meta |
|---|
| | 629 | self.name = name |
|---|
| | 630 | self.extra = extra |
|---|
| | 631 | self.fields = fields |
|---|
| | 632 | self.template = template or self.default_template |
|---|
| | 633 | self.verbose_name = model._meta.verbose_name |
|---|
| | 634 | self.verbose_name_plural = model._meta.verbose_name_plural |
|---|
| | 635 | self.prepopulated_fields = {} |
|---|
| | 636 | |
|---|
| | 637 | class StackedInline(InlineModelAdmin): |
|---|
| | 638 | default_template = 'admin/edit_inline_stacked.html' |
|---|
| | 639 | |
|---|
| | 640 | class TabularInline(InlineModelAdmin): |
|---|
| | 641 | default_template = 'admin/edit_inline_tabular.html' |
|---|
| | 642 | |
|---|
| | 643 | class BoundInline(object): |
|---|
| | 644 | def __init__(self, inline_admin, formset): |
|---|
| | 645 | self.inline_admin = inline_admin |
|---|
| | 646 | self.formset = formset |
|---|
| | 647 | self.template = inline_admin.template |
|---|
| | 648 | |
|---|
| | 649 | def __iter__(self): |
|---|
| | 650 | for form, original in zip(self.formset.change_forms, self.formset.get_inline_objects()): |
|---|
| | 651 | yield BoundInlineObject(form, original, self.inline_admin) |
|---|
| | 652 | for form in self.formset.add_forms: |
|---|
| | 653 | yield BoundInlineObject(form, None, self.inline_admin) |
|---|
| | 654 | |
|---|
| | 655 | def fields(self): |
|---|
| | 656 | # HACK: each form instance has some extra fields. Getting those fields |
|---|
| | 657 | # from the form class will take some rearranging. Get them from the |
|---|
| | 658 | # first form instance for now. |
|---|
| | 659 | return list(self.formset.forms[0]) |
|---|
| | 660 | |
|---|
| | 661 | def verbose_name(self): |
|---|
| | 662 | return self.inline_admin.verbose_name |
|---|
| | 663 | |
|---|
| | 664 | def verbose_name_plural(self): |
|---|
| | 665 | return self.inline_admin.verbose_name_plural |
|---|
| | 666 | |
|---|
| | 667 | class BoundInlineObject(object): |
|---|
| | 668 | def __init__(self, form, original, inline_admin): |
|---|
| | 669 | self.inline_admin = inline_admin |
|---|
| | 670 | self.base_form = form |
|---|
| | 671 | self.form = AdminForm(form, self.fieldsets(), inline_admin.prepopulated_fields) |
|---|
| | 672 | self.original = original |
|---|
| | 673 | |
|---|
| | 674 | def fieldsets(self): |
|---|
| | 675 | """ |
|---|
| | 676 | Generator that yields Fieldset objects for use on add and change admin |
|---|
| | 677 | form pages. |
|---|
| | 678 | |
|---|
| | 679 | This default implementation looks at self.fields, but subclasses can |
|---|
| | 680 | override this implementation and do something special based on the |
|---|
| | 681 | given HttpRequest object. |
|---|
| | 682 | """ |
|---|
| | 683 | if self.inline_admin.fields is None: |
|---|
| | 684 | default_fields = [f for f in self.base_form.fields] |
|---|
| | 685 | yield Fieldset(fields=default_fields) |
|---|
| | 686 | else: |
|---|
| | 687 | for name, options in self.opts.fields: |
|---|
| | 688 | yield Fieldset(name, options['fields'], classes=options.get('classes', '').split(' '), description=options.get('description')) |
|---|