Django test not actualizing object - django

I have a django rest framework test, it is just a wrapper over regular django tests that works exactly the same way. The code looks like this:
user_created = User.objects.create_user(first_name="Wally", username="farseer#gmail.com", password="1234",
email="farseer#gmail.com")
client_created = Client.objects.create(user=user_created, cart=cart)
data_client_profile["user"]["first_name"] = "Apoc"
response = self.client.put(reverse("misuper:client_profile"), data_client_profile, format="json")
client_created.refresh_from_db() # Tried this too
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(client_created.user.first_name, data_client_profile["user"]["first_name"])
So, I want to update the client_created object with some data in the dict data_client_profile, then assertEqual that the client.user.first_name is "Apoc".
Here is the code in the view, I added two pdb.set_trace() that will help more than just pasting all the code:
pdb.set_trace()
client_existing_user_obj.phone = phone
client_existing_user_obj.user.email = email
client_existing_user_obj.user.first_name = first_name # Updating here!
client_existing_user_obj.user.last_name = last_name
client_existing_user_obj.user.save()
client_existing_user_obj.save()
pdb.set_trace()
The first pdb break shows this:
(Pdb) client_existing_user_obj.user.username
u'farseer#gmail.com' # Make sure I'm updating the created object
(Pdb) client_existing_user_obj.user.first_name
u'Wally' # First name is not updated yet
The second pdb break shows this:
(Pdb) client_existing_user_obj.user.first_name
u'Apoc' # Looks like the first name has being updated!
But, when the test runs I get the error:
self.assertEqual(client_created.user.first_name, data_client_profile["user"]["first_name"])
AssertionError: 'Wally' != 'Apoc'
Why does it fail? I even call refresh_from_db(). I confirm it has being updated in the view, but then in the test it looks like it has not. I don't understand.

Note that the docs for refresh_from_db say that client_created.user will not be refreshed by client_created.refresh_from_db(), because client_created.user_id has stayed the same:
The previously loaded related instances for which the relation’s value is no longer valid are removed from the reloaded instance. For example, if you have a foreign key from the reloaded instance to another model with name Author, then if obj.author_id != obj.author.id, obj.author will be thrown away, and when next accessed it will be reloaded with the value of obj.author_id.
Therefore you need to refresh client_created.user:
client_created.user.refresh_from_db()
or refetch client_created yourself:
client_created = Client.objects.get(pk=client_created.pk)

It's the user you need to refresh from the database, since that's the object you're modifying:
user_created.refresh_from_db()

Related

pytest-django object not being persisted in test database

I am using pytest with pytest-django and pytest-bdd to test a simple django application.
The settings file defines a test sqlite3 database, which is created when the tests are run.
My first function creates a model object:
#given("A message exists")
def given_a_message_exists(transactional_db, message_text):
message = message_models.Message(text=message_text)
message.save()
But although I can open and inspect the database using sql-browser, the model is never persisted to the database. I can, in my next function, obtain the message from the django ORM, but the message isn't persisted in the database, and a call to a url route to delete the message returns a 404 with 'Message not found matching the query'.
#when("User visits the delete message page", target_fixture="page")
def user_visits_delete_message_page(transactional_db, browser, message_text):
message = message_models.Message.objects.get(text=message_text)
url = f"{browser.domain}/delete_message/{message.id}/{message.slug}/"
browser.visit(url)
return browser
When I run the site normally, everything works as expected.
Here are the fixtures from my conftest.py...
MESSAGE_TEXT = "Ipsum Lorum Dolum Est"
CREATE_MESSAGE_URL = reverse("django_messages:message_create")
LIST_MESSAGE_URL = reverse("django_messages:message_list")
LINKS_DICT = {
"create_message": f"a[href='{CREATE_MESSAGE_URL}']",
"list_message": f"a[href='{LIST_MESSAGE_URL}']",
}
PAGES_DICT = {
"create_message": CREATE_MESSAGE_URL,
"list_message": LIST_MESSAGE_URL,
}
#pytest.fixture()
def message_text():
return MESSAGE_TEXT
#pytest.fixture()
def browser(sb, live_server, settings):
staging_server = os.environ.get("STAGING_SERVER")
if staging_server:
sb.visit(staging_server)
else:
sb.visit(live_server)
sb.domain = sb.get_domain_url(sb.get_current_url())
settings.EMAIL_PAGE_DOMAIN = sb.domain
sb.pages = PAGES_DICT
sb.links = LINKS_DICT
return sb
Why is the model not persisted to the database when I call message.save()?
btw, I have tried, transactional_db, and db, along with a whole host of other permutations...
It turns out that using the browser fixture causes a problem that I don't fully understand yet. If I pass the browser into a step, and then return it as a target_fixture for later functions to consume then everything works as expected.
[EDIT]
I discovered that if I define the browser in a fixture, that I must have referenced that browser fixture before or at the same time as the django object using it, or the browser refers to a different domain.
So, in the below, even though the message is created in a fixture (test_message), I must refer to it having referenced the browser. If the function message_exists is not passed 'browser', then the message is not listed on the message_list page.
def message_exists(browser, test_message):
test_message.save()
return test_message
#when("User visits the message list page", target_fixture="page")
def user_visits_messages_page(db, browser):
browser.visit(browser.domain + browser.pages["list_message"])
return browser
#then("The message is listed")
def message_is_listed(message, page):
page.assert_element(f"a[href='/{message.id}/{message.slug}/']")

