Runtime Updation of Multiple Fields using F Function in Django
In this tutorial, we will learn how we can continuously update multiple fields using the F
function in Django.
<!--more-->
Let us imagine a scenario - a patriot in Nigeria normally has an electricity meter installed in his house. Every second he uses the power, the usage data is sent from the electricity meter to the power office.
Consequently, two values detailing his amount of total power consumed and amount of power remaining are sent to this user via the smart phone.
In this tutorial, we will mimic an electricity meter that reads a user's power usage on one end, and stream the electricity meter generated data (consumption rate, power remaining, and total power used) on the other end.
This runtime streaming of data can be done easily and efficiently using the Django F()
function.
Table of contents
- Table of contents
- Pre-requisites
- How is F() efficient?
- Implementation
- How the F() works?
- Points to note
- Conclusion
Pre-requisites
To make the most of this tutorial, it is required to have the following:
- Basic understanding of Python.
- Familiarity with the Django framework and Django REST framework.
- Familiarity with the Django browseable API interface.
- PyCharm professional code editor installed.
How is F() efficient?
The traditional approach would have been to constantly fetch and iterate over the stream of data – readings, get the sum of power used and subtract it from the threshold, to get the values of power used and power remaining. This is a less efficient approach.
With the F(), a single reading object (meant for the user) can be updated on the fly without reference to the previous data, can be saved to the database and be ready for the user to view it. You can do this for multiple fields at a go.
Implementation
In this demonstration of how F function works, we will simulate a meter reading process as described earlier in this text.
Model
We will require three models: RegisterMeter
, MeterReading
and CurrentUsage
. The snippets below show the code for creating these models:
from django.db import models
class RegisterMeter(models.Model):
meter_id = models.CharField(max_length=10)
date_added = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.pk)
class MeterReading(models.Model):
meter = models.CharField(max_length=10)
meter_reading = models.IntegerField()
date_sent = models.DateTimeField(auto_now=True)
def __str__(self):
return self.meter
class CurrentUsage(models.Model):
meter = models.CharField(max_length=10)
total_power_used = models.CharField(max_length=10)
power_remaining = models.CharField(max_length=10)
def __str__(self):
return self.meter
Serializer
We will also need to have serializers for the models shown in the code snippet above.
The serializers converts objects to JSON format. Serializers for each of the models is shown below:
from rest_framework import serializers
from .models import MeterReading, CurrentUsage, RegisterMeter
class MeterReadingSerializer(serializers.ModelSerializer):
class Meta:
model = MeterReading
fields = '__all__'
class CurrentUsageSerializer(serializers.ModelSerializer):
class Meta:
model = CurrentUsage
fields = '__all__'
class RegisterMeterSerializer(serializers.ModelSerializer):
class Meta:
model = RegisterMeter
fields = '__all__'
The RegisterMeter
in the models.py
file is used to on-board an electricity meter.
MeterReading
represents a single data body generated from the electricity meter and sent to the power office.
The CurrentUsage
is the reading of power used and power remaining.
The idea is that, while onboarding a new electricity meter, a default CurrentUsage
is created, where the total_power_used
and power_remaining
are set to 0
by default.
On every creation of the MeterReading
object, the CurrentUsage
object is updated.
View
Inside the views.py
file, we create a view to onboard an electricity meter as shown:
class RegisterMeterCreateView(generics.CreateAPIView):
queryset = RegisterMeter.objects.all()
serializer_class = RegisterMeterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid(raise_exception=True):
return Response({"message": "Something went wrong"}, status=status.HTTP_400_BAD_REQUEST)
meter = request.data.get('meter_id')
payload = {
"meter": meter,
"total_power_used": "0",
"power_remaining": "0",
}
CurrentUsage.objects.create(**payload)
if serializer.is_valid():
serializer.save()
return Response({"message": "Meter has been on-boarded and a default current reading created"},
status=status.HTTP_200_OK)
URL
Inside the urls.py
file, we display the endpoint to access the meter created as shown below:
from django.urls import path
from meter.views import RegisterMeterCreateView, check_meter_usage, CreateMeterReading, \
AllMetersListView
urlpatterns = [
path('add-meter', RegisterMeterCreateView.as_view()),
path('all-meters', AllMetersListView.as_view()),
]
To view all meters that have been registered, use the code snippet below:
class AllMetersListView(generics.ListAPIView):
queryset = RegisterMeter.objects.all()
serializer_class = RegisterMeterSerializer
As a result, we also need to create a view to check for a CurrentUsage
object using the meter_id
as shown below:
@api_view(['GET'])
def check_meter_usage(self, meter_id):
meter_reading = CurrentUsageSerializer(CurrentUsage.objects.get(meter=meter_id))
return Response(meter_reading.data)
Similarly, we need an URL with the endpoint to fetch response from the view above:
from django.urls import path
from meter.views import RegisterMeterCreateView, check_meter_usage, CreateMeterReading, \
AllMetersListView
urlpatterns = [
path('add-meter', RegisterMeterCreateView.as_view()),
path('all-meters', AllMetersListView.as_view()),
path('meter-usage/<str:meter_id>', check_meter_usage), # New
To create a single meter reading object, we create a new view as shown below:
class CreateMeterReading(generics.CreateAPIView): # New
queryset = MeterReading.objects.all()
serializer_class = MeterReadingSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid(raise_exception=True):
return Response({"message": "Something went wrong"}, status=status.HTTP_400_BAD_REQUEST)
meter = request.data.get('meter')
meter_reading = int(request.data.get('meter_reading'))
meter_usage_object = CurrentUsage.objects.get(meter=meter)
CurrentUsage.objects.filter(meter=meter_usage_object.meter).update(
total_power_used=F("total_power_used") + meter_reading,
power_remaining=25 - (F("total_power_used") + meter_reading)
)
meter_usage_object.refresh_from_db()
if serializer.is_valid():
serializer.save()
return Response({"message": "Reading created, current usage updated"}, status=status.HTTP_200_OK)
The URL for the view above is:
from django.urls import path
from meter.views import RegisterMeterCreateView, check_meter_usage, CreateMeterReading, \
AllMetersListView
urlpatterns = [
path('add-meter', RegisterMeterCreateView.as_view()),
path('all-meters', AllMetersListView.as_view()),
path('meter-usage/<str:meter_id>', check_meter_usage),
path('create-reading', CreateMeterReading.as_view()), # new
]
How the F() works?
Now that we have our models, serializers, views, and URLs ready, let us begin the actual demonstration of the mini-project.
We create a new meter using the endpoint localhost:8000/add-meter
and fill in the page accordingly:
Creating a new meter
A new meter object created
To check that the meter was successfully registered, visit localhost:8000/all-meters
to view them:
View all meters
To view details of a particular meter, visit localhost:8000/meter-usage/PM01
as shown:
View details about a particular meter
Let us create a new MeterReading
object with value 1
for our meter PM01
(this reading is sent to the power office) and update the meter's CurrentUsage
in the process as shown:
Add a new meter reading
Adding a new meter reading to PM01
Meter reading created for PM01
From above, we see that a reading of 1
unit is sent from the meter and consequently, the current usage updated to reflect an expense from the MeterReading
.
Check the current usage by visiting the endpoint localhost:8000/meter-usage/PM01
to confirm if the POST operation was successful.
We are assuming a usage threshold of 25 units. So, when the meter read that the user has spent 1 unit, he has 24 left.
MeterReading object for PM01 updated with new reading
Let's try with a higher meter reading, say 3
:
Add new meter reading with value '3'
We see that the CurrentUsage
is updated again with total_power_used
as 4
and power_remaining
as 21
.
MeterReading object updated for meter PM01
After following this tutorial, you may have another insight where one can manipulate the fields of a Django model without having to make too many calls and passing objects about.
The F() does a lot of this heavy-lifting and you can manipulate as many fields as you might want to.
Points to note
- By using F(), one can keep track of all the changes made. For instance, when
MeterReading
is saved, the power usage history of any user can be fetched, for transparency or audit sakes. - Validation and conditions can be set before updating any value. For instance, while updating the
CurrentUsage
for every creation of a reading, validations and conditions can be set before. - The current usage can be reset by sending in a negative number for the
power_used
field, say-1
.
Conclusion
In this article, we have learned how to use the F function and why using it is better than the traditional approach
In programming, efficiency matters. It is best that we always seek out ways to improve the efficiency of our programs. In Python, using F function makes our program more efficient.
You can find the full code on Github.
Happy coding!
Peer Review Contributions by: Srishilesh P S