Unit Testing Django Model Save Function - django

I'm creating tests to check that a custom calibration model save function updates an asset record (foreign key) if it is the latest calibration record for the asset. The save function performs exactly as expected in live dev & production server and even in the django shell, but appears to fail during testing...
models.py
class Asset(models.Model):
...
requires_calibration = models.BooleanField()
passed_calibration = models.BooleanField(default=False)
calibration_date_prev = models.DateField(null=True, blank=True)
calibration_date_next = models.DateField(null=True, blank=True)
class CalibrationRecord(models.Model):
calibration_record_id = models.AutoField(primary_key=True)
asset = models.ForeignKey(
"myapp.Asset",
on_delete=models.CASCADE,
limit_choices_to={"requires_calibration": True}
)
calibration_date = models.DateField(default=timezone.now)
calibration_date_next = models.DateField(null=True, blank=True)
calibration_outcome = models.CharField(max_length=10, default="Pass")
def save(self, *args, **kwargs):
super(CalibrationRecord, self).save(*args, **kwargs)
# Check if this is the latest calibration record for any asset, if so update asset.calibration_dates and status
latest_asset_calibration = CalibrationRecord.objects.filter(asset=self.asset.pk).order_by(
"-calibration_date", "-calibration_record_id")[0]
if self.pk == latest_asset_calibration.pk:
Asset.objects.filter(pk=self.asset.pk).update(calibration_date_prev=self.calibration_date)
if self.calibration_date_next:
Asset.objects.filter(pk=self.asset.pk).update(calibration_date_next=self.calibration_date_next)
else:
Asset.objects.filter(pk=self.asset.pk).update(calibration_date_next=None)
if self.calibration_outcome == "Pass":
Asset.objects.filter(pk=self.asset.pk).update(passed_calibration=True)
else:
Asset.objects.filter(pk=self.asset.pk).update(passed_calibration=False)
tests_models.py example failing test
class CalibrationRecordTests(TestCase):
def test_calibration_record_updates_asset_cal_date_prev(self):
"""
All calibration records should update related Asset record's "calibration_date_prev" to calibration_date
"""
asset1 = Asset.objects.create(asset_description="Test Asset 2", requires_calibration=True)
self.assertIsNone(asset1.calibration_date_prev)
cal = CalibrationRecord.objects.create(asset=asset1, calibration_description="Test Calibration 2", calibration_date=timezone.now())
self.assertEqual(cal.calibration_date, asset1.calibration_date_prev)
Error log
======================================================================
FAIL: test_calibration_record_updates_asset_cal_date_prev (assetregister.tests_models.CalibrationRecordTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\[path]\app\tests_models.py", line 159, in test_calibration_record_updates_asset_cal_date_prev
self.assertEqual(cal.calibration_date, asset1.calibration_date_prev)
AssertionError: datetime.datetime(2018, 2, 26, 12, 26, 34, 457513, tzinfo=<UTC>) != None
======================================================================
All of my tests relating to this custom calibration record save function appear to fail because the related asset record isn't updated when it should be.
Any ideas why this would work during dev and production but not during testing?
Even though the .create() method should automatically do a .save() after, I even tried doing a manual .save() after creating the calibration record, but it still seems to fail.

Solved!
Custom save functions correctly update the database, but not the model instance being tested! Need to refresh the model instance to get any updates by calling it again with something like [model].objects.get()
e.g.:
asset = Asset.objects.create(asset_description="Test Asset 2", requires_calibration=True)
self.assertIsNone(asset.calibration_date_prev)
CalibrationRecord.objects.create(asset=asset, calibration_description="Test Calibration 2",
calibration_date=timezone.now())
cal = CalibrationRecord.objects.get(calibration_description="Test Calibration 2")
asset = Asset.objects.get(asset_description="Test Asset 2")
self.assertEqual(cal.calibration_date, asset.calibration_date_prev)

If you find yourself here, you'd be better off reloading the object with Model.refresh_from_db(using=None, fields=None). See the Django documentation
Also checkout Reload django object from database

Related

Get Data from Choice field in Django rest framework

I have searched and tried out all solutions provided for this question in similar problem but they're not working for me. I am getting the following error when trying to get data from an endpoint
Got AttributeError when attempting to get a value for field
hotel_type on serializer HotelDetailSerializer. The serializer
field might be named incorrectly and not match any attribute or key on
the Hotels instance. Original exception text was: 'Hotels' object
has no attribute 'get_hotel_Type_display'.
This is my model field truncated for clarity
class Hotels(models.Model):
HOTEL_TYPE = (
('hotel', "Hotel"),
('apartment', "Apartment"),
('villa', "Villa"),
hotel_Type = models.CharField(
max_length=20,
choices=HOTEL_TYPE,
default='hotel', null=True, blank=True
#property
def hotel_type(self):
return self.get_hotel_Type_display()
This is my serializer class also truncated for clarity
class HotelDetailSerializer(serializers.ModelSerializer):
hotel_type = serializers.Field(source='hotel_type.hotel_Type')
class Meta:
model = Hotels
fields = ("hotel_type" )
This is the apiview
class HotelDetailAPIView(RetrieveAPIView):
"""Display details of a single hotel"""
queryset = Hotels.objects.all()
serializer_class = HotelDetailSerializer
permission_classes = ()
lookup_field = 'slug'
Could anyone kindly assist me figure out why this is not working? Thanks
EDIT
I am editing this question to add more context. I have been doing some debugging in Django shell. This is what i am getting
>>> from hotels.models import Hotels
>>> h = Hotels.objects.all()
>>> for n in h:
... print (n.hotel_Type)
...
(<django.db.models.fields.CharField>,)
>>> for n in h:
... print (n.get_hotel_Type_display())
...
Traceback (most recent call last):
File "<console>", line 2, in <module>
AttributeError: 'Hotels' object has no attribute 'get_hotel_Type_display'
I am following Django's get_FOO_display() tutorial and i still cannot get this work. I am not seeing anything wrong with my Hotels model. Could this be a bug in Django? Kindly assist
This really has eaten me up. I finally found the issue was a misplaced comma on my model field as shown below
class Hotels(models.Model):
HOTEL_TYPE = (
('hotel', "Hotel"),
('apartment', "Apartment"),
('villa', "Villa"),
hotel_Type = models.CharField(
max_length=20,
choices=HOTEL_TYPE,
default='hotel', null=True, blank=True
),# <--------------This comma was the source of my problems
#property
def hotel_type(self):
return self.get_hotel_Type_display()

Django Webhook creates double database entries

I'm currently using Django to code a webhook application for google Dialogflow.
It was working fine, I was basically done.
For some reason, I now started encountering various randomly appearing problems, one of the worst being the following:
Whenever the webhook executes the user account creation call, it creates a double-database entry, which crashes the program (because my .get suddenly returns multiple elements instead of a single user).
I'm using the following simple models:
# model to create user entries
class TestUser(models.Model):
name = models.CharField(max_length=200)
userID = models.CharField(max_length=12, blank=True)
registrationDate = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
# model to add watched movies to user
class Movie(models.Model):
username = models.ForeignKey(TestUser, on_delete=models.CASCADE)
title = models.CharField(max_length=200, blank=True)
genreID = models.IntegerField (blank=True)
def __str__(self):
return self.title
def list_genre_id(self):
return self.genreID
The part that gets executed in my webhook while the problem occurs should be the following:
if action == "account_creation":
selection = req.get("queryResult").get("parameters").get("account_selection")
if selection == "True":
q = TestUser(name=f"{username}", userID=idgen())
q.save()
userID = TestUser.objects.get(name=f"{username}").userID
fullfillmenttext = {"fulfillment_text": "Alright, I created a new account for you! Would you like to add "
"some of your favorite movies to your account?",
"outputContexts": [
{
"name": f"projects/nextflix-d48b9/agent/sessions/{userID}/contexts/create_add_movies",
"lifespanCount": 1,
"parameters": {
"data": "{}"
}
}]}
This is the simple idgen function I'm using:
def idgen():
y = ''.join(
random.SystemRandom().choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(12))
return y
I'm trying to create this userID as a way of having a unique session ID in the webhook calls for all users. Something seems to mess it up, but I don't have the slightest clue what.
Thank you so much for looking over this!
It seems I was able to fix the issue:
The problem was apparently that I had the lifespan of a previous outputContext set to 2 instead of 1, which resulted in the answer executing the codecell twice for some reason. Man, dialogflow is such a terrible program.

Shapefile to PostGIS import generates django datetime error

Hi I'm trying to populate the PostGIS database of my Django application using a Shapefile. My models.py is the following :
class Flow(models.Model):
# Primary key
flow_id = models.AutoField("ID, flow identifier", primary_key=True)
# Other attributes
stime = models.DateTimeField("Start time")
stime_bc = models.IntegerField("Year of start time before Christ")
stime_unc = models.DateTimeField("Start time uncertainty")
etime = models.DateTimeField("End time")
etime_bc = models.IntegerField("Year of end time before Christ")
etime_unc = models.DateTimeField("End time uncertainty")
final_vers = models.BooleanField("1 if it's the final version, else 0",
default=False)
com = models.CharField("Comments", max_length=255)
loaddate = models.DateTimeField("Load date, the date the data was entered "
"(in UTC)")
pubdate = models.DateTimeField("Publish date, the date the data become "
"public")
cb_ids = models.ManyToManyField(Bibliographic)
# Foreign key(s)
fissure_id = models.ForeignKey(Fissure, null=True,
related_name='flow_fissure_id',
on_delete=models.CASCADE)
cc_load_id = models.ForeignKey(Contact, null=True,
related_name='flow_cc_id_load',
on_delete=models.CASCADE)
cc_pub_id = models.ForeignKey(Contact, null=True,
related_name='flow_cc_id_pub',
on_delete=models.CASCADE)
# Override default table name
class Meta:
db_table = 'flow'
I want to add the features of my coulees.shp Shapefile into my database (more precisely in the flow table). The attribute table looks like this:
Atribute table
To do so I use the Django Layer Mapping:
import os
from django.contrib.gis.utils import LayerMapping
from .models import Flow
mapping = {'stime':'stime', 'stime_bc':'stime_bc', 'stime_unc':'stime_unc', 'etime':'etime', 'etime_bc':'etime_bc', 'etime_unc':'etime_unc', 'com':'com', 'loaddate':'loaddate', 'pubdate':'pubdate', 'geometry':'geometry'}
shp = os.path.abspath(
os.path.join(os.path.dirname(__file__), 'data', '/home/sysop/Coulees/coulees.shp')
)
def run(verbose=True):
lm = LayerMapping(
Flow, shp, mapping,
transform=True
)
lm.save(verbose=True)
But when I try to run this function I get the following error:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/opt/mapobs/mapobs/app/load.py", line 30, in run
transform=True
File "/opt/mapobs/lib/python3.5/site-packages/django/contrib/gis/utils /layermapping.py", line 106, in __init__
self.check_layer()
File "/opt/mapobs/lib/python3.5/site-packages/django/contrib/gis/utils/layermapping.py", line 256, in check_layer
(ogr_field, ogr_field.__name__, fld_name))
django.contrib.gis.utils.layermapping.LayerMapError: OGR field "<class 'django.contrib.gis.gdal.field.OFTDate'>" (of type OFTDate) cannot be mapped to Django DateTimeField.
Unfortunately, I can't find any useful documentation on the internet.

Django difficulty saving multiple model objects within save method

This is a hard question for me to describe, but I will do my best here.
I have a model that is for a calendar event:
class Event(models.Model):
account = models.ForeignKey(Account, related_name="event_account")
location = models.ForeignKey(Location, related_name="event_location")
patient = models.ManyToManyField(Patient)
datetime_start = models.DateTimeField()
datetime_end = models.DateTimeField()
last_update = models.DateTimeField(auto_now=False, auto_now_add=False, null=True, blank=True)
event_series = models.ForeignKey(EventSeries, related_name="event_series", null=True, blank=True)
is_original_event = models.BooleanField(default=True)
When this is saved I am overriding the save() method to check and see if the event_series (recurring events) is set. If it is, then I need to iteratively create another event object for each recurring date.
The following seems to work, though it may not be the best approach:
def save(self, *args, **kwargs):
if self.pk is None:
if self.event_series is not None and self.is_original_event is True :
recurrence_rules = EventSeries.objects.get(pk=self.event_series.pk)
rr_freq = DAILY
if recurrence_rules.frequency == "DAILY":
rr_freq = DAILY
elif recurrence_rules.frequency == "WEEKLY":
rr_freq = WEEKLY
elif recurrence_rules.frequency == "MONTHLY":
rr_freq = MONTHLY
elif recurrence_rules.frequency == "YEARLY":
rr_freq = YEARLY
rlist = list(rrule(rr_freq, count=recurrence_rules.recurrences, dtstart=self.datetime_start))
for revent in rlist:
evnt = Event.objects.create(account = self.account, location = self.location, datetime_start = revent, datetime_end = revent, is_original_event = False, event_series = self.event_series)
super(Event, evnt).save(*args, **kwargs)
super(Event, self).save(*args, **kwargs)
However, the real problem I am finding is that using this methodology and saving from the Admin forms, it is creating the recurring events, but if I try to get self.patient which is a M2M field, I keep getting this error:
'Event' instance needs to have a primary key value before a many-to-many relationship can be used
My main question is about this m2m error, but also if you have any feedback on the nested saving for recurring events, that would be great as well.
Thanks much!
If the code trying to access self.patient is in the save method and happens before the instance has been saved then it's clearly the expected behaviour. Remember that Model objects are just a thin (well...) wrapper over a SQL database... Also, even if you first save your new instance then try to access self.patient from the save method you'll still have an empty queryset since the m2m won't have been saved by the admin form yet.
IOW, if you have something to do that depends on m2m being set, you'll have to put it in a distinct method and ensure that method get called when appropriate
About your code snippet:
1/ the recurrence_rules = EventSeries.objects.get(pk=self.event_series.pk) is just redundant, since you alreay have the very same object under the name self.event_series
2/ there's no need to call save on the events you create with Event.objects.create - the ModelManager.create method really create an instance (that is: save it to the database).

UPDATE in raw sql does not hit all records although they meet the criteria

I am trying to update several records when I hit the save button in the admin with a raw sql which is located in models.py (def save(self, *args, **kwargs)
The raw sql is like this as a prototype
cursor=connection.cursor()
cursor.execute("UPDATE sales_ordered_item SET oi_delivery = %s WHERE oi_order_id = %s", ['2011-05-29', '1105212105'])
Unfortunately it does not update all records which meet the criteria. Only one and sometimes more but never all.
With the SQLite Manager and the following SQL everything works great and all the records get updated:
UPDATE sales_ordered_item
SET oi_delivery = '2011-05-29'
WHERE oi_order_id = '1105212105'
I was thinking of using a manager to update the table but I have no idea how this would work when not using static data like '2011-05-29'. Anyways, it would be great to understand in the first place how to hit all records with the raw sql.
Any recommendations how to solve the problems in a different way are also highly appreciated
Here ist the code which I stripped a little to keep it short
# Orders of the customers
class Order(models.Model):
"""
Defines the order data incl. payment, shipping and delivery
"""
# Main Data
o_customer = models.ForeignKey(Customer, related_name='customer',
verbose_name=_u'Customer'), help_text=_(u'Please select the related Customer'))
o_id = models.CharField(_(u'Order ID'), max_length=10, primary_key=True,
help_text=_(u'ID has the format YYMMDDHHMM'))
o_date = models.DateField(_(u'created'))
and more...
# Order Item
class Ordered_item(models.Model):
"""
Defines the ordered item to which order it belongs, pricing is decoupled from the
catalogue to be free of any changes in the pricing. Pricing and description is copied
from the item catalogue as a proposal and can be altered
"""
oi_order = models.ForeignKey(Order, related_name='Order', verbose_name=_(u'Order ID'))
oi_pos = models.CharField(_('Position'), max_length=2, default='01')
oi_quantity = models.PositiveIntegerField(_('Quantity'), default=1)
# Date of the delivery to determine the status of the item: ordered or already delivered
oi_delivery = models.DateField(_(u'Delivery'), null=True, blank=True)
and more ...
def save(self, *args, **kwargs):
# does not hit all records, use static values for test purposes
cursor=connection.cursor()
cursor.execute("UPDATE sales_ordered_item SET oi_delivery = %s WHERE oi_order_id = %s", ['2011-05-29', '1105212105'])
super(Ordered_item, self).save(*args, **kwargs)
This is probably happening because you are not commiting the transaction (See https://docs.djangoproject.com/en/dev/topics/db/sql/#executing-custom-sql-directly)
Add these lines after your cursor.execute:
from django.db import transaction
transaction.commit_unless_managed()
You asked for a manager method.
SalesOrderedItem.objects.filter(oi_order='1105212105').update(oi_delivery='2011-05-29')
should do the job for you!
Edit:
I assume that you have two models (I am guessing this code from your raw SQL):
class OiOrder(models.Model):
pass
class SalesOrderedItem(models.Model):
oi_order = models.ForeignKey(OiOrder)
oi_delivery = models.DateField()
So:
SalesOrderedItem.objects.filter(oi_order='1105212105')
gives you all SalesOrderedItem which have a oi_order of 1105212105.
... update(oi_delivery='2011-05-29')
The update method updates all oi_delivery attributes.