AttributeError: 'NoneType' object has no attribute '<attribute-name>' , while testing Django UserModel - django-models

I am experiencing a very annoying problem, I would appreciate your help.
I will explain the problem:
I have a user-model that I run unit tests on.
The tests fail time and time again because of the problem that it does not recognize the attributes at all.
I would very much appreciate a solution if anyone has one.
Attaching the relevant code.
the model:
from django.db import models
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin
)
class UserManager(BaseUserManager):
"""manager for users"""
def create_user(self, email, password=None, **extra_fields):
"""create save and return a new user"""
user = self.model(email=self.normalize_email(email), **extra_fields)
user.set_password(password)
user.save(using=self._db)
class User(AbstractBaseUser, PermissionsMixin):
"""user in the system"""
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
the tests:
from django.test import TestCase
from django.contrib.auth import get_user_model
class ModelTests(TestCase):
def test_create_user_with_email_successful(self):
email = 'test#example.com'
password = 'testpass123'
user = get_user_model().objects.create_user(
email=email,
password=password,
)
self.assertTrue(user.check_password(password))
self.assertEqual(user.email, email)
def test_new_user_email_normelaized(self):
sample_emails = [
['test1#EXAMPLE.com', 'test1#example.com'],
['Test2#example.com', 'test2#example.com'],
['TEST3#EXAMPLE.COM', 'test3#example.com'],
['test4#example.COM', 'test4#example.com'],
]
for email, expected in sample_emails:
user = get_user_model().objects.create_user(email, 'sample123')
self.assertEqual(user.email, expected)
the problem:
ERROR: test_create_user_with_email_successful (core.tests.test_models.ModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/app/core/tests/test_models.py", line 16, in test_create_user_with_email_successful
self.assertTrue(user.check_password(password))
AttributeError: 'NoneType' object has no attribute 'check_password'
======================================================================
ERROR: test_new_user_email_normelaized (core.tests.test_models.ModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/app/core/tests/test_models.py", line 29, in test_new_user_email_normelaized
self.assertEqual(user.email, expected)
AttributeError: 'NoneType' object has no attribute 'email'
----------------------------------------------------------------------
Ran 6 tests in 0.313s
FAILED (errors=2)

Your UserManager is not returning anything that's why you are getting None object when you are creating user.
You can use replace the below code with your UserManager and that will fix all your test cases.
class UserManager(BaseUserManager):
"""manager for users"""
def create_user(self, email, password=None, **extra_fields):
"""create save and return a new user"""
user = self.model(email=self.normalize_email(email.lower()), **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
Here I am doing email.lower() because normalize_email function only normalizes string before # that means it returns Test2#example.com as it is instead
of test2#example.com and TEST3#EXAMPLE.COM as TEST3#example.com which is not what you are expecting here.

Ok... so after a long time of trying to find the problem, it turns out it's really simple.
The problem was that no value was returned from the instance of the UserManger, so the object returned NoneType. And of course it's attributes were not identified.
I added a return user at the end of class and everything got identified back again.

Related

Wagtail ModelAdmin > How to use custom validation?

I am using wagtail ModelAdmin for some of my non page models and want to add some custom validation.
This is some of the code.
class EditPlanningView(EditView):
def publish_url(self):
return self.url_helper.get_action_url('publish', self.pk_quoted)
def unpublish_url(self):
return self.url_helper.get_action_url('unpublish', self.pk_quoted)
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
instance = form.save(commit=False)
if bool(request.POST.get('action-publish')):
try:
instance.publish(commit=True)
except PublishWithoutMeetingError as e:
form.add_error(
'planning_meeting',
e
)
return self.form_invalid(form)
When validation fails the invalid form is returned, but the error I added is not bound to the field. In stead a 'general error message' appears at the top.
Can someone help me out?
Cheers,
Robert
I think the error is in the following lines.
form.add_error(
'planning_meeting',
e
)
Actually can't say anything without knowing about PublishWithoutMeetingError, the type of e. Better to replace e with a string. And make sure the post method is not throwing any exceptions. Other than that, what you have done is correct. Read the following to also to check if you have missed any point.
Long Answer
There are two ways that you can achieve showing an error messages in forms.
Overriding the Form
Overriding the EditView
In both of these cases, you are going to use a method called add_error. That method takes 2 argument, field and error. From these two, error is the most important argument. The field simply state the field of the form that this error applies to. This can be None.
The error argument can be multiple types.
The error argument can be an instance of str. Then wagtail will assign the given error to the given field.
The error argument can be an instance of list of str. Then wagtail will assign the given list of errors to the given field.
The error argument can be an instance of dict with str keys and str or list of str values. In this case field should be None. The keys will be used as the fields for the errors given by values.
The error argument can be an instance of ValidationError exception. You can create a ValidationError using a str, list, or dict, which represent the above three cases.
Overriding the Form
In the form clean method need to be overridden in order to find errors.
from wagtail.admin.forms.models import WagtailAdminModelForm
class ExtraForm(WagtailAdminModelForm):
def clean(self):
cleaned_data = super().clean() # Get the already cleaned data. Same as self.cleaned_data in this case. But this way is better.
title = cleaned_data.get('title') # Get the cleaned title
if title is None: # Title is never None here, but still..
return cleaned_data
title = title.strip() # Do some formatting if needed
if title.startswith('A'): # Validation
self.add_error('title', 'Title cannot start with A') # Validation error
else:
cleaned_data['title'] = title # Use the formatted title
return cleaned_data
class MyModel(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=500, default='', blank=False)
# Or any other fields you have
base_form_class = ExtraForm # Tell wagtail to use ExtraForm instead of the default one
Overriding the EditView
This way is same as the way that you have mentioned in the question. You need to override post method. You need to check if the form associated with the EditView is valid or invalid and return the appropriate form.
To check validity, is_valid method of the form is used by default. That method will clean the form and check if there are errors added to the form.
If form is valid, you need to return self.valid_form and self.invalid_form otherwise.
Unlike overriding the Form, you can access the request here.
class MyEditView(EditView):
def post(self, request, *args, **kwargs):
form = self.get_form() # Get the form associated with this edit view
if form.is_valid(): # Check if the form pass the default checks
my_field = request.POST.get('my_field') # You can access the request
title = form.cleaned_data.get('title') # You can access the form data
if title != my_field: # Validation
form.add_error('title', 'Title must match my_field') # Validation error
return self.form_invalid(form) # Return invalid form if there are validation errors
return self.form_valid(form) # Return the valid form if there are no validation errors
else:
return self.form_invalid(form) # Return invalid form if default check failed
class MyModelAdmin(ModelAdmin):
model = MyModel
menu_label = 'My Model'
list_display = ('id', 'title')
search_fields = (
'title',
)
edit_view_class = MyEditView # Tell wagtail to use MyEditView instead of the default one.

google appengine endpoint proto datastore simple get id example return not found

I am trying the simple get at endpoint proto datastore for few days.
http://endpoints-proto-datastore.appspot.com/examples/simple_get.html
It works, but it always return not found.
Here is the get by id api.
https://pttbuying.appspot.com/_ah/api/pttbuying/v1/items/4504690549063680
Here is my model code.
class Item(EndpointsModel):
_message_fields_schema = ('id', 'item_title', 'item_link', 'item_price', 'item_description_strip', 'datetime')
item_title = ndb.StringProperty(indexed=False)
item_author_name = ndb.StringProperty(indexed=False)
item_link = ndb.StringProperty(indexed=True)
item_description_strip = ndb.StringProperty(indexed=False)
item_price = ndb.StringProperty(indexed=False)
datetime = ndb.DateTimeProperty(auto_now_add=True)
Here is my api code
#endpoints.api(name='pttbuying', version='v1',
allowed_client_ids=[WEB_CLIENT_ID, ANDROID_CLIENT_ID,
IOS_CLIENT_ID, endpoints.API_EXPLORER_CLIENT_ID],
audiences=[ANDROID_AUDIENCE],
scopes=[endpoints.EMAIL_SCOPE])
class PttBuyingApi(remote.Service):
"""PttBuying API v1."""
#Item.method(request_fields=('id',),
path='items/{id}', http_method='GET', name='item.MyModelGet')
def MyModelGet(self, my_item):
if not my_item.from_datastore:
raise endpoints.NotFoundException('Item not found.')
return my_item
#Item.query_method(query_fields=('limit', 'order', 'pageToken'), path='items', name='item.list')
def MyModelList(self, query):
return query
Am i missing something?
Thanks for advice.
I did your example here and it works nicely with some changes:
#Item.query_method(query_fields=('limit', 'pageToken',),
path='items',
http_method='GET',
name='items.list')
def ItemsList(self, query):
return query
#Item.method(request_fields=('id',),
path='item',
http_method='GET',
name='item.get')
def ItemGet(self, item):
if not item.from_datastore:
raise endpoints.NotFoundException('item not found')
return item
#Item.method(path='item',
http_method='POST',
name='item.post')
def ItemPost(self, item):
item.put()
return item
I didn't change a thing regarding the model, just the api methods.
Just perform an item insertion, get the ID of the item that was just inserted and then perform the ItemGet with the ID provided.
For the get i prefer this way (not using the /{id}, but requiring the user to do a GET Query - i.e, ah/api/path?id=__ , which seems more correct for me). If you have any questions, ask bellow.

