How to test clean_<fieldname> method? - django

I try to write a test for my clean_ method.
Here is the code for my test
def test_clean_restraints(self):
form = NewTaskForm(dict(restraints="90 20 <>"))
form.clean_restraints()
At this step I receive an error:
Error
Traceback (most recent call last):
File "/home/user/django_projects/my_webservice/tasks/tests/test_forms.py", line 12, in test_clean_restraints
form.clean_restraints()
File "/home/user/django_projects/my_webservice/tasks/forms.py", line 22, in clean_restraints
if self.cleaned_data.get('restraints') == '':
AttributeError: 'NewTaskForm' object has no attribute 'cleaned_data'
NewTaskForm looks like this:
class NewTaskForm(ModelForm):
class Meta:
model = Task
restraints = forms.CharField()
region = forms.CharField()
interactions = forms.CharField()
def clean_restraints(self):
if self.cleaned_data.get('restraints') == '':
return self.cleaned_data.get('restraints')
data = self.cleaned_data.get('restraints').strip().split('\n')
regexp = re.compile(r'^(\d+)[\t ]+(\d+)[ \t]+([><]{2})?$')
cleaned_data = []
for i, line in enumerate(data):
match = regexp.match(line)
if not match:
raise forms.ValidationError(f"Error in restraints in line {i + 1}")
else:
rst_1, rst_2, loop_type = match.groups()
rst_1 = int(rst_1)
rst_2 = int(rst_2)
cleaned_data.append((rst_1, rst_2, loop_type))
return cleaned_data
I'm using Django 2.1, python 3.7.1, PyCharm 2018.3.3 Professional
I tried to run it under debugger in PyCharm but things goes crazy. I receive different error message. It looks like debugger stopped after full form validation ignoring breakpoints. I have no idea what is going on.

You should test the results of the validation process.
form = NewTaskForm(dict(restraints="90 20 <>"))
self.assertFalse(form.is_valid())
self.assertEqual(form.errors['restraints'], "Error in restraints in line 1")

Ok, I found what was wrong.
form.cleaned_data is created in full_clean(). Not in constructor as I thought. It also calls every clean_fieldname(). So the ugly workaround is something like this:
def test_clean_restraints(self):
initial_data = dict(restraints="90 20 <>")
form = NewTaskForm()
form.cleaned_data = initial_data
form.clean_restraints()
(...)

Related

Django Rest Framework and Channels, You cannot call this from an async context

What i am trying to do is to nest a DRF model serializer into another model serialiser's field like so
class username_serial(ModelSerializer):
class Meta:
model = User
fields = ['username','email']
class game_serial(ModelSerializer):
user_01 = username_serial()
class Meta:
model = game
fields = ['id','user_01','user_02','is_private','is_accepted']
Error :
Exception inside application: You cannot call this from an async
context - use a thread or sync_to_async. Traceback (most recent call
last): File
"C:\Users\baza\Desktop\production\venv\lib\site-packages\django\db\models\fields\related_descriptors.py",
line 173, in get
rel_obj = self.field.get_cached_value(instance) File "C:\Users\baza\Desktop\production\venv\lib\site-packages\django\db\models\fields\mixins.py",
line 15, in get_cached_value
return instance._state.fields_cache[cache_name] KeyError: 'user_01'
This works normally without Django Chennels because channels is async and i can't use sync code with, works fine by using:
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
In the settings file but it's not a safe approach when it comes to production.
i tried using channel's database_sync_to_async as a decorator and as well as a function with a SerializerMethodField like so:
class game_serial(ModelSerializer):
user_01 = SerializerMethodField(read_only=True)
#database_sync_to_async
def get_user_01(self, obj):
username = obj.user_01.username
return str(username)
class Meta:
model = game
fields = ['id','user_01','user_02','is_private','is_accepted']
but i get back:
[OrderedDict([('id', 23), ('user_01', <coroutine object SyncToAsync.__call__ at 0x0000000005DF6678>), ('user_02', None), ('is_private', False), ('is_accepted', False)]), OrderedDict([('id', 24), ('user_01', <coroutine object SyncToAsync.__call__ at 0
x0000000005DF6D58>), ('user_02', None), ('is_private', False), ('is_accepted', False)])]
with an
Exception inside application: Object of type 'coroutine' is not JSON
serializable
so i guess that JSON couldn't serialize those "coroutine" objects and normally i should or "want" to get the actual values of them instead.
How can i do the task? any workaround or other methods are welcomed, thank you in advance.
in consumers.py ..
games = await database_sync_to_async(self.get_games)()
serialized_games = game_serial(games, many=True)
await self.send({
"type": "websocket.send",
'text': json.dumps(serialized_games.data)
})
def get_games(self):
return list(game.objects.all())
I never used Django Channels but I know Django and async.
I'll be honest, I don't like these hacky decorators. And I don't think running such a simple task in a thread is a good idea.
You have an obj, so you were able to query the DB earlier. If so, there's a place without async context or async context where accessing the DB works. In the error message user_01 is not found in the "cached" object from the DB. So just prefetch what you need before.
def get_queryset(self):
return game_serial.objects.select_related('user_01')
class game_serial(ModelSerializer):
user_01 = serializers.CharField(source='user_01.username')
This way you don't have problems with this sync-to-async magic, it's more efficient and easier to reason about.
EDIT:
I repeat, you should select related where you fetch the data. After you added another example, I can suggest something like that
def get_games(self):
return list(game.objects.select_related('user_01').all())
and it will work just fine.
You can also try
#database_sync_to_async
def get_games(self):
return list(game.objects.select_related('user_01').all())
and
serialized_games = await game_serial(games, many=True)
In both cases this serializer will work just fine.
class game_serial(ModelSerializer):
user_01 = serializers.CharField(source='user_01.username')
class Meta:
model = game
fields = ['id','user_01','user_02','is_private','is_accepted']