Problem with Django Tests and Trigram Similarity

I have a Django application that executes a full-text-search on a database. The view that executes this query is my search_view (I'm ommiting some parts for the sake of simplicity). It just retrieve the results of the search on my Post model and send to the template:
def search_view(request):
posts = m.Post.objects.all()
query = request.GET.get('q')
search_query = SearchQuery(query, config='english')
qs = Post.objects.annotate(
rank=SearchRank(F('vector_column'), search_query) + TrigramSimilarity('post_title', query)
).filter(rank__gte=0.15).order_by('-rank'), 15
)
context = {
results = qs
}
return render(request, 'core/search.html', context)
The application is working just fine. The problem is with a test I created. Here is my tests.py:
class SearchViewTests(TestCase):
def test_search_without_results(self):
"""
If the user's query did not retrieve anything
show him a message informing that
"""
response = self.client.get(reverse('core:search') + '?q=eksjeispowjskdjies')
self.assertEqual(response.status_code, 200)
self.assertContains(response.content, "We didn\'t find anything on our database. We\'re sorry")
This test raises an ProgrammingError exception:
django.db.utils.ProgrammingError: function similarity(character varying, unknown) does not exist
LINE 1: ...plainto_tsquery('english'::regconfig, 'eksjeispowjskdjies')) + SIMILARITY...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
I understand very well this exception, 'cause I got it sometimes. The SIMILARITY function in Postgres accepts two arguments, and both need to be of type TEXT. The exception is raising because the second argument (my query term) is of type UNKNOWN, therefore the function won't work and Django raises the exception. And I don't understand why, because the actual search is working! Even in the shell it works perfectly:
In [1]: from django.test import Client
In [2]: c = Client()
In [3]: response = c.get(reverse('core:search') + '?page=1&q=eksjeispowjskdjies')
In [4]: response
Out[4]: <HttpResponse status_code=200, "text/html; charset=utf-8">
Any ideas about why test doesn't work, but the actual execution of the app works and console test works too?
I had the same problem and this how I solved it in my case:
First of all, the problem was that when Django creates the test database that it is going to use for tests it does not actually run all of your migrations. It simply creates the tables based on your models.
This means that migrations that create some extension in your database, like pg_trgm do not run when creating the test database.
One way to overcome this is to use a fixture in your conftest.py file which will make create said extensions before any tests run.
So, in your conftest.py file add the following:
# the following fixture is used to add the pg_trgm extension to the test database
#pytest.fixture(scope="session", autouse=True)
def django_db_setup(django_db_setup, django_db_blocker):
"""Test session DB setup."""
with django_db_blocker.unblock():
with connection.cursor() as cursor:
cursor.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;")
You can of course replace pg_trgm with any other extension you require.
PS: You must make sure the extension you are trying to use works for the test database you have chosen. In order to change the database used by Django you can change the value of
DATABASES = {'default': env.db('your_database_connection_uri')}
in your application's settings.py.

How do I get pytest to submit a flask wtforms form correctly

A few of my tests are failing repeatedly so I'm using the shortest one as an example for this question
test
def test_delete_char(self):
with self.client:
start = characters.query.count() #num of entries in db
self.client.post(
url_for("delete", identity=1),
follow_redirects=True)
end = characters.query.count() #num of entries in db after deletion
self.assertTrue(start > end) #check if delete has gone through
route
#app.route("/delete/<identity>", methods=["GET", "POST"])
def delete(identity):
form = DeleteForm()
if form.validate_on_submit():
char = characters.query.filter_by(id=identity).first()
db.session.delete(char)
db.session.commit()
return redirect(url_for("home"))
return render_template("delete.html", form=form, identity=identity, title="Delete")
When running this function on the website manually there are no issues. Using pytest I can see that it doesn't get past if form.validate_on_submit(): in the delete function. I can only conclude that my self.client.post is wrong but I have no idea what the issue is. Any help would be appreciated
I found the issue. In my setUp function I had set WTF_CSRF_ENABLE=False instead of WTF_CSRF_ENABLED=False. this meant it was looking for a CSRF token that wasn't provided whenever a wtforms containing function was tested. Moral of the story is don't use vim/an editor without error highlighting

