Test not passing in Django when it should - python

I just started with Django and thought maybe someone could enlighten me about a simple test I wrote that should pass but obviously doesn't. Well, I say it should but being pretty new to Django I guess my lack of knowledge is the reason it fails.
I extended the django User model with another model and a one to one relationship, as suggested in the docs, to add a field to the model. That model, namely UserStep, simply contains an Integer that starts at zero. I decided to extend the manager too to implement a method named setUserStep that set the step to a new value if the condition is met.
models.py
class UserStepManager(models.Manager):
def setUserStep(self, user, conditional_step, step):
if user.userstep.step == conditional_step:
user.userstep.step = step
user.userstep.save()
class UserStep(models.Model):
"""This model holds the user step count"""
user = models.OneToOneField(User)
step = models.IntegerField(default=0)
# we set the custom manager
objects = UserStepManager()
Then when the User signs up, a new UserStep object gets created.
models.py
#receiver(user_signed_up)
def complete_social_signup(sender, **kwargs):
"""We start step to 0"""
user = kwargs.pop('user')
us = UserStep(user=user)
us.save()
I use the method setUserStep defined in the manager in one of my views that gets called when a user requests /phase1/step1/ URL. So, if the current step is zero, set it to 1. As simple as that.
views.py
#login_required
def step1(request):
context_dict = {}
UserStep.objects.setUserStep(request.user, 0, 1)
return render(request, 'phase1/step1.html', context_dict)
I tested that behavior by hand by signing up and calling that view, and everything worked as expected. When I check the database, user.userstep.step is set to 1.
Then, being a good boy, I decided I would start writing some tests (I'm not at the point where I write the tests first yet, I should write tests to test my tests before that :P).
tests.py
class MyTests(TestCase):
def setUp(self):
self.test_user = User.objects.create_user(username="test",
email="test#test.com",
password="test")
# Mock user_signed_up signal.
us = UserStep(user=self.test_user)
us.save()
self.c = Client()
def test_step_gets_incremented(self):
self.c.login(username="test", password="test")
response = self.c.get('/phase1/step1/')
# user is logged in, the test should pass
self.assertEqual(response.status_code, 200)
current_user_step = self.test_user.userstep.step
# Test doesn't pass. WHY ME
self.assertEqual(current_user_step, 1)
AND BOOM AssertionError: 0 != 1
I'm even doing a simple print in my view method and it gets called as it should.
Thanks!

You have to reload test_user from the database since changing of the user is performed to another user instance. That means if:
u1 = User.objects.get(id = 1)
u2 = User.objects.get(id = 1)
changes to u1 (even if saved) are not mirrored to u2. So you have to get again the user from the db after the request.

Related

Django TestCase client.logout() is not logging out user correctly

I'm writing some tests for Django. I'm having trouble with the client.logout() method, which doesn't seem to be logging the test client out.
Here's my setUp():
def setUp(self):
# Set up a user to use with all of the tests and log in the user.
User.objects.create_user(username='temporary', password='temporary')
self.client.login(username='temporary', password='temporary')
# Create second user for later use.
User.objects.create_user(username='temporary2', password='temporary2')
And here's my test method:
def test_user_can_only_access_own_recipes(self):
"""
A user should only be able to access recipes they have created.
"""
# temporary1 user already logged in via setUp().
user_1_recipe = self.create_recipe()
self.client.logout()
# Login second user and create recipe.
self.client.login(username='temporary2', password='temporary2')
user_2_recipe = self.create_recipe()
# Response_owned is a recipe made by user 2, who is still logged in, so the recipe should
be viewable.
# Response_not_owned is recipe made by user 1 and should not be viewable as they are
logged out.
response_owned = self.client.get(
reverse('recipes:display_recipe', args=(user_2_recipe.id,)))
response_not_owned = self.client.get(
reverse('recipes:display_recipe', args=(user_1_recipe.id,)))
self.assertEqual(response_not_owned.status_code, 403)
# Convert to str, otherwise Django compares 'temporary' with just temporary no quotes.
self.assertEqual(str(user_1_recipe.user), 'temporary')
self.assertEqual(user_2_recipe.user, 'temporary2')
self.assertEqual(response_owned.status_code, 200)
This fails with the assertion error:
self.assertEqual(user_2_recipe.user, 'temporary2')
AssertionError: <User: temporary> != 'temporary2'
So my test should create a recipe owned by user 1. User 1 should be logged out, and user 2 is then logged in, and a recipe is created owned by user 2. The test should check whether the second user can access their own recipe, and can't access the first user's recipe. Trouble is, only the first user seems to be logged in, as the assertion error states that the second recipe is still owned by the first user. I don't think logout() is logging out the first user at all. Any help with this would be greatly appreciated.
EDIT:
Please see the create_recipe() method below. After comments from Iain Shelvington it's become apparent that I had been adding the same user 'temporary' to every recipe created by the create_recipe method, by including this line:
user = User.objects.get(username='temporary'),
The amended method taking a username as an argument looks as follows, and the test can now be modified to allow the post to be from any user you like.
def create_recipe(self, recipe_name='Test Recipe', username='temporary'):
"""
Creates a recipe with default values and a modifiable name to be
used in tests.
"""
recipe = RecipeCard.objects.create(
user = User.objects.get(username=username),
# REMAINDER OF CODE
ORIGINAL CODE:
def create_recipe(self, recipe_name='Test Recipe'):
"""
Creates a recipe with default values and a modifiable name to be
used in tests.
"""
# Object is receiving default values that can be changed when method
# is used
recipe = RecipeCard.objects.create(
user = User.objects.get(username='temporary'),
recipe_name=recipe_name,
source = 'Test Source',
servings = 3,
active_time_hours = 2,
active_time_minutes = 15,
total_time_hours = 2,
total_time_minutes = 15,
recipe_description = 'Test Description')
recipe.save()
ingredient = Ingredient.objects.create(
ingredient_name = 'Test Ingredient',
quantity = 1,
recipe = recipe)
ingredient.save()
method = MethodStep.objects.create(
step = 'Test Step',
recipe = recipe)
method.save()
# Make sure you return the created recipe object for use when method
# is called in tests.
return recipe

Django custom decorator user_passes_test() can it obtain url parameters?

Can Django's user_passes_test() access view parameters?
For example I have view that receives an id to retrieve specific record:
def property(request, id):
property = Property.objects.get(id=int(id))
The record has a field named user_id that contains the id for user that originally created record. I want users to be able to view only their own records otherwise be redirected.
I'd like to use a custom decorator which seems simple and clean.
For custom decorator is there some variation of something like this that will work?
#user_passes_test(request.user.id = Property.objects.get(id=int(id)).id, login_url='/index/')
def property(request, id):
property = Property.objects.get(id=int(id))
I have tried creating separate test_func named user_is_property_owner to contain logic to compare current user to property record user_id
#user_passes_test(user_is_property_owner(id), login_url='/index/')
def property(request, id):
property = Property.objects.get(id=int(id))
def user_is_property_owner(property_id):
is_owner = False
try:
Property.objects.filter(id=property_id, user_id=user_id).exists()
is_owner = True
except Property.DoesNotExist:
pass
But having trouble getting current user id and the property id from the request into the user_is_property_owner decorator function.
EDIT to add solution I was using. I did test inside each view test was required. It is simple. i thought using a decorator might be prettier and slightly more simple.
def property(request, id):
# get object
property = Property.objects.get(id=int(id))
# test if request user is not user id on property record
if request.user.id != property.user_id:
# user is not same as property user id so redirect to index
return redirect('index')
# rest of the code if request user is user_id on property record
# eg it is ok to let user into view
Typically, (using class based views), I'll handle this in the get_queryset method so it would be something like
class PropertyDetail(DetailView):
def get_queryset(self):
return self.request.user.property_set.all()
and that will give a 404 if the property isn't for the current user. You might prefer to use a project like django-guardian if you end up with more permission relationships than just Property.
If you take a look at UserPassesTestMixin you'll see that it processes the test_func before calling dispatch so you'll have to call self.get_object(request) yourself if you decide to go that route.

How to test a function for different database states in Python and Django? (avoiding repetitions)

I wrote unit tests first, then I made all the tests pass, now I am looking how to refactor the code to avoid repetitions.
I have a function which returns different values depending on the context. All context is extracted on-the-fly from the Django models.
Currently my code is structured like that:
from django.test import TestCase
class MyTest(TestCase):
def test_case1(self):
user = User.objects.create(username='user')
tested_class = MyClass(user)
Model1.objects.create(...) # one type of context
self.assertEqual(...) # test the class method for this type of context
def test_case2(self):
user = User.objects.create(username='user')
tested_class = MyClass(user)
Model2.objects.create(...) # another type of context
self.assertEqual(...) # test the class method for this type of context
def test_case3(self):
user = User.objects.create(username='user')
tested_class = MyClass(user)
Model1.objects.create(...) # yet another type of context
Model2.objects.create(...)
self.assertEqual(...) # test the class method for this type of context
Obviously, the code is quite repetitive: the first two lines are the same in each function.
My first idea was to use a shared setup function:
def setUp(self):
self.user = User.objects.create(username='user')
self.tested_class = MyClass(user)
but this solution didn't work: all model updates were shared, and tests became dependent on each other.
What I need instead is a clean state ("empty database") before starting each test.
What else can I try?
Why don't you just destroy all the objects you don't want in your teardown? Looks like Django allows you to do this type of thing pretty easily.
def tearDown(self):
User.objects.all().delete()

django save() method does nothing when called from within a function

I am having some troubles with Django's save() method.
1 / I have this simple Model :
class User (models.Model):
userId = models.IntegerField()
appInstance = models.TextField(null=True, blank=True)
2 / Then in a view, I check if the appInstance does exist and if not I call a function:
if not u.appInstance:
instance = autoAddApplication(request)
3 / and autoAddApplication is defined as follows:
def autoAddApplication(request):
session = request.session
user = get_object_or_404(User, userId = session['user_id'])
## do stuff here and end up with an 'instanceMap' dictionary
user.appInstance = simplejson.dumps(instanceMap)
user.save()
return instanceMap
The code runs with no error, but I don't get the Model saved in the database when the autoAddApplication function is called from the condition in step 2.
I tried to call this 'autoAddApplication' function directly by mapping a URL diretly to this function and then it does work and I get my Model saved in the DB.
I'm completely puzzled here. Why doesn't it work when I call this 'autoAddApplication' function from within another function ? Any help would be greatly appreciated.
EDIT
I finally found what I was doing wrong.
Later in step 2 I had a u.save() which was indeed saving u (and therefore overridding the changes I made in the autoAddApplication function).
I solved it by passing along u to the autoAddApplication function.
Anyway thanks for your help.
Try changing the class name from User to MyUser or something like that, you may be having troubles with the User model defined in Django.
How about to use autoAddApplication as a private function inside the view?
def _auto_add_application(session):
user = get_object_or_404(User, user_id = session['user_id'])
user.app_instance = simplejson.dumps(instance_map)
user.save()
return instance_map

Django models: Only permit one entry in a model?

I want to make some of my Django global settings configurable through the admin interface.
To that end, I've decided to set them as database fields, rather than in settings.py.
These are the settings I care about:
class ManagementEmail(models.Model):
librarian_email = models.EmailField()
intro_text = models.CharField(max_length=1000)
signoff_text = models.CharField(max_length=1000)
These are one-off global settings, so I only ever want there to be a single librarian_email, intro_text etc floating around the system.
Is there a way I can prevent admin users from adding new records here, without preventing them from editing the existing record?
I guess I can do this by writing a custom admin template for this model, but I'd like to know if there's a neater way to configure this.
Could I use something other than class, for example?
Thanks!
Please see this question on "keep[ing] settings in database", where the answer seems to be django-dbsettings
Update
Just thought of another option: you can create the following model:
from django.contrib.sites.models import Site
class ManagementEmail(models.Model):
site = models.OneToOneField(Site)
librarian_email = models.EmailField()
intro_text = models.CharField(max_length=1000)
signoff_text = models.CharField(max_length=1000)
Because of the OneToOneField field, you can only have one ManagementEmail record per site. Then, just make sure you're using sites and then you can pull the settings thusly:
from django.contrib.sites.models import Site
managementemail = Site.objects.get_current().managementemail
Note that what everyone else is telling you is true; if your goal is to store settings, adding them one by one as fields to a model is not the best implementation. Adding settings over time is going to be a headache: you have to add the field to your model, update the database structure, and modify the code that is calling that setting.
That's why I'd recommend using the django app I mentioned above, since it does exactly what you want -- provide for user-editable settings -- without making you do any extra, unnecessary work.
I think the easiest way you can do this is using has_add_permissions function of the ModelAdmin:
class ContactUsAdmin(admin.ModelAdmin):
form = ContactUsForm
def has_add_permission(self, request):
return False if self.model.objects.count() > 0 else super().has_add_permission(request)
You can set the above to be any number you like, see the django docs.
If you need more granularity than that, and make the class a singleton at the model level, see django-solo. There are many singleton implementations also that I came across.
For StackedInline, you can use max_num = 1.
Try django-constance.
Here are some useful links:
https://github.com/jezdez/django-constance
http://django-constance.readthedocs.org/en/latest/
I'd take a page out of wordpress and create a Model that support settings.
class Settings(models.Model):
option_name = models.CharField(max_length=1000)
option_value = models.CharField(max_length=25000)
option_meta = models.CharField(max_length=1000)
Then you can just pickle (serialize) objects into the fields and you'll be solid.
Build a little api, and you can be as crafty as wordpress and call. AdminOptions.get_option(opt_name)
Then you can just load the custom settings into the runtime, keeping the settings.py module separate, but equal. A good place to write this would be in an __init__.py file.
Just set up an GlobalSettings app or something with a Key and Value field.
You could easily prevent admin users from changing values by not giving them permission to edit the GlobalSettings app.
class GlobalSettingsManager(models.Manager):
def get_setting(self, key):
try:
setting = GlobalSettings.objects.get(key=key)
except:
raise MyExceptionOrWhatever
return setting
class GlobalSettings(models.Model):
key = models.CharField(unique=True, max_length=255)
value = models.CharField(max_length=255)
objects = GlobalSettingsManager()
>>> APP_SETTING = GlobalSettings.objects.get_setting('APP_SETTING')
There are apps for this but I prefer looking at them and writing my own.
You can prevent users from adding/deleting an object by overriding this method on your admin class:
ModelAdmin.has_add_permission(self, request)
ModelAdmin.has_delete_permission(self, request, obj=None)
Modification of #radtek answer to prevent deleting if only one entry is left
class SendgridEmailQuotaAdmin(admin.ModelAdmin):
list_display = ('quota','used')
def has_add_permission(self, request):
return False if self.model.objects.count() > 0 else True
def has_delete_permission(self, request, obj=None):
return False if self.model.objects.count() <= 1 else True
def get_actions(self, request):
actions = super(SendgridEmailQuotaAdmin, self).get_actions(request)
if(self.model.objects.count() <= 1):
del actions['delete_selected']
return actions
I had basically the same problem as the original poster describes, and it's easily fixed by overriding modelAdmin classes. Something similar to this in an admin.py file easily prevents adding a new object but allows editing the current one:
class TitleAdmin(admin.ModelAdmin):
def has_delete_permission(self, request, obj=TestModel.title):
return False
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=TestModel.title):
return True
This doesn't prevent a user from posting a form that edits data, but keeps things from happening in the Admin site. Depending on whether or not you feel it's necessary for your needs you can enable deletion and addition of a record with a minimum of coding.

Categories