Mock patch results in error -- TypeError: object() takes no parameters in python 3

This is a bit complicated because I'm debugging some code written a long time ago in python 2.7
In progress of migrating to Python 3 (I know, I know) and facing this problem when trying to fix unit tests
The problem is I'm getting an error TypeError: object() takes no parameters
I'll list the functions below. I have to replace a lot of names of functions and objects. If you see an inconsistency in module names, assume it's typo.
First the class it's calling
class Parser(object):
def __init__(self, some_instance, some_file):
self._some_instance = some_instance
self.stream = Parser.formsomestream(some_file)
self.errors = []
#staticmethod
def formsomestream(some_file):
# return a stream
class BetterParser(Parser):
def parse(self):
# skip some steps, shouldn't relate to the problem
return details # this is a string
class CSVUploadManager(object):
def __init__(self, model_instance, upload_file):
self._model_instance = model_instance
self._upload_file = upload_file
# then bunch of functions here
# then.....
def _parse(self):
parser_instance = self._parser_class(self._model_instance, self._upload_file)
self._csv_details = parser_instance.parse()
# bunch of stuff follows
def _validate(self):
if not self._parsed:
self._parse()
validator_instance = self._validator_class(self._model_instance, self._csv_details)
# some attributes to set up here
def is_valid(self):
if not self._validated:
self._validate()
Now the test function
from somewhere.to.this.validator import MockUploadValidator
from another.place import CSVUploadManager
class TestSomething(SomeConfigsToBeMixedIn):
#mock.patch('path.to.BetterParser.parse')
#mock.patch('path.to.SomeValidator.__new__')
#mock.patch('path.to.SomeValidator.validate')
def test_validator_is_called(self, mock_validator_new, mock_parse):
mock_validator_new.return_value = MockUploadValidator.__new__(MockUploadValidator)
mock_parse.return_value = mock_csv_details
mock_validator_new.return_value = MockUploadValidator()
string_io = build_some_string_io_woohoo() # this returns a StringIO
some_file = get_temp_from_stream(string_io)
upload_manager = CSVUploadManager(a_model_instance, some_file)
upload_manager.is_valid() # this is where it fails and produces that error
self.assertTrue(mock_parse.called)
self.assertTrue(mock_validator_new.called)
validator_new_call_args = (SomeValidator, self.cash_activity, mock_csv_details)
self.assertEqual(mock_validator_new._mock_call_args_list[0][0], validator_new_call_args)
As you can see, the CSVUploadManager takes in the a django model instance and a file-like obj, this thing will trigger self._parser_class which calls BetterParser, then BetterParser does its things.
However, I'm guessing it's due to the mock, it returns TypeError: object() takes no parameters
My questions:
Why would this error occur?
Why only happening on python 3.x? (I'm using 3.6)
This also causes other tests (in different testcases) to fail when they would normally pass if I don't test them with the failed test. Why is that?
Is it really related to mocking? I'd assume it is because when I test on the server, the functionality is here.
EDIT: adding Traceback
Traceback (most recent call last):
File "/path/to/lib/python3.6/site-packages/mock/mock.py", line 1305, in patched
return func(*args, **keywargs)
File "/path/to/test_file.py", line 39, in test_validator_is_called:
upload_manager.is_valid()
File "/path/to/manager.py", line 55, in is_valid
self._validate()
File "/path/to/manager.py", line 36, in _validate
validator_instance = self._validator_class(self._model_instance, self._csv_details)
TypeError: object() takes no parameters
There should be 3 mock arguments, except self.
Like this:
#mock.patch('path.to.BetterParser.parse')
#mock.patch('path.to.SomeValidator.__new__')
#mock.patch('path.to.SomeValidator.validate')
def test_validator_is_called(self, mock_validate, mock_validator_new, mock_parse):
...

TypeError: object is not iterable when creating object

probably it's too late because I totaly do not understand this error. I created two new classes in models.py:
class SuggestionEmailSent(models.Model):
user = models.OneToOneField(User, related_name='suggestion_sent')
frequency = models.CharField(max_length=10, choices=EMAIL_FREQUENCY_CHOICES, default=EMAIL_FREQUENCY_CHOICES[0][0])
date = models.DateField(default=timezone.now)
class Meta:
unique_together = ("user", "date")
class SuggestionEmailContent(models.Model):
percentage = models.IntegerField()
buy_stock = models.ForeignKey('stocks.Stock', related_name='suggestion_email_buy_stock')
sell_stock = models.ForeignKey('stocks.Stock', related_name='suggestion_email_sell_stock')
portfolio = models.OneToOneField('portfolio.Portfolio', unique=True)
suggestion_sent = models.ForeignKey(SuggestionEmailSent, related_name='contents')
And then I have a code:
try:
content = user.suggestion_sent.contents.get(portfolio=portfolio)
print content.sell_stock
except ObjectDoesNotExist: #mail not sent for this portfolio, send and save
content, created = SuggestionEmailContent.objects.create(percentage=percentage,
buy_stock=suggestion,
sell_stock=rank,
portfolio=portfolio,
suggestion_sent=user.suggestion_sent)
And this is error traceback:
Traceback (most recent call last):
File "./test.py", line 49, in <module>
send_suggestion_email(User.objects.get(id=1))
File "/var/www/django/digrin/wsgi/digrin/suggestion/utils.py", line 192, in send_suggestion_email
suggestion_sent=user.suggestion_sent)
TypeError: 'SuggestionEmailContent' object is not iterable
What does this mean? Error fires up when ObjectDoesNotExist and I want to create new object SuggestionEmailContent. user.suggestion_set is of type <class 'suggestion.models.SuggestionEmailSent'> as it should be. What am I missing? I am using django 1.8
Edit1:
Here is my test.py:
if __name__ == '__main__':
from suggestion.utils import *
send_suggestion_email(User.objects.get(id=1))
and this is my send_suggestion_email:
def send_suggestion_email(user):
percentage = 100
for portfolio in Portfolio.objects.filter(testing=False, user=user):
dividends, monthly_shares = get_portfolio_month_shares(portfolio)
shares_price = get_portfolio_current_year_price(monthly_shares)
suggestions, ranks = get_suggestion_data(portfolio=portfolio, shares=monthly_shares)
if not suggestions or not ranks:
print "no suggestions nor ranks for portfolio" + str(portfolio.id)
continue
suggestion, rank = suggestions.keys()[0], ranks.keys()[0]
try:
content = user.suggestion_sent.contents.get(portfolio=portfolio)
print content.sell_stock
except ObjectDoesNotExist: #mail not sent for this portfolio, send and save
content, created = SuggestionEmailContent.objects.create(percentage=percentage,
buy_stock=suggestion,
sell_stock=rank,
portfolio=portfolio,
suggestion_sent=user.suggestion_sent)
create only returns the created instance instead of (instance, created), so your assignment tries to unpack it.
get_or_create on the other hand does return (instance, created).