Django object "lock" and context rendering

I have a simple(I think) question, about Django context rendering.
I'll step right into it -
Basically what I need is, some temp table, which in my case, I called Locked. And when a user presses a button, which Is a form, that object goes straight to the table Locked(just a simple insert). And inside that table there is a field called is_locked, and if its True, that object needs to go gray, or to have some lock icon inside the html table.
Just some kind of a viewable sign, that an object is inside the table Locked, and that another user can't access it.
But, my problem is, since in my views.py, my lock function is not returning exact html where I want to render that locker icon, instead, it returns another html.
Is there any way, to render same context, on 2 html pages? Thank's.
This is my code :
views.py
def lock(request, pk):
# Linking by pk.
opp = get_object_or_404(OpportunityList, pk=pk)
opp_locked = get_object_or_404(Locked, pk=pk)
# Taking two parametters for 2 fields.
eluid = Elementiur.objects.get(eluid=pk)
user = User.objects.get(username=request.user)
# Dont bother with this one! Just pulling one field value.
field_name = 'optika_korisnik'
obj = OpportunityList.objects.get(pk=pk)
field_object = OpportunityList._meta.get_field(field_name)
field_value = getattr(obj, field_object.attname)
# This is the main part! This is where i'm inserting data into Locked table.
if opp_locked.DoesNotExist:
opp_locked.id = int(eluid.eluid)
opp_locked.locked_eluid = eluid
opp_locked.locked_comment = field_value
opp_locked.locked_user = user
opp_locked.locked_name = 'Zaključao korisnik - ' + request.user.username
opp_locked.is_locked = True
opp_locked.save()
# This is what has to be returned, but i need context on the other page.
return render(request, 'opportunity/detalji/poziv.html',
{'opp': opp, 'locked': opp_locked})
else:
# This return has the context that i need(from the first opp_locked variable)
return render(request, 'opportunity/opp_optika.html', {'locked_test': opp_locked})
I can provide more code, but i think that it's not important for this type of question, because all of the logic is happening inside the lock finction, and last two returns.
I just had a quick overview of your snippet sorry if this not help you but you need to review it a little bit.
You call DoesNotExist on an instance of a Locked model
if opp_locked.DoesNotExist: [...]
that's not how you should use this exception.
You have a method .exists() that is available but only for Querysets.
Also if your instance does not exists you are alredy returning an Http404 response when you use get_object_or_404() method.
And perhaps you should avoid sharing primary keys between instances and replace them with models.OneToOneField (OneToOnefield)
Since i got no answers, i added a new field, is_locked, into my Locked model and that solved it.

Test case data stays in Django development database Selenium

I have a simple form for registering new user. I wrote a test case for it. It looks as follows:
class AccountTestCase(LiveServerTestCase):
def setUp(self):
self.selenium = webdriver.Firefox()
super(AccountTestCase, self).setUp()
def tearDown(self):
self.selenium.quit()
super(AccountTestCase, self).tearDown()
def test_register(self):
selenium = self.selenium
#Opening the link we want to test
selenium.get('http://localhost:8000/register/')
#find the form element
first_name = selenium.find_element_by_id('id_first_name')
last_name = selenium.find_element_by_id('id_last_name')
username = selenium.find_element_by_id('id_username')
email = selenium.find_element_by_id('id_email')
password1 = selenium.find_element_by_id('id_password1')
password2 = selenium.find_element_by_id('id_password2')
submit = selenium.find_element_by_id('btn_signup')
#Fill the form with data
first_name.send_keys('abc')
last_name.send_keys('abc')
username.send_keys('abc')
email.send_keys('abc#gmail.com')
password1.send_keys('abcabcabc')
password2.send_keys('abcabcabc')
#submitting the form
submit.send_keys(Keys.RETURN)
#check the returned result
self.assertTrue('Success!' in selenium.page_source)
When I ran the test case for the first time it passed with flying colors, but on the second run it failed.
After little investigation I realized a user with credentials from test cases is already created in my database. Thus, when I ran test case for second time it failed to create a new user, as user with these details is already present. (I can see user from Django admin).
I believe this is not the expected behaviour of LiveServerTestCase(or any type of test case). In setup, a temporary database is created, test case is ran on it, and destroyed in tearDown phase.
I want to know if, this is the intended behavior? if not why this is happening ? How can I avoid doing it ?
I have not made any changes in settings.py which are related to selenium or testing (is there a flag or something that needs to be set?). Also, I need to keep the server running for this to work (is this normal?) .
As pointed by #Paulo Almeida :
I was using a wrong url. The URL that I should be using is self.live_server_url . Since I was using http://localhost:8000/register/ It was expecting server to be running and creating records there.
Thanks.