Django DatabaseError "more than one row returned by a subquery used as an expression" Editable related fields to object

I am trying to add inlines to my template but continue to get a Database error:
more than one row returned by a subquery used as an expression
I have 3 objects in my models.py that relate to each other. The user will be able to see which Teacher is selected and have all Owners under that Teacher listed (Teacher and Owner will only appear as an uneditable list). I'd like to have all the Pets under the Owner listed and editable. Any ideas on why I am receiving this error? And how I may be able to accomplish my goal?
models.py
class Teacher(models.Model):
teacher = models.CharField(max_length=300)
class Owner(models.Model):
relevantteacher = models.ForeignKey(Teacher)
owner = models.CharField(max_length=300)
class PetName(models.Model):
relevantowner = models.ForeignKey(Owner)
pet_name = models.CharField(max_length=50)
forms.py
class OwnerForm(forms.ModelForm):
class Meta:
model = Owner
PetNameFormSet = inlineformset_factory(Owner,
PetName,
can_delete=False,
extra=3,
form=OwnerForm)
views.py
def petname(request, teacher_id):
teacher = get_object_or_404(Teacher, pk=teacher_id)
owners = Owner.objects.filter(relevantteacher=teacher_id)
if request.method == "POST":
petNameInlineFormSet = PetNameFormSet(request.POST, request.FILES, instance=owners)
if petNameInlineFormSet.is_valid():
petNameInlineFormSet.save()
return HttpResponseRedirect(reverse('success'))
else:
petNameInlineFormSet = PetNameFormSet(instance=owners) //error might be here?
context = {'teacher': teacher, 'owners': owners, 'petNameInlineFormSet' : petNameInlineFormSet}
return render(request, 'petname.html', context)
Update:
Here is the traceback:
File "hde/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "/views.py" in petname
60. petNameInlineFormSet = PetNameFormSet(instance=owners)
File "lib/python2.7/site-packages/django/forms/models.py" in __init__
697. queryset=qs, **kwargs)
File "lib/python2.7/site-packages/django/forms/models.py" in __init__
424. super(BaseModelFormSet, self).__init__(**defaults)
Needed to pass only 1 object to the instance
owner = owners[0]
then
instance=owner
However, I am only able to add/edit pet names 1 owner at a time.
Thanks aamir for the help!
I believe your error is in the second line of the views.py file. I believe it is the call to the get_object_or_404 method causing the error when you try to specify teacher.id in your template. The call to the get_object_or_404 method is returning more than one row from the database, so calling teacher.id is not possible on it from more than one row.