Include django logged user in django Traceback error

What is the easist way to include username, first and last name and e-amil in django Traceback error.
I know that the way is create a custom error report:
Create a new class that innherit from django.views.debug.SafeExceptionReporterFilter
Set DEFAULT_EXCEPTION_REPORTER_FILTER
But, what method a should overwrite to receive traceback with also this information?
I would like that my treceback look likes:
Traceback (most recent call last):
File "/usr...o/core/handlers/base.py", line 89, in get_response
response = middleware_method(request)
File "/.../g...ap/utils/middleware.py", line 23,...
if elapsedTime.min > 15:
TypeError: can't compare datetime.timedelta to int
Logged user information:
User: pepito
name: Pepito Grillo
e-mail: grillo#peppeto.com
I did it using Custom Middleware. I'm not sure this is the best answer, but it is how I solved it for my project.
settings.py:
MIDDLEWARE_CLASSES = (
...
'utilities.custom_middleware.CustomMiddleware',
...
)
utilities/custom_middleware.py:
from utilities.request import AddRequestDetails
class CustomMiddleware(object):
"""
Adds user details to request context during request processing, so that they
show up in the error emails. Add to settings.MIDDLEWARE_CLASSES and keep it
outermost(i.e. on top if possible). This allows it to catch exceptions in
other middlewares as well.
"""
def process_exception(self, request, exception):
"""
Process the request to add some variables to it.
"""
# Add other details about the user to the META CGI variables.
try:
if request.user.is_authenticated():
AddRequestDetails(request)
request.META['AUTH_VIEW_ARGS'] = str(view_args)
request.META['AUTH_VIEW_CALL'] = str(view_func)
request.META['AUTH_VIEW_KWARGS'] = str(view_kwargs)
except:
pass
utilities/request.py:
def AddRequestDetails(request):
"""
Adds details about the user to the request, so any traceback will include the
details. Good for troubleshooting; this will be included in the email sent to admins
on error.
"""
if request.user.is_anonymous():
request.META['AUTH_NAME'] = "Anonymous User"
request.META['AUTH_USER'] = "Anonymous User"
request.META['AUTH_USER_EMAIL'] = ""
request.META['AUTH_USER_ID'] = 0
request.META['AUTH_USER_IS_ACTIVE'] = False
request.META['AUTH_USER_IS_SUPERUSER'] = False
request.META['AUTH_USER_IS_STAFF'] = False
request.META['AUTH_USER_LAST_LOGIN'] = ""
else:
request.META['AUTH_NAME'] = str(request.user.first_name) + " " + str(request.user.last_name)
request.META['AUTH_USER'] = str(request.user.username)
request.META['AUTH_USER_EMAIL'] = str(request.user.email)
request.META['AUTH_USER_ID'] = str(request.user.id)
request.META['AUTH_USER_IS_ACTIVE'] = str(request.user.is_active)
request.META['AUTH_USER_IS_SUPERUSER'] = str(request.user.is_superuser)
request.META['AUTH_USER_IS_STAFF'] = str(request.user.is_staff)
request.META['AUTH_USER_LAST_LOGIN'] = str(request.user.last_login)
My trivial solution (works in django 1.5)
settings.py:
MIDDLEWARE_CLASSES = (
...
'utilities.custom_middleware.UserTracebackMiddleware',
...
)
custom_middleware.py:
class UserTracebackMiddleware(object):
"""
Adds user to request context during request processing, so that they
show up in the error emails.
"""
def process_exception(self, request, exception):
if request.user.is_authenticated():
request.META['AUTH_USER'] = unicode(request.user.username)
else:
request.META['AUTH_USER'] = "Anonymous User"
hope it helps

