23đź‘Ť
Careful – “obj” is not the inline object, it’s the parent. That’s arguably a bug – see for example this Django ticket
6đź‘Ť
As a workaround to this issue I have associated a form and a Widget to my Inline:
admin.py:
...
class MasterCouponFileInline(admin.TabularInline):
model = MasterCouponFile
form = MasterCouponFileForm
extra = 0
in Django 2.0:
forms.py
from django import forms
from . import models
from feedback.widgets import DisablePopulatedText
class FeedbackCommentForm(forms.ModelForm):
class Meta:
model = models.MasterCouponFile
fields = ('Comment', ....)
widgets = {
'Comment': DisablePopulatedText,
}
in widgets.py
from django import forms
class DisablePopulatedText(forms.TextInput):
def render(self, name, value, attrs=None, renderer=None):
"""Render the widget as an HTML string."""
if value is not None:
# Just return the value, as normal read_only fields do
# Add Hidden Input otherwise the old fields are still required
HiddenInput = forms.HiddenInput()
return format_html("{}\n"+HiddenInput.render(name, value), self.format_value(value))
else:
return super().render(name, value, attrs, renderer)
older Django Versions:
forms.py
....
class MasterCouponFileForm(forms.ModelForm):
class Meta:
model = MasterCouponFile
def __init__(self, *args, **kwargs):
super(MasterCouponFileForm, self).__init__(*args, **kwargs)
self.fields['range'].widget = DisablePopulatedText(self.instance)
self.fields['quantity'].widget = DisablePopulatedText(self.instance)
in widgets.py
...
from django import forms
from django.forms.util import flatatt
from django.utils.encoding import force_text
class DisablePopulatedText(forms.TextInput):
def __init__(self, obj, attrs=None):
self.object = obj
super(DisablePopulatedText, self).__init__(attrs)
def render(self, name, value, attrs=None):
if value is None:
value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_text(self._format_value(value))
if "__prefix__" not in name and not value:
return format_html('<input{0} disabled />', flatatt(final_attrs))
else:
return format_html('<input{0} />', flatatt(final_attrs))
- Django admin enable sorting for calculated fields
- Django – Override admin site's login form
- Can not use celery delay for saved form: object is not JSON serializable
- Field choices() as queryset?
- Easy Way to Escape Django Template Variables
2đź‘Ť
This is still currently not easily doable due to the fact that obj is the parent model instance not the instance displayed by the inline.
What I did in order to solve this, was to make all the fields, in the inline form, read only and provide a Add/Edit link to a ChangeForm for the inlined model.
Like this
class ChangeFormLinkMixin(object):
def change_form_link(self, instance):
url = reverse('admin:%s_%s_change' % (instance._meta.app_label,
instance._meta.module_name), args=(instance.id,))
# Id == None implies and empty inline object
url = url.replace('None', 'add')
command = _('Add') if url.find('add') > -1 else _('Edit')
return format_html(u'<a href="{}">%s</a>' % command, url)
And then in the inline I will have something like this
class ItemInline(ChangeFormLinkMixin, admin.StackedInline):
model = Item
extra = 5
readonly_fields = ['field1',...,'fieldN','change_form_link']
Then in the ChangeForm I’ll be able to control the changes the way I want to (I have several states, each of them with a set of editable fields associated).
- Django template object type
- Saving a Pandas DataFrame to a Django Model
- Django datetime field – convert to timezone in view
- How to make a rest_framework Serializer disallow superfluous fields?
- Is there such a thing for Django as there is Heroku for Ruby on Rails
1đź‘Ť
As others have added, this is a design flaw in django as seen in this Django ticket (thanks Danny W). get_readonly_fields
returns the parent object, which is not what we want here.
Since we can’t make it readonly, here is my solution to validate it can’t be set by the form, using a formset and a clean method:
class ItemInline(admin.TabularInline):
model = Item
formset = ItemInlineFormset
class ItemInlineFormset(forms.models.BaseInlineFormSet):
def clean(self):
super(ItemInlineFormset, self).clean()
for form in self.forms:
if form.instance.some_condition:
form.add_error('some_condition', 'Nope')
- Django docker – could not translate host name "db" to address: nodename nor servname provided, or not known
- Django – Function inside a model. How to call it from a view?
-2đź‘Ť
You are on the right track. Update self.readonly_fields with a tuple of what fields you want to set as readonly.
class ItemInline(admin.TabularInline):
model = Item
extra = 5
def get_readonly_fields(self, request, obj=None):
# add a tuple of readonly fields
self.readonly_fields += ('field_a', 'field_b')
return self.readonly_fields
- Django bootstrap alerts not working as expected
- ALLOWED_HOSTS and Django
- Disable pylint warning E1101 when using enums
- Does a library to prevent duplicate form submissions exist for django?
- Django form field grouping