Unique in ModelForm, how to get back to the form with error message?

I have found some posts about more or less the same situation like this one or this other one, but I was not able to adapt any of these to my situation.
What I would like is to return to my form with a warning if the user try to create another model with the same name of one already stored by himself in the db.
I would like to use Django built in facilities as described here, but I need some advices to change my code: could you help me, please?
My code follows:
models.py
class ShapeFile(models.Model):
name = models.CharField(max_length=100)
srid = models.ForeignKey(SpatialRefSys)
model_definition = models.OneToOneField(ModelDefinition)
user = models.ForeignKey(User)
color_table = models.ManyToManyField(ColorTable)
file = models.FileField(upload_to=get_upload_path)
class Meta:
unique_together = ('name', 'user')
forms.py
class UploadForm(ModelForm):
class Meta:
model = ShapeFile
fields = ('name','srid','file','color_table')
widgets = {'srid': TextInput()}
views.py
#login_required
def index(request):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
req = request.POST
sridVal = form.cleaned_data['srid'].pk
shpVal = form.cleaned_data['name']
# The final table name is something like 'mutant_celeryPy2_123_salzburg_lc'
end_table_name = request.user.username + "_" + shpVal + '_lc'
# Creates a table, otherwise return the retrieved one
model_def, created = ModelDefinition.objects.get_or_create(
app_label='celeryPy2',
object_name=end_table_name,
defaults=dict(
bases=(BaseDefinition(base=GeoModel),),
fields=(GeometryFieldDefinition(name='the_geom', srid=sridVal),
SmallIntegerFieldDefinition(name='cat'),)
)
)
obj = form.save(commit=False)
obj.user = request.user
obj.model_definition = model_def
obj.save()
messages.success(request, 'Shapefile upload succesful!')
return HttpResponse('Stored!')
else:
print "Upload shapefile form is invalid!!!"
else:
form = UploadForm()
return render_to_response('celeryPy2/index.html',
{'form': form,},
context_instance=RequestContext(request))
If I login as user1 and fill the form, let's suppose with name 'myshape', when I submit it I get the "Stored!" message: everything fine, model user1_myshape_lc is created.
If I re-login with the same user1 and try to store other data with field name set at 'myshape' as before I correctly get an exception:
Exception Type: IntegrityError at /celeryPy2/main
Exception Value: duplicate key value violates unique constraint "celeryPy2_shapefile_model_definition_id_key"
DETAIL: Key (model_definition_id)=(154) already exists.
How to get back my form with a warning instead to get the Django exception error message?
Thank you.
Model forms validation should take care of this and raise the validation error. But you have excluded the user field from form, which is part of unique_together constraint, hence its not getting verified. Ref validate_unique
You can try changing the view code to add user field in posted data and then instantiate the form.
Or better way would be add hidden user field in the form, so that its available in request.POST and the validation works as required.
Thanks Rohan, I found the hidden field solution the best: now I correctly get back my form with the error message: Shape file with this Name and User already exists.
Here's my modified code to get things work:
forms.py
class UploadForm(ModelForm):
class Meta:
model = ShapeFile
fields = ('name','user','srid','file','color_table')
widgets = {'srid': TextInput(), 'user': HiddenInput()}
views.py
#login_required
def index(request):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
req = request.POST
sridVal = form.cleaned_data['srid'].pk
shpVal = form.cleaned_data['name']
# The final table name is something like 'mutant_celeryPy2_123_salzburg_lc'
end_table_name = request.user.username + "_" + shpVal + '_lc'
# Creates a table, otherwise return the retrieved one
model_def, created = ModelDefinition.objects.get_or_create(
app_label='celeryPy2',
object_name=end_table_name,
defaults=dict(
bases=(BaseDefinition(base=GeoModel),),
fields=(GeometryFieldDefinition(name='the_geom', srid=sridVal),
SmallIntegerFieldDefinition(name='cat'),)
)
)
obj = form.save(commit=False)
obj.model_definition = model_def
obj.save()
messages.success(request, 'Shapefile upload succesful!')
return HttpResponse('Stored!')
else:
print "Upload shapefile form is invalid!!!"
else:
form = UploadForm(initial={'user': request.user})
return render_to_response('celeryPy2/index.html',
{'form': form,},
context_instance=RequestContext(request))

