Django assertEqual does not show actual vs expected values - django

So I'm learning how to practice TDD in Django and I'm having some minor trouble. I created a custom user object that links to authenticated system users in a one to one relationship. I have the following test, which exercises part of my custom user class:
def test_creating_a_user_with_attributes(self):
myuser = Myuser.objects.create_user('Gary', email='me#email.com')
current_time = now()
myuser.birthday = current_time
myuser.save()
first_user = Myuser.objects.all()[0]
self.assertEqual(first_user.birthday, current_time, 'first_user.birthday should be equal to the current_time')
The problem is that my test was failing and I couldn't immediately see why. The assert failure reported the message I had supplied and I was confused because I was certain that the birthday was set to the value of now. I ended up having to refactor my assert to make the failing value clear.
self.assertEqual(first_user.birthday, current_time,
'first_user.birthday ' + str(first_user.birthday) + ' should equal ' + str(current_time))
This revealed that the birthday was a date field and not a datetime field. My question is wether there exists some alternate form of assert that dumps the expected and actual values as part of the failure message or if I am somehow misusing or misunderstanding the API?

Django doesn't implement assertEqual, it simply uses Python's unittest module for that one.
What you need is to set the longMessage attribute to True for your test case class, like so:
class VerboseTestCase(TestCase):
longMessage = True
def test_creating_a_user_with_attributes(self):
myuser = Myuser.objects.create_user('Gary', email='me#email.com')
current_time = now()
myuser.birthday = current_time
myuser.save()
first_user = Myuser.objects.all()[0]
self.assertEqual(first_user.birthday, current_time, 'first_user.birthday should be equal to the current_time')
Which will output something like this if the test fails:
AssertionError: <datetime 1> != <datetime 2> : first_user.birthday should be equal to the current_time
This is explained in Python's unittest docs.

The default error message does show the values that failed. But you have overridden that by supplying a third argument to assertEqual. If you left that out, it would print the values.
As Gonzalo shows, you can in fact get the best of both worlds by using the longMessage attribute.

Related

Having difficulty mocking object returned by patched function

I have an instance method on a Django form class that returns a Python object from a payment service if successful.
The object has an id property, which I then persist on a Django model instance.
I'm having some difficulty getting the mocked object to return its .id property correctly.
# tests.py
class DonationFunctionalTest(TestCase):
def test_foo(self):
with mock.patch('donations.views.CreditCardForm') as MockCCForm:
MockCCForm.charge_customer.return_value = Mock(id='abc123')
# The test makes a post request to the view here.
# The view being tested calls:
# charge = credit_card_form.charge_customer()
# donation.charge_id = charge.id
# donation.save()
However:
print donation.charge_id
# returns
u"<MagicMock name='CreditCardForm().charge_customer().id'
I expected to see "abc123" for the donation.charge_id, but instead I see a unicode representation of the MagicMock. What am I doing wrong?
Got it working by doing the patching a bit differently:
#mock.patch('donations.views.CreditCardForm.create_card')
#mock.patch('donations.views.CreditCardForm.charge_customer')
def test_foo(self, mock_charge_customer, mock_create_card):
mock_create_card.return_value = True
mock_charge_customer.return_value = MagicMock(id='abc123')
# remainder of code
Now the id matches what I expect. I'd still like to know what I did wrong on the previous code though.

calling a function to obtain a model field value

