Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django alter model data before rendering ModelForm

Tags:

python

django

I'm trying to generate an object that allows values to be stored in a standard unit, but displayed in different units based on the settings for the project. Below is an example where I need to be able to store the temperature of a room. I want the database value to be in Kelvin for ease of calulations, but want the users to be able to view/enter in F or C as the project requires.

The model can take the values from a form and convert them to Kelvin on save. I know this can be done in the form, but I have it here for other data entry methods. A striped down version of the model is as follows:

class Room(models.Model):
    name                = models.CharField(max_length=255)            
    temperature         = models.FloatField(default=293.15)

    def save(self, *args, **kwargs):
        self.temperature = convert.temperature(self.temperature, 
            from_unit=self.plant.temperature_scale)
        super(Room, self).save(*args, **kwargs)

I created a ModelForm to use in CreateView and UpdateView. I needed the ModelForm to limit the queryset for other fields (which works fine). Here I was able to convert the temperature field to the desired units for a new object in CreateView, but I can not seem to find the way to pull this off for an existing record

class RoomForm(ModelForm):
    def __init__(self, plant, *args, **kwargs):
        super (RoomForm,self ).__init__(*args,**kwargs)

        self.fields['temperature'].initial = convert.temperature(
            value=self.fields['temperature'].initial, 
            from_unit='K',
            to_unit=self.fields['unit'].queryset[0].plant.temperature_scale)

        self.instance.temperature = convert.temperature(
            value=self.instance.temperature, 
            from_unit='K',
            to_unit=self.fields['unit'].queryset[0].plant.temperature_scale)

    class Meta:
        model = Room
        fields = ['name', 'temperature']

For good measure, here are the views that call this form.

class NewRoom(CreateView):
    model = Room
    form_class = RoomForm

    def get_form_kwargs(self, **kwargs):
        form_kwargs = super(NewRoom, self).get_form_kwargs(**kwargs)
        form_kwargs["plant"] = Plant.objects.get(slug=self.kwargs['plant'])
        return form_kwargs    

class EditRoom(UpdateView):
    model = Room
    form_class = RoomForm
    template_name_suffix = "_update_form"

    def get_form_kwargs(self, **kwargs):
        form_kwargs = super(EditRoom, self).get_form_kwargs(**kwargs)
        form_kwargs["plant"] = Plant.objects.filter(slug=self.kwargs['plant'])
        return form_kwargs

In the templates (not forms) I use this to display the temperature correctly.

{{ room.temperature|convert_temperature:room.plant.temperature_scale }}

I assume I need to supplement the part where the ModelForm imports the data from the database record, but I could not identify where this happens in the django.forms source code.

I tried calling this manually in the shell

>>> from rooms.models import *
>>> from rooms.forms import *
>>> room = Room.objects.first()
>>> form = RoomForm(room.plant, instance=room)
>>> form.instance.temperature
20.0 
>>> form.as_p()

and the rendered form is as follows with the incorrect value for temperature

<p>
    <label for="id_name">Name:
    </label> 
    <input id="id_name" maxlength="255" name="name" type="text" value="Testing" 
        required />
</p>\n
<p>
    <label for="id_temperature">Temperature:
    </label> 
    <input id="id_temperature" name="temperature" 
        step="any" type="number" value="293.15" required /> 
</p>\n

I'm using Django version 1.10.3 if it's possible this is a bug.

like image 958
Cohan Avatar asked Oct 19 '25 17:10

Cohan


2 Answers

While lucasnadalutti let me in the right spot, it did not manage to do the trick. Overriding the instance passed to the ModelForm.__init__ and calling it again after altering.

class RoomForm(ModelForm):

    def __init__(self, plant, *args, **kwargs):
        super(RoomForm, self).__init__(*args,**kwargs)

        self.instance.temperature = convert.temperature(
            value=self.instance.temperature,
            from_unit='K', to_unit=self.fields['unit'].queryset[0].plant.temperature_scale)

        # Override instance passed to __init__ and call super again
        kwargs['instance'] = self.instance
        super(RoomForm, self).__init__(*args, **kwargs)

    class Meta:
        model = Room
        fields = ['name', 'temperature']
like image 126
Cohan Avatar answered Oct 22 '25 07:10

Cohan


To modify data in your existing record before rendering, just do it in self.instance after calling the parent's __init__.

def __init__(self, plant, *args, **kwargs):
    super(RoomForm,self ).__init__(*args,**kwargs)
    self.instance.temperature = # ...
like image 24
lucasnadalutti Avatar answered Oct 22 '25 05:10

lucasnadalutti