Только для существующих элементов в админке Django. - PullRequest
19 голосов
/ 11 апреля 2011

У меня есть табличная встроенная модель в админке Django. Мне нужно, чтобы одно из полей не было изменяемым после того, как оно было создано, но установка его как доступного только для чтения (через readonly_fields), который отлично работает, но превращает поле в метку при нажатии «Добавить другой элемент» вместо выпадающего списка. *

Есть ли способ сохранить поле доступным только для чтения, но при этом разрешить создание новых элементов с помощью соответствующего ввода поля?

Спасибо!

Thomas

Редактировать: удалось выяснить это с помощью пользовательского виджета

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 полем только для чтения).

Ответы [ 6 ]

34 голосов
/ 26 января 2015

Имея ту же проблему, я наткнулся на это исправление:

Создайте два встроенных объекта, один без разрешения на изменение, а другой со всеми полями только для чтения.Включите оба в модель администратора.

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 голос
/ 08 апреля 2019

Этот код отлично работает в соответствии с вашими требованиями.

На самом деле я получил ответ на свой вопрос, но относящийся к моей проблеме, и удалил несколько строк, связанных с моей проблемой.И кредит идет на @YellowShark. Проверьте здесь мой вопрос .

После того, как вы создали новый inline, вы не сможете редактировать существующий inline.

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

Вы можете добавить только новый inline и прочитатьтолько все существующие inline.

1 голос
/ 21 февраля 2019

Я действительно наткнулся на другое решение, которое, кажется, работает очень хорошо (я не могу взять кредит на себя, но ссылка здесь ).

Вы можете определить метод 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, который обсуждает это здесь .

1 голос
/ 11 октября 2017

Согласно этой записи эта проблема была зарегистрирована как ошибка в Ticket15602 .

Обходной путь - переопределить метод clean встроенной модели в forms.py и вызвать ошибку при изменении существующего встроенного:

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 с именем 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 
0 голосов
/ 16 декабря 2014

Это возможно с патчем обезьяны.

В следующем примере поле «заметка» будет доступно только для чтения для существующих объектов 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
0 голосов
/ 25 октября 2011

Вот лучший виджет только для чтения, который я использовал раньше:
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
...