tipos - relaciones entre modelos django
Solo lectura para artículos existentes solo en Django admin en línea (5)
Aquí hay un mejor widget de solo lectura que he usado antes:
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
Tengo un modelo tabular en línea en el administrador de Django. Necesito que 1 de los campos no se pueda cambiar después de que se haya creado, pero configurándolo como de solo lectura (a través de readonly_fields) que funciona bien, pero convierte el campo en una etiqueta al hacer clic en "Agregar otro elemento" en lugar de un menú desplegable.
¿Hay una manera de mantener un campo de lectura solo, pero aún así permitir que se creen nuevos elementos con la entrada de campo adecuada?
¡Gracias!
Thomas
Edición: Gestionado para resolverlo a través de un widget personalizado
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)
Solo lo convierte en un valor oculto si hay un valor, no funcionará en todas las situaciones (solo funciona realmente con 1 campo de solo lectura).
De acuerdo con este post, este problema ha sido reportado como un error en Ticket15602 .
Una solución alternativa sería anular el método de clean
del modelo en línea en forms.py y generar un error cuando se cambia una en línea existente:
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__''
Lo anterior da una solución a nivel de modelo.
Para generar un error cuando se cambia un campo específico, el clean_<field>
puede ayudar. Por ejemplo, si el campo es un ForeignKey
llamado category
:
class MyModelForm(forms.Form):
pass # Several lines of code here for the needs of the Model Form
# The following form will be called from the admin inline class only
class MyModelInlineForm(MyModelForm):
def clean_category(self):
category = self.cleaned_data.get(''category'', None)
initial_value = getattr(
self.fields.get(''category'', None),
''initial'',
None
)
if all(
(
self.has_changed(),
category.id != initial_value,
)
):
raise forms.ValidationError(
_(''You cannot change this''),
code=''Forbidden''
)
return category
class Meta:
# Copy here the Meta class of the parent model
De hecho, me encontré con otra solución que parece funcionar muy bien (no puedo reconocerlo, pero enlace aquí ).
Puede definir el método get_readonly_fields
en su TabularInline
y configurar los campos de solo lectura adecuadamente cuando hay un objeto (edición) frente a cuando no hay uno (creación).
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 ()
Esto tiene el efecto de hacer que su campo de destino sea de solo lectura cuando está editando una instancia de salida, al tiempo que permite que sea editable al crear una nueva instancia.
Como se señala a continuación en el comentario, esto no funciona como se esperaba porque el objetivo que se aprobó es en realidad el principal ... Hay un antiguo ticket de django que trata esto Ticket15602 .
Esto es posible con un parche de mono.
El siguiente ejemplo hará que el campo "nota" se lea solo para los objetos AdminNote existentes. A diferencia de los campos de conversión que se ocultan, como se sugiere en otras respuestas, esto eliminará los campos del flujo de trabajo de envío / validación (que es más seguro y utiliza representadores de campo existentes).
#
# 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
Teniendo el mismo problema, me encontré con esta solución:
Cree dos objetos en línea, uno sin permiso de cambio y el otro con todos los campos de solo lectura. Incluir ambos en el modelo de administrador.
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
Inclúyelos en el mismo modelo de administrador:
class UserAdmin(admin.ModelAdmin):
inlines = [ AddSupscriptionInline, SubscriptionInline]
Para agregar una nueva suscripción, uso el AddSubscriptionInline
en el administrador. Una vez que se guarda, la nueva suscripción desaparece de esa línea, pero ahora aparece en la SubscriptionInline
, como solo lectura.
Para SubscriptionInline
, es importante mencionar extra = 0
, por lo que no mostrará suscripciones de solo lectura no deseadas. También es mejor ocultar la opción de agregar para SubscriptionInline
, para permitir agregar solo a través de AddSubscriptionInline
, configurando el has_add_permission
para devolver siempre False
.
No es perfecto en absoluto, pero es la mejor opción para mí, ya que debo proporcionar la posibilidad de agregar suscripciones en la página de administración de usuarios, pero después de agregar una, solo se debe cambiar a través de la lógica interna de la aplicación.