Whats the most Django/pythonic way to create or overwrite a record?

Working with Django 1.2 I am making a wine review site. A user should only be able to review each wine once, but should be able to go back and re-review a wine without raising an error.
Using the get_or_create method seems the most rational solution but I have been running into various problems implementing it. Searching I found this article which looked promising:
Correct way to use get_or_create?
and of course the django documentation on it:
http://docs.djangoproject.com/en/1.2/ref/models/querysets/#get-or-create
But didn't seem to answer my question. Here is my code:
Views.py
#login_required
def wine_review_page(request, wine_id):
wine = get_object_or_404(Wine, pk=wine_id)
if request.method == 'POST':
form = WineReviewForm(request.POST)
if form.is_valid():
review, created = Review.objects.get_or_create(
user = request.user,
wine = wine,
like_dislike = form.cleaned_data['like_dislike'],
...
)
variables = RequestContext(request, {
'wine': wine
})
review.save()
return HttpResponseRedirect(
'/detail/%s/' % wine_id
)
else:
form = WineReviewForm()
variables = RequestContext(request, {
'form': form,
'wine': wine
})
return render_to_response('wine_review_page.html', variables)
Models.py
class Review(models.Model):
wine = models.ForeignKey(Wine, unique=True)
user = models.ForeignKey(User, unique=True)
like_dislike = models.CharField(max_length=7, unique=True)
...
If I understand how to use get_or_create correctly, since I am not matching on all the values like_dislike, etc... then django perceives it to be unique. I tried removing the other form parameters, but then they are not submitted with the post request.
Suggestions would be greatly appreciated.
I came across this too when making a CRUD based app. I'm not sure if there's a better way but the way I ended up getting doing was using a exists() to check if an entry ... exists.
You can use get_or_create within the is_valid() scope, however, you need to check if the review exists before displaying your form in order to load instance data into the form in the case that the review already exists.
Your models.py might look like this:
from django.db import models
from django.contrib.auth.models import User
class Wine(models.Model):
name = models.CharField()
class Review(models.Model):
wine = models.ForeignKey(Wine)
user = models.ForeignKey(User)
like = models.BooleanField(null=True, blank=True) # if null, unrated
Your forms.py might look like this:
from django import forms
class WineReviewForm(forms.ModelForm):
class Meta:
model = Review
fields = ['like',] # excludes the user and wine field from the form
Using get_or_create will let you do this if used like so:
#login_required
def wine_review_page(request, wine_id):
wine = get_object_or_404(Wine, pk=wine_id)
review, created = Review.objects.get_or_create(user=request.user, wine=wine)
if request.method == 'POST':
form = WineReviewForm(request.POST, instance=review)
if form.is_valid():
form.save()
return HttpResponseRedirect('/detail/%s/' % wine_id )
else:
form = WineReviewForm(instance=review)
variables = RequestContext(request, {'form': form, 'wine': wine })
return render_to_response('wine_review_page.html', variables)
Doing creates a review just by visiting the page and requires that the other information either have a default or are allowed to be blank at the model level.
With exists(), you get two db hits if the review exists, however you don't create an object unless the user submits a valid form:
#login_required
def wine_review_page(request, wine_id):
wine = get_object_or_404(Wine, pk=wine_id)
review = None
if Review.objects.filter(user=request.user, wine=wine).exists():
review = Review.objects.get(user=request.user, wine=wine)
if request.method == 'POST':
form = WineReviewForm(request.POST, instance=review)
if form.is_valid():
form.save()
return HttpResponseRedirect('/detail/%s/' % wine_id )
else:
form = WineReviewForm(instance=review)
variables = RequestContext(request, {'form': form, 'wine': wine })
return render_to_response('wine_review_page.html', variables)
I used exists() but I think that this might be better?
try:
review = Review.objects.get(user=request.user, wine=wine)
except Review.DoesNotExist:
review = None
Hopefully someone with more experience will chime in.
Edit:
Here is a fairly old post from Daniel Roseman's Blog. I don't know if it is still applicable, but might be relevant to your question.

Resources