Django and nosetests : DoesNotExist: <object> matching query does not exist

I'm just starting a small app on django. Its aim, for now, is just to manage testers (aka users) and teams. here's my model.py :
from django.db import models
class Team(models.Model):
name = models.CharField(max_length=200)
def __unicode__(self):
return self.name
def get_testers(self):
return self.tester_set.all()
class Tester(models.Model):
team = models.ForeignKey(Team)
visa = models.CharField(max_length=3)
privileged = models.BooleanField()
def __unicode__(self):
return self.visa
I'm trying to write a test for the "get_testers" function.
Here it is :
from models import Team, Tester
def testTeamGetTesters_test():
t = list(Team.objects.get(id=2L).get_testers())
a = Tester(visa = 'a', privileged = True)
b = Tester(visa = 'b', privileged = True)
assert(t[0].visa == a.visa and t[0].privileged == a.privileged and t[1].visa == b.visa and t[1].privileged == b.privileged)
But when I run :
$ python manage.py test tmg
I get this error :
nosetests --verbosity 1 tmg
E
======================================================================
ERROR: tempsite.tmg.tests.testTeamGetTesters_test
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/pymodules/python2.6/nose/case.py", line 183, in runTest
self.test(*self.arg)
File "/home/charlie/code/tempsite/../tempsite/tmg/tests.py", line 8, in testTeamGetTesters_test
t = list(Team.objects.get(id=2L).get_testers())
File "/usr/lib/pymodules/python2.6/django/db/models/manager.py", line 132, in get
return self.get_query_set().get(*args, **kwargs)
File "/usr/lib/pymodules/python2.6/django/db/models/query.py", line 341, in get
% self.model._meta.object_name)
DoesNotExist: Team matching query does not exist.
So, I wrote just about the same test, but directly runable :
from models import Team, Tester
t = list(Team.objects.get(id=2L).get_testers())
a = Tester(visa = 'a', privileged = True)
b = Tester(visa = 'b', privileged = True)
print "%r" % (t[0].visa == a.visa and t[0].privileged == a.privileged and t[1].visa == b.visa and t[1].privileged == b.privileged)
...And when I run it :
$ python tests.py
True
This is very confusing... I checked the database, the objects are all perfectly retrieved, but I still get this error...
Are you creating the Team object with id 2 somewhere in your test? Don't forget tests start with a blank database.