Allowing Edit to editable=False Fields in Django A

2019-02-15 09:30发布

DRF will use the editable=False on a field to default the Serializer to read-only. This is a very helpful / safe default that I take advantage of (ie I won't forget to set the Serializer to read-only). That being said once I have set editable=False is there any way to then force the Django admin to allow editing one of those fields?

Presumably the admin is a super user and I do want him to be able to change the fields value but fore safety I want the default Serializer logic to be read only.

UPDATE

I don't actually need to be able to edit the field as much as "set-it" when I create the object.

2条回答
贪生不怕死
2楼-- · 2019-02-15 09:50

For those who want to allow editing of a non-editabled field only during creation (no instance.pk, yet):

# models.py
class Entity(Model):
    name = CharField(max_length=200, unique=True, null=False, blank=False, editable=False)

# admin.py
@register(Entity)
class EntityAdmin(ModelAdmin):
    def get_readonly_fields(self, request, obj=None):
        if obj:  # This is the case when obj is already created i.e. it's an edit
            return ['id', 'name']
        else:
            return []

    # this override prevents that the new_name field shows up in the change form if it's not a creation
    def get_form(self, request, obj=None, **kwargs):
        orig_self_form = self.form
        if not obj:
            self.form = CreateEntityForm
        result = super().get_form(request, obj=obj, **kwargs)
        self.form = orig_self_form
        return result

# forms.py
class CreateEntityForm(ModelForm):
    new_name = CharField(max_length=200, min_length=2, label='Name', required=True)

    def clean_new_name(self):
        code = self.cleaned_data['new_name']
        # validate uniqueness - if you need
        exists = Entity.objects.filter(name=code).first()
        if exists:
            raise ValidationError('Entity with this name already exists: {}', exists)
        return name

    def save(self, commit=True):
        if self.instance.pk:
            raise NotImplementedError('Editing of existing Entity is not allowed!')

        self.instance.name = self.cleaned_data['new_name'].upper()
        return super().save(commit)

    class Meta:
        model = Entity
        fields = ['new_name']
        exclude = ['id', 'name']
查看更多
Root(大扎)
3楼-- · 2019-02-15 10:12

You are going about this the wrong way.

Your models should be the most pure implementation of the things you are modelling. If something about a model is fixed (for example a creation date) it shouldn't be editable in the model, if its mutable, then leave as editable in the model.

Otherwise, in the future you (or someone else) might be stuck wondering why a field which is set to editable=False is some how being changed. Especially as the documentation states:

If False, the field will not be displayed in the admin or any other ModelForm. They are also skipped during model validation.

If you have one view in which it shouldn't be editable (such as in the API), then override it there.

If you have multiple serilaizers for a model, instead make an abstract serializer with a read_only_fields set and then subclass that. For example:

class AbstractFooSerializer(serializers.ModelSerializer):
    class Meta:
        model = Foo
        read_only_fields = ('bar',)


class MainFooSerializer(AbstractFooSerializer):
    pass

class DifferentFooSerializer(AbstractFooSerializer):
    pass

If you really, really want to use editable=False, but allow the item to be edited in the Admin site only on creation you have an up hill battle.

Probably the best approach would be to reimplement the AdminForm you are using for the Admin

So instead of:

class FooAdmin(admin.ModelAdmin):

Use:

class FooAdmin(admin.ModelAdmin):
    form = MySpecialForm

Then declare the form:

class MySpecialForm(forms.Model):
    def __init__(self, *args, **kwargs):
        self.is_new = False
        if kwargs.get('instance',None) is None:
            # There is no instance, thus its a new item
            self.is_new = True
            self.fields['one_time_field'] = forms.CharField() # Or what have you.
        super(MySpecialForm, self).__init__(*args, **kwargs)

    def save(self, commit=True):
         instance = super(MySpecialForm, self).save(commit)
         if self.is_new:
             instance.your_one_time_only_field = self.one_time_field
             instance.save()
         return instance

Note: you will need to manually add a field and save each readonly field that you want to do this for. This may or may not be 100% functional.

查看更多
登录 后发表回答