I have a simple model of an Observation made by a Sensor:
class Observation(models.Model):
class ObservationType(models.TextChoices):
PM25 = 'pm25_kal', 'PM2,5'
PM10 = 'pm10_kal', 'PM10'
RH = 'rh', _('Relative humidity')
TEMP = 'temp', _('Temperature')
date_time = models.DateTimeField()
sensor = models.ForeignKey(Sensor, on_delete=models.CASCADE)
obs_type = models.CharField(max_length=8, choices=ObservationType.choices)
value = models.DecimalField(max_digits=6, decimal_places=3)
What I want to do, is get a list or QuerySet with the latest Observation of a certain type that should at least have been created within 24 hours, for each sensor. I solved the problem using a model method for my Sensor model and a custom QuerySet for my Observation model, to filter recent observations.
class ObservationQuerySet(models.query.QuerySet):
def recent(self):
return self.filter(date_time__gte=timezone.now() - timedelta(days=1))
def latest_recent_observation(self, obs_type):
try:
return self.observation_set.filter(obs_type=obs_type).recent().latest('date_time')
except Observation.DoesNotExist:
return None
I can loop over all sensors and get the latest_recent_observation() for each of them, but for larger datasets it is pretty slow. Is there any way to make this more efficient?
Edit: At this moment I'm using SQLite, but I might switch to MariaDB. Would that make this faster as well?
I eventually figured it out myself. I used annotation to get the value of the latest recent observation.
latest_observation = Subquery(Observation.objects.filter(sensor_id=OuterRef('id'), obs_type=obs_type)
.recent()
.order_by('-date_time')
.values('value')[:1])
With this, I can use annotate() on a Queryset of my Sensor model, which returns a new Queryset with the value of the latest Observation of that given Sensor.
sensors = Sensor.objects.all().annotate(latest_observation=latest_observation)
Related
I have the following models (simplified):
class Resource(models.Model):
name = models.CharField(max_length=64, unique=True)
class ResourceFlow(models.Model):
resource = models.ForeignKey(Resource, related_name="flow")
amount = models.IntegerField()
class Workflow(models.Model):
inputs = models.ManyToManyField(ResourceFlow, related_name="workflow")
class Stock(models):
resource = models.ForeignKey(Resource, related_name="stock")
amount = models.IntegerField()
class Producer(models.Model):
workflow = models.ForeignKey(Workflow, related_name="location")
stocks = models.ManyToManyField(Stock, related_name="location")
I would like to test with computation done by the the DB engine if I can start a production.
A production can start if I have enough stock: for my Producer's workflow, all inputs ResourcesFlow amount have to be present in the Producer'stocks
So the queryset might be one those result:
for a given producer return all stocked resources that do not fulfill Workflow inputs amounts conditions
for a given producer return inputs resources needed for the workflow that are not in sufficient quantity in its stocks
It is possible to do that in Django? And if yes how to do it?
Not sure if you've found the answer but anyways, hope I understand your question correctly.
Let's assume we have the following resources:
head = Resource.objects.create(name="head")
neck = Resource.objects.create(name="neck")
body = Resource.objects.create(name="body")
arm = Resource.objects.create(name="arm")
leg = Resource.objects.create(name="leg")
And we have a build_a_robot workflow:
build_a_robot = Workflow.objects.create()
build_a_robot.inputs.add(ResourceFlow.objects.create(resource=head, amount=1))
build_a_robot.inputs.add(ResourceFlow.objects.create(resource=neck, amount=1))
build_a_robot.inputs.add(ResourceFlow.objects.create(resource=body, amount=1))
build_a_robot.inputs.add(ResourceFlow.objects.create(resource=arm, amount=2))
build_a_robot.inputs.add(ResourceFlow.objects.create(resource=leg, amount=2))
And finally, we have a producer:
producer = Producer.objects.create(workflow=build_a_robot)
producer.stocks.add(Stock.objects.create(resource=head, amount=0))
producer.stocks.add(Stock.objects.create(resource=neck, amount=3))
producer.stocks.add(Stock.objects.create(resource=body, amount=1))
producer.stocks.add(Stock.objects.create(resource=arm, amount=10))
producer.stocks.add(Stock.objects.create(resource=leg, amount=1))
We want to find the list of resources that we have run out of to build a robot given producer.
I think here's one way to do it:
from django.db.models import OuterRef, Subquery
required_resources = ResourceFlow.objects.filter(pk__in=producer.workflow.inputs.values("pk")).values("resource")
required_amount = producer.workflow.inputs.filter(resource=OuterRef("resource")).values("amount")[:1]
missing_stocks = Stock.objects.filter(resource_id__in=required_resources).filter(amount__lt=required_amount)
In this example, missing_stocks will be equal to:
<QuerySet [<Stock: Stock [Resource [head], 0]>, <Stock: Stock [Resource [leg], 1]>]>
So, we need more head and leg to build a robot.
There is a trial end field in subscription model, i want to initialize the field with trial_end_date ,
problem I'm facing now trial_end in subscription model showing null value, How can I extract out the field of trial end field and initialize it? I have attached the def create method looked at that.
I will appreciate your help .
def create(self, validated_data):
subscriptions_data = validated_data.pop('plan')
print(subscriptions_data)
user_memberships = UserMembership.objects.create(**validated_data)
trial_end = user_memberships.membership.get_trial_period_days()
trial_end_date = datetime.date.today() + datetime.timedelta(trial_end)
for subscription_data in subscriptions_data:
Subscription.objects.create(user_membership=user_memberships, **subscription_data,)
return user_memberships
[Model][2]
my aim is when in membership model plan contain trial period days , it will add in trial end field of subscription model
Probably because you are not passing trial_end_date's value in Subscription.objects.create(..). So you can fix it like this:
trial_end = user_memberships.membership.get_trial_period_days()
trial_end_date = datetime.date.today() + datetime.timedelta(trial_end)
for subscription_data in subscriptions_data:
Subscription.objects.create(trial_end = trial_end_date, user_membership=user_memberships, **subscription_data,)
I have a ModelSerializer class which implements some fields with SerializerMethodField. There are a few fields which calculates currency data according to the rates retrieved from a database if there are, otherwise from a bank API.
With current implementation there are a lot of short queries to the database just to retrieve or check a rate. I'm thinking of reducing the number of queries by sharing already calculated data between fields.
The code will explain the idea better:
class CalcualteSomethingSerializer(ModelSerializer):
currency_value_1 = serializers.SerializerMethodField()
currency_value_2 = serializers.SerializerMethodField()
def get_currency_value_1(obj):
if obj.currency_code != 'USD':
rate = get_rates('USD', obj.currency_code, obj.date)
return calculation_logic1(obj.value, rate)
def get_currency_value_2(obj):
if obj.currency_code != 'USD':
rate = get_rates('USD', obj.currency_code, obj.date)
return calculation_logic2(obj.value, rate)
I've tried to save the rates into self._kwargs but it reduced a number of queries only for 5 queries.
I want to write a model method that modifies it's nested fields
I'm having trouble iterating through an object that is related to the main model. The code in particular is:
def set_si_units(self):
self.currently.get_si_units()
for i in range(0, self.hourly.data.count()):
self.hourly.data[i].get_si_units()
The 2nd line that modifies self.currently runs without a hitch and I receive converted temperatures. The for loop however gives me the following error:
TypeError: 'RelatedManager' object does not support indexing
I'd really like to be able to iterate through each instance of the Data model individually so I can convert the temperatures as I am doing with the Currently model.
I've included the relevant code below as well. Please let me know if you need to see something else. Any help or feedback with regards to my approach is greatly appreciated!
Traceback
File "/path_to_project/project/weather/models.py", line 137, in get_si_units
self.hourly.data[i] = self.hourly.data[i].get_si_units()
TypeError: 'RelatedManager' object does not support indexing
Classes with get_si_units() (eg. Currently & Data)
class SomeClass(model.Models):
temperature = models.FloatField(null=True, blank=True)
... # Other fields
def convert_f_to_c(self, temperature_f):
...
def get_si_units(self):
data_point = self
data_point.temperature = self.convert_f_to_c(self.temperature)
... # Convert other fields
return data_point
Location class that I'm stuck on
class Location(models.Model):
currently = models.OneToOneField(Currently, on_delete=models.CASCADE)
hourly = models.OneToOneField(Hourly, on_delete=models.CASCADE)
...
def set_si_units(self):
self.currently.get_si_units()
for i in range(0, self.hourly.data.count()):
self.hourly.data[i].get_si_units()
You can use get all objects then iterate over it.
class Location(models.Model):
currently = models.OneToOneField(Currently, on_delete=models.CASCADE)
hourly = models.OneToOneField(Hourly, on_delete=models.CASCADE)
...
def set_si_units(self):
self.currently.get_si_units()
for hourly_data in self.hourly.data.all():
hourly_data.get_si_units()
Say I have a number of probes which record values. I want to set up Django models to represent the probes as well as the measurements they record. So something like this would work:
class Probe(models.Model):
name = models.CharField(max_length=256)
def __unicode__(self):
return u'<Probe: %s>' % self.name
class Observation(models.Model):
probe = models.ForeignKey(Probe)
obstime = models.DateTimeField()
# the above field should be understood to represent the time in the world
# represented by the measurement value recorded. *not* the time at which
# that value was written to the database.
value = models.FloatField()
class Meta:
unique_together = (('probe', 'obstime'), )
def __unicode__(self):
tup = (self.probe.name,
self.obstime.strftime('%Y%m%d-%H%M%S'),
'%0.2f' % self.value)
return u'<Observation: %s # %s = %s>' % tup
But beyond this, I would really like my database and app to be able to keep track of a bit more information. In particular, I really want the Observation model to have 3 additional fields: db_recording_time, previous_value, previous_db_recording_time. I want users to directly attempt to manipulate just the entries given above, but have the other fields behave automatically in the expected way. I think this should be possible by overriding the save method on the Observation class, but I need some help!
So say a person comes back from the field and sits down at their computer at Noon on Jan5th. They want to record some (never-before-entered) data indicating that ProbeA read 3.14 at 2AM on Jan1st. I'd only want them to need to specify 3.14 and 2AMJan1st, but I'd want an observation to go into the DB with:
probe: ProbeA,
obstime: 2AMJan1st,
value: 3.14,
db_recording_time: NoonJan5th,
previous_value: Null,
previous_db_reording_time: Null
Then, a couple hours later (2PMJan5th), that same person could look at their notebook and realize "oops, I misread the value in my notebook...it was really 2.14". So I'd want them to (either using the admin or a Python console) call up the existing observation and correct the 3.14 to be a 2.14. When they did that, I'd want the observation in the DB to show:
probe: ProbeA,
obstime: 2AMJan1st,
value: 2.14,
db_recording_time: 2PMJan5th,
previous_value: 3.14,
previous_db_reording_time: NoonJan5th
I feel like this should be pretty straightforward with some combination of read-only fields in the Admin interface as well as some reasonable bit of overriding in the save method for the Observation class. Any help would be much appreciated!
You could retrieve the old record from the database before saving:
recording_time = models.DateTimeField(auto_now=True)
previous_value = models.FloatField(null=True, default=None)
previous_recording_time = models.DateTimeField(null=True, default=None)
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
try:
obj = Observation.objects.get(pk=self.pk)
# this is the record of this instance before editing
except Observation.DoesNotExist: # self.pk==None: this is a new Observation
pass
else: # this is indeed an update
self.previous_value = obj.value
previous_db_recording_time = obj.db_recording_time
super(Observation, self).save(force_insert=force_insert, force_update=force_update, using=using,
update_fields=update_fields)
Now, nobody should ever have to manually set recording_time, previous_value, or previous_recording_time. So you can exclude those fields from the ModelForm that you use in Observation's Admin class.
As I suggested in comments, you could add a foreign key to self in Observation model which refers to the observation taken earlier and then you don't need to duplicate these fields e.g. previous_value, previous_db_recording_time.
class Observation(models.Model):
...
prev_observation = models.ForeignKey('self', null=True)
# you can also add a `is_recent` field to this model so you set it to True when its created
is_recent = models.BooleanField(default=True)
So let's say you added 7 observations in a week [o1, o2, o3, o4, o5, o6, o7] so for all these 7 is_recentt will be True.
And, you can get these observations as follows:
Observation.objects.filter(probe=chosenprobe,
obstime__gte=start, obstime__lte=end,
is_recent=True)
# [o1, o2, o3, o4, o5, o6, o7]
Now, you corrected o1, o3 and o6 with o8, o9 and o10 so at this time you can set is_recent for o1, o3 and o6 to False.
And, then running the above query again will give you updated (recent) observations:
# [o2, o4, o5, o7, o8, o9, o10]