I'm trying to get a unique value for a field (unique within the db column).
my code (other model fields omitted):
class PlatformUserChildren(models.Model):
dashboard = models.CharField('dashboard URL', max_length=64, unique=True, default=createDashboardCode(self))
def createDashboardCode(self):
stringCheck = False
while stringCheck is False:
newString = str(uuid.uuid4())[:32]
doesStringExist = newString in self.dashboard
if not doesStringExist:
stringCheck = True
return newString
I'm getting name 'self' is not defined as an error.
What should I be passing to the function so that I can check the db column to ensure the value is unique -or- is there a built-in way of doing this?
What I've already tried or looked at:
setting unique=True for the field and using default=uuid.uuid4 - that gives me duplicate values and generates a validation error (SO link)
I'm aware of Django 1.8's UUID field, however i'm on 1.7
The problem lies in the following line (indented for better readability) as you already know and mentioned before:
dashboard = models.CharField(
'dashboard URL',
max_length=64,
unique=True,
default=createDashboardCode(self)
)
In this part:
default=createDashboardCode(self)
you're calling the method createDashboardCode with the argument self. This is wrong, because you never pass self to a method as it is passed by Python. Whenever you call the method createDashboardCode you should do it this way:
createDashboardCode()
That's it, you're not passing the argument self explicitly.
You're getting an error "name 'self' is not defined" because self is not defined. There is no variable self in your code that you can pass to the method.
Now we're one step further, but your problem won't be solved if you just apply this slight change to your code.
The return value from the method createDashboardCode will be assigned to default. That's not what you really want. You have to assign a reference of the method to default:
default = createDashboardCode
Pay attention to the missing brackets. This will call the method every time a new instance of the model is created
Define a function:
def my_function():
print "hello"
and run it in the Python interpreter:
# once like this
my_function()
# and again like this
my_function
and you'll see the difference. That should help you to better comprehend this issue.

Django: How to use django.forms.ModelChoiceField with a Raw SQL query?

I'm trying to render a form with a combo that shows related entities. Therefore I'm using a ModelChoiceField.
This approach works well, until I needed to limit which entities to show. If I use a simple query expression it also works well, but things break if I use a raw SQL query.
So my code that works, sets the queryset to a filter expression.
class ReservationForm(forms.Form):
location_time_slot = ModelChoiceField(queryset=LocationTimeSlot.objects.all(), empty_label="Select your prefered time")
def __init__(self,*args,**kwargs):
city_id = kwargs.pop("city_id") # client is the parameter passed from views.py
super(ReservationForm, self).__init__(*args,**kwargs)
# TODO: move this to a manager
self.fields['location_time_slot'].queryset = LocationTimeSlot.objects.filter(city__id = city_id )
BUT, if I change that to a raw query I start having problems. Code that does not work:
class ReservationForm(forms.Form):
location_time_slot = ModelChoiceField(queryset=LocationTimeSlot.objects.all(), empty_label="Select your prefered time")
def __init__(self,*args,**kwargs):
city_id = kwargs.pop("city_id") # client is the parameter passed from views.py
super(ReservationForm, self).__init__(*args,**kwargs)
# TODO: move this to a manager
query = """SELECT ts.id, ts.datetime_to, ts.datetime_from, ts.available_reserves, l.name, l.'order'
FROM reservations_locationtimeslot AS ts
INNER JOIN reservations_location AS l ON l.id = ts.location_id
WHERE l.city_id = %s
AND ts.available_reserves > 0
AND ts.datetime_from > datetime() """
time_slots = LocationTimeSlot.objects.raw(query, [city_id])
self.fields['location_time_slot'].queryset = time_slots
The first error I get when trying to render the widget is: 'RawQuerySet' object has no attribute 'all'
I could solve that one thanks to one of the commets in enter link description here, by doing:
time_slots.all = time_slots.__iter__ # Dummy fix to allow default form rendering with raw SQL
But now I'm getting something similar when posting the form:
'RawQuerySet' object has no attribute 'get'
Is there a proper way to prepare a RawQuerySet to be used by ModelChoiceField?
Thanks!
Are you sure you actually need a raw query there? Just looking at that query, I can't see any reason you can't just do it with filter(location__city=city_id, available_reserves__gte=0, datetime_from__gt=datetime.datetime.now()).
Raw query sets are missing a number of methods that are defined on conventional query sets, so just dropping them in place isn't likely to work without writing your own definitions for all those methods.
I temporarily fixed the problem adding the missing methods.
The way I'm currently using the ModelChoiceField I only needed to add the all() and get() methods, but in different scenarios you might need to add some other methods as well. Also this is not a perfect solution because:
1) Defining the get method this way migth produce incorrect results. I think the get() method is used to validate that the selected option is within the options returned by all(). The way I temporarily implemented it only validates that the id exists in the table.
2) I guess the get method is less performant specified this way.
If anyone can think of a better solution, please let me know.
So my temporary solution:
class LocationTimeSlotManager(models.Manager):
def availableSlots(self, city_id):
query = """SELECT ts.id, ts.datetime_to, ts.datetime_from, ts.available_reserves, l.name, l.'order'
FROM reservations_locationtimeslot AS ts
.....
.....
MORE SQL """
time_slots = LocationTimeSlot.objects.raw(query, [city_id])
# Dummy fix to allow default form rendering with raw SQL
time_slots.all = time_slots.__iter__
time_slots.get = LocationTimeSlot.objects.get
return time_slots

