简体   繁体   中英

How to make a readonly field that can not be edited by user in a Django forms?

I'm trying to create my first own project in python django. What I'v done so far is a simple shop. When I click this:

item_details.html

<a href="{% url 'buy_item' pk=item.pk %}" class="submit">Kup teraz</a>

I will be redirected

path('buy/<int:pk>', ItemCreate.as_view(), name='buy_item')

To this form:

class BuyForm(forms.ModelForm):

    class Meta:
        model = Order
        fields = (
        'item',
        'delivery',
        'price',
        'buyer',)

With this view:

class ItemCreate(CreateView):
    model = Order
    fields = ['item', 'price', 'buyer', 'delivery']

    def get_initial(self):
        item = get_object_or_404(Item, pk=self.kwargs['pk'])
        self.initial.update({
            'buyer': self.request.user,
            'price': item.price,
            'item': item.pk,
        })
        return super(ItemCreate, self).get_initial()

from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User

class Category(models.Model):
    title = models.CharField(max_length=100)
    description = models.TextField()

    def __str__(self):
        return self.title
class Item(models.Model):
    seller = models.ForeignKey(User, on_delete=models.CASCADE)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
    price = models.FloatField(null=False, blank=False)
    img = models.ImageField(blank=True, null=True,
        upload_to='covers/%Y/%m/%D/')
    published_date = models.DateTimeField(default=timezone.now)
    def __str__(self):
        return self.title

class Supplier(models.Model):
    title = models.CharField(max_length=100)
    def __str__(self):
        return self.title

class Delivery(models.Model):
    title = models.ForeignKey(Supplier, on_delete=models.CASCADE)
    price_of_delivery = models.FloatField(null=False, blank=False)
    def __str__(self):
        return self.title

class Order(models.Model):
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    delivery = models.ForeignKey(Delivery, on_delete=models.CASCADE)
    order_date = models.DateTimeField(default=timezone.now)
    buyer = models.ForeignKey(User, on_delete=models.CASCADE)
    price = models.FloatField(null=False, blank=False)
    def __str__(self):
        return self.item

Because the initial data shouldn't be edited by user, I'd like to make them readonly.

What I've tried so far is:

class BuyForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
       super(BuyForm, self).__init__(*args, **kwargs)
       self.fields['buyer'].widget.attrs['readonly'] = True    

    class Meta:
        model = Order
        fields = (
        'item',
        'delivery',
        'price',
        'buyer',)

No errors in my console but also there's no results.

My order_form.html looks that:

{% extends 'shop/base.html' %}
{% block content %}

<div align="center">
    <h1>Buy item</h1>
    <form method="POST" class="post-form">{% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="save btn btn-default">Buy</button>
    </form>
</div>
<
{% endblock %}

What should I do to make "buyer", "price" and the "item" readonly?

All other answers are great and correct, but that didn't solve the problem when I created your example.

Actual problem is that your view never use your form in a first place - that's why overriding attributes did nothing!

More about class-based views with custom forms from django docs

views.py

from django.shortcuts import get_object_or_404
from django.views.generic import CreateView
from .models import Item
from .forms import BuyForm

class ItemCreate(CreateView):
    form_class = BuyForm
    template_name = 'order_form.html'
    success_url = '/thanks/'

    def get_initial(self):
        item = get_object_or_404(Item, pk=self.kwargs['pk'])
        self.initial.update({
            'buyer': self.request.user.id,
            'price': item.price,
            'item': item.pk,
        })
        return super(ItemCreate, self).get_initial()

forms.py

from django import forms
from .models import Order


class BuyForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(BuyForm, self).__init__(*args, **kwargs)
        self.fields['buyer'].disabled = True

    class Meta:
        model = Order
        fields = (
            'item',
            'delivery',
            'price',
            'buyer',)

try this:

class BuyForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
       super(BuyForm, self).__init__(*args, **kwargs)
       self.fields['buyer'].required = False
       self.fields['buyer'].widget.attrs['disabled'] = "disabled" 

       ...

    def clean_buyer(self):
        instance = getattr(self, 'instance', None)
        if instance:
            return instance.buyer
        else:
            return self.cleaned_data.get('buyer', None)

but the filed with disabled attr will post blank data. That why we override the clean method to set the field's value to be what was originally in the instance.

What you are looking for is a formfield's disabled attribute:
https://docs.djangoproject.com/en/2.2/ref/forms/fields/#disabled

Another approach is to make them hidden fields. Add this to the Meta...

class Meta:
    widgets = {
        'buyer': forms.HiddenInput(),
        'price': forms.HiddenInput(),
        'item': forms.HiddenInput(),
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM