我在Django管理员中有一个表格内联模型。我需要1个字段在创建后不可更改,但将其设置为readonly(通过readonly_fields),工作正常,但在单击“添加其他项目”而不是下拉列表时将字段变为标签。
有没有办法保持字段只读,但仍然允许使用正确的字段输入创建新项目?
谢谢!
托马斯
编辑:管理通过自定义小部件来计算出来
class ReadOnlySelectWidget(forms.Select):
def render(self, name, value, attrs=None):
if value:
final_attrs = self.build_attrs(attrs, name=name)
output = u'<input value="%s" type="hidden" %s />' % (value, flatatt(final_attrs))
return mark_safe(output + str(self.choices.queryset.get(id=value)))
else:
return super(ReadOnlySelectWidget, self).render(name, value, attrs)
如果有值,它只会将其变为隐藏,不会在每种情况下都有效(仅适用于1个只读字段)。
答案 0 :(得分:34)
遇到同样的问题,我遇到了这个问题:
创建两个内联对象,一个没有更改权限,另一个包含所有字段只读。包括在模型管理员中。
class SubscriptionInline(admin.TabularInline):
model = Subscription
extra = 0
readonly_fields = ['subscription', 'usedPtsStr', 'isActive', 'activationDate', 'purchaseDate']
def has_add_permission(self, request):
return False
class AddSupscriptionInline(admin.TabularInline):
model = Subscription
extra = 0
fields = ['subscription', 'usedPoints', 'isActive', 'activationDate', 'purchaseDate']
def has_change_permission(self, request, obj=None):
return False
# For Django Version > 2.1 there is a "view permission" that needs to be disabled too (https://docs.djangoproject.com/en/2.2/releases/2.1/#what-s-new-in-django-2-1)
def has_view_permission(self, request, obj=None):
return False
将它们包含在同一型号的管理员中:
class UserAdmin(admin.ModelAdmin):
inlines = [ AddSupscriptionInline, SubscriptionInline]
要添加新订阅,请使用管理员中的AddSubscriptionInline
。保存后,新订阅会从内联中消失,但现在确实显示在SubscriptionInline
中,只读。
对于SubscriptionInline
,提及extra = 0
非常重要,因此它不会显示垃圾只读订阅。
最好隐藏SubscriptionInline
的添加选项,只允许通过AddSubscriptionInline
添加,方法是将has_add_permission
设置为始终返回False
。
根本不完美,但它对我来说是最好的选择,因为我必须提供在用户管理页面上添加订阅的功能,但是在添加一个订阅后,它应该只通过内部应用程序逻辑进行更改。
答案 1 :(得分:2)
您可以仅使用一个内联来实现此目的,
class MyInline(admin.TabularInline):
fields = [...]
extra = 0
def has_change_permission(self, request, obj):
return False
答案 2 :(得分:1)
根据this post,此问题已被报告为Ticket15602中的错误。
解决方法是覆盖forms.py中内联模型的clean
方法,并在更改现有内联时引发错误:
class NoteForm(forms.ModelForm):
def clean(self):
if self.has_changed() and self.initial:
raise ValidationError(
'You cannot change this inline',
code='Forbidden'
)
return super().clean()
class Meta(object):
model = Note
fields='__all__'
以上给出了模型级别的解决方案。
要在更改特定字段时引发错误,clean_<field>
方法可以提供帮助。例如,如果该字段是名为ForeignKey
的{{1}}:
category
答案 3 :(得分:1)
我实际上遇到了另一个看起来效果很好的解决方案(对此我不敢恭维,但是link here)。
您可以在get_readonly_fields
上定义TabularInline
方法,并在有一个对象(编辑)与没有一个对象(创建)时适当地设置只读字段。
def get_readonly_fields(self, request, obj=None):
if obj is not None: # You may have to check some other attrs as well
# Editing an object
return ('field_name', )
else:
# Creating a new object
return ()
这具有以下作用:在编辑现有实例时将目标字段设置为只读,而在创建新实例时允许其可编辑。
正如下面的注释中指出的那样,这并没有达到预期的效果,因为传递的obj
实际上是父项……有一张旧的django票正在讨论这个here。 / p>
答案 4 :(得分:1)
此代码可以根据您的要求完美地工作。
实际上,我从我自己的问题中得到了这个答案,但具体针对我的问题,我删除了一些与我的问题有关的行。并归功于@YellowShark。 Check here my question。
创建新的内联之后,您将无法编辑现有的内联。
class XYZ_Inline(admin.TabularInline):
model = YourModel
class RequestAdmin(admin.ModelAdmin):
inlines = [XYZ_Inline, ]
# If you wanted to manipulate the inline forms, to make one of the fields read-only:
def get_inline_formsets(self, request, formsets, inline_instances, obj=None):
inline_admin_formsets = []
for inline, formset in zip(inline_instances, formsets):
fieldsets = list(inline.get_fieldsets(request, obj))
readonly = list(inline.get_readonly_fields(request, obj))
prepopulated = dict(inline.get_prepopulated_fields(request, obj))
inline_admin_formset = helpers.InlineAdminFormSet(
inline, formset, fieldsets, prepopulated, readonly,
model_admin=self,
)
if isinstance(inline, XYZ_Inline):
for form in inline_admin_formset.forms:
#Here we change the fields read only.
form.fields['some_fields'].widget.attrs['readonly'] = True
inline_admin_formsets.append(inline_admin_formset)
return inline_admin_formsets
您只能添加新的内联,并且只能读取所有现有的内联。
答案 5 :(得分:0)
这是我之前使用过的更好的只读小部件:
https://bitbucket.org/stephrdev/django-readonlywidget/
from django_readonlywidget.widgets import ReadOnlyWidget
class TestAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
field = super(TestAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if field:
field.widget = ReadOnlyWidget(db_field=db_field)
return field
答案 6 :(得分:0)
这可以通过猴子补丁来实现。
以下示例将使“note”字段仅对现有AdminNote对象进行读取。与其他答案中建议的隐藏转换字段不同,这实际上会从提交/验证工作流程中删除字段(这更安全,并使用现有的字段渲染器)。
#
# app/models.py
#
class Order(models.Model):
pass
class AdminNote(models.Model):
order = models.ForeignKey(Order)
time = models.DateTimeField(auto_now_add=True)
note = models.TextField()
#
# app/admin.py
#
import monkey_patches.admin_fieldset
...
class AdminNoteForm(forms.ModelForm):
class Meta:
model = AdminNote
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.get_readonly_fields():
del self.fields[field]
def get_readonly_fields(self):
if self.instance.pk:
return ['note']
return []
class AdminNoteInline(admin.TabularInline):
model = AdminNote
form = AdminNoteForm
extra = 1
fields = 'note', 'time'
readonly_fields = 'time',
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
inlines = AdminNoteInline,
#
# monkey_patches/admin_fieldset.py
#
import django.contrib.admin.helpers
class Fieldline(django.contrib.admin.helpers.Fieldline):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if hasattr(self.form, 'get_readonly_fields'):
self.readonly_fields = list(self.readonly_fields) + list(self.form.get_readonly_fields())
django.contrib.admin.helpers.Fieldline = Fieldline