Why does get_FOO_display() return integer value when logging info (django)?

Why does get_FOO_display() return integer value when logging info (django)?
I have a model field that is using a choice to restrict its value. This works fine
and I have it working everywhere within the app, except when logging information,
when the get_FOO_display() method returns the underlying integer value instead
of the human-readable version.
This is the model definition (abridged):
THING_ROLE_MONSTER = 0
THING_ROLE_MUMMY = 1
ROLE_CHOICES = (
(THING_ROLE_MONSTER, u'Monster'),
(THING_ROLE_MUMMY, u'Mummy'),
)
# definition of property within model
class Thing(models.Model):
...
role = models.IntegerField(
'Role',
default=0,
choices=ROLE_CHOICES
)
If I run this within the (django) interactive shell it behaves exactly as you would expect:
>>> from frankenstein.core.models import Thing
>>> thing = Thing()
>>> thing.role = 0
>>> thing.get_role_display()
u'Monster'
However, when I use exactly the same construct within a string formatting / logging
scenario I get the problem:
logger.info('New thing: <b>%s</b>', thing.get_role_display())
returns:
New thing: <b>0</b>
Help!
[UPDATE 1]
When I run the logging within the interactive shell I get the correct output:
>>> from frankenstein.core.models import Thing
>>> import logging
>>> thing = Thing()
>>> thing.role = 0
>>> logging.info('hello %s', b.get_role_display())
INFO hello Monster
[UPDATE 2] Django internals
Following up on the answer from #joao-oliveira below, I have dug into the internals and uncovered the following.
The underlying _get_FIELD_display method in django.db.models looks like this:
def _get_FIELD_display(self, field):
value = getattr(self, field.attname)
return force_unicode(dict(field.flatchoices).get(value, value), strings_only=True)
If I put a breakpoint into the code, and then run ipdb I can see that I have the issue:
ipdb> thing.get_role_display()
u'1'
ipdb> thing._get_FIELD_display(thing._meta.get_field('role'))
u'1'
So, the fix hasn't changed anything. If I then try running through the _get_FIELD_display method code by hand, I get this:
ipdb> fld = thing._meta.get_field('role')
ipdb> fld.flatchoices
[(0, 'Monster'), (1, 'Mummy')]
ipdb> getattr(thing, fld.attname)
u'1'
ipdb> value = getattr(thing, fld.attname)
ipdb> dict(fld.flatchoices).get(value, value)
u'1'
Which is equivalent to saying:
ipdb> {0: 'Monster', 1: 'Mummy'}.get(u'1', u'1')
u'1'
So. The problem we have is that the method is using the string value u'1' to look up the corresponding description in the choices dictionary, but the dictionary keys are integers, and not strings. Hence we never get a match, but instead the default value, which is set to the existing value (the string).
If I manually force the cast to int, the code works as expected:
ipdb> dict(fld.flatchoices).get(int(value), value)
'Mummy'
ipdb> print 'w00t'
This is all great, but doesn't answer my original question as to why the get_foo_display method does return the right value most of the time. At some point the string (u'1') must be cast to the correct data type (1).
[UPDATE 3] The answer
Whilst an honourable mention must go to Joao for his insight, the bounty is going to Josh for pointing out the blunt fact that I am passing in the wrong value to begin with. I put this down to being an emigre from 'strongly-typed-world', where these things can't happen!
The code that I didn't include here is that the object is initialised from a django form, using the cleaned_data from a ChoiceField. The problem with this is that the output from a ChoiceField is a string, not an integer. The bit I missed is that in a loosely-typed language it is possible to set an integer property with a string, and for nothing bad to happen.
Having now looked into this, I see that I should have used the TypedChoiceField, to ensure that the output from cleaned_data is always an integer.
Thank you all.
I'm really sorry if this sounds condescending, but are you 100% sure that you're setting the value to the integer 1 and not the string '1'?
I've gone diving through the internals and running some tests and the only way that the issue you're experiencing makes sense is if you're setting the value to a string. See my simple test here:
>>> from flogger.models import TestUser
>>> t = TestUser()
>>> t.status = 1
>>> t.get_status_display()
u'Admin'
>>> t.status = '1'
>>> t.get_status_display()
u'1'
Examine your view code, or whatever code is actually setting the value, and examine the output of the field directly.
As you pasted from the internal model code:
def _get_FIELD_display(self, field):
value = getattr(self, field.attname)
return force_unicode(dict(field.flatchoices).get(value, value), strings_only=True)
It simply gets the current value of the field, and indexes into the dictionary, and returns the value of the attribute if a lookup isn't found.
I'm guessing there were no errors previously, because the value is coerced into an integer before being inserted into the database.
Edit:
Regarding your update mentioning the type system of python. Firstly, you should be using TypedChoiceField to ensure the form verifies the type that you expect. Secondly, python is a strongly typed language, but the IntegerField does its own coercing with int() when preparing for the database.
Variables are not typed, but the values within them are. I was actually surprised that the IntegerField was coercing the string to an int also. Good lessen to learn here - check the basics first!
Haven't tried your code, neither the #like-it answer sorry, but _get_FIELD_display from models.Model is curried in the fields to set the get_Field_display function, so thats probably why you'r getting that output
try calling the _get_FIELD_display:
logging.info('hello %s', b._get_FIELD_display(b._meta.get('role')))
try this:
class Thing(models.Model):
THING_ROLE_MONSTER = 0
THING_ROLE_MUMMY = 1
ROLE_CHOICES = (
(THING_ROLE_MONSTER, u'Monster'),
(THING_ROLE_MUMMY, u'Mummy'),
)
role = models.IntegerField('Role', default=0,choices=ROLE_CHOICES)

Django, randomization of "default" parameter of a model

I want to set the "default" value as a randomly generated String for the promotion_code part of my Promotion model, for that the code_generate function is used.
The issue with the code below that it seems like default=code_generate() generates this random string once every server start thus assigning the same value. I can see that by the admin panel, every time I try to generate a new Promotion, it gives me the exact same string.
#generate a string, which is not already existing in the earlier Promotion instances
def code_generate():
while 1:
from django.conf import settings
import random, string
prom_code = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(6))
try:
Promotion.objects.get(promotion_code=prom_code)
except:
return prom_code
class Promotion(models.Model):
purchase = models.ForeignKey('Purchase')
promotion_code = models.CharField(max_length=20,unique=True,default=code_generate())
How can I make it random ?
Regards
You need to pass a callable as default, not call the callable:
promotion_code = models.CharField(max_length=20,unique=True,default=code_generate)
As indicated in the other answer, the simplest way to get a random string is as follows:
str(random.random())[2:]
Altho' it is a string of numbers. Fair enough, until you would want to replace it eventually with sha.