Problem understanding a django form - django

class UserField(forms.EmailField):
def clean(self, value):
super(UserField, self).clean(value)
try:
User.objects.get(username=value)
raise forms.ValidationError("There is an existing account associated with this email.")
except User.DoesNotExist:
return value
the try except method is straightforward. However, I am having a hard time figuring out the
super(UserField, self).clean(value)

super(UserField, self).clean(value) is calling the ancestor method : forms.EmailField.clean(value) to check that the email is well formed.

Related

Django ChoiceField, ModelChoiceField validation

I see that forms.ChoiceField is using this code to validate the value:
def validate(self, value):
"""
Validates that the input is in self.choices.
"""
super(ChoiceField, self).validate(value)
if value and not self.valid_value(value):
raise ValidationError(
self.error_messages['invalid_choice'],
code='invalid_choice',
params={'value': value},
)
def valid_value(self, value):
"Check to see if the provided value is a valid choice"
text_value = force_text(value)
for k, v in self.choices:
if isinstance(v, (list, tuple)):
# This is an optgroup, so look inside the group for options
for k2, v2 in v:
if value == k2 or text_value == force_text(k2):
return True
else:
if value == k or text_value == force_text(k):
return True
return False
and forms.models.ModelChoiceField this code:
def validate(self, value):
return Field.validate(self, value)
Q1. Why Django uses validation to check if the selected value (from dropdown) is indeed in the choice list for forms.ChoiceField?
Q2. When Django uses the validation from Q1, to check if the value is indeed in the choice list, why does not also check if the selected value is in the model records for forms.models.ModelChoiceField?
The validation process starts from form.full_clean() where you have form._clean_fields() and form._clean_form executed in this order.
Now if you take a closer look at what form._clean_fields() do, you will probably notice that it only calls field.clean(value, initial) and collects the results into a cleaned_data dict. So the interesting part is at field.clean, lets see what happens there:
def clean(self, value):
"""
Validate the given value and return its "cleaned" value as an
appropriate Python object. Raise ValidationError for any errors.
"""
value = self.to_python(value)
self.validate(value)
self.run_validators(value)
return value
First, we have a to_python call, followed by validate and finishing with run_validators.
So in terms of ModelChoiceField when you reach the .validate method, your choice is already a Model instance, thats why, this kind of validation (from Q2) is happening inside the to_python method.
def to_python(self, value):
if value in self.empty_values:
return None
try:
key = self.to_field_name or 'pk'
value = self.queryset.get(**{key: value})
except (ValueError, TypeError, self.queryset.model.DoesNotExist):
raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
return value
one thing i can say is for forms.ChoiceField the input are coming from the user perspective means a user can use inspect element and enter a choice which doesnt appear from the backend .
but for models one the choices are directly coming from the backend or the database

has_object_permission not taken into account

I'm trying to enforce a permission with Django Rest Framework where a specific user cannot post an object containing a user id which is not his.
For example i don't want a user to post a feedback with another id.
My model is something like :
class Feedback(Model):
user = ForeignKey(User)
...
I try to put a permission on my view which would compare the feedback.user.id with the request.user.id, the right work ok on a post on an object and return false, but it's still posting my object... Why?
The View
class FeedbackViewSet(ModelViewSet):
model = Feedback
permission_classes = (IsSelf,)
serializer_class = FeedbackSerializer
def get_queryset(self):
....
The Permission
class IsSelf(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
#return eval(obj.user.id) == request.user.id
return False
I've commented the line to show where the problem lies.
Again the function is correctly called and returns False, but there's just no PermissionDenied raised.
While at it, i'm wondering if this is actually the way to implement this behaviour, and if not, what would be...?
Thanks.
Your problem is that has_object_permission is only called if you're trying to access a certain object. So on creation it is never actually used.
I'd suggest you do the check on validation. Example:
class FeedbackSerializer(HyperlinkedModelSerializer):
def validate(self, attrs):
user = self.context['request'].user
if attrs['user'].id != user.id:
raise ValidationError('Some exception message')
return attrs
If you have some other super serializer class then just change it.
Now that I think of it if the user field must always be the posting user, then you should just make that field read-only and set it on pre_save() in the viewset class.
class FeedbackViewSet(ModelViewSet):
def pre_save(self, obj, *args, **kwargs):
if self.action == 'create':
obj.user = self.request.user
And in the serializer set the user field read-only
class FeedbackSerializer(HyperlinkedModelSerializer):
user = serializers.HyperlinkedRelatedField(view_name='user-detail', read_only=True)
....
I don't know if this is still open...
However, in order to work, you should move that line from "has_object_permission" to "has_permission", something like this:
class IsSelf(permissions.BasePermission):
def has_permission(self, request, view, obj):
if request.method == 'POST':
#your condition
Worked for me.
As it was stated in the selected answer
has_object_permission is only called if you're trying to access a certain object
so you have to place your condition under has_permission instead.

Django Item Use Class

So in my application the user can use their items and it changes certain things in the database, however most actions would require different code.
An example would be an item could be used to add a month to the users account, another would change a variable on their profile.
I'm thinking the best was to do this is writing the code for each item use case (I guess you could use an eval and store in the DB but thats nasty). Heres what I have in my views
try:
profile = request.user.profile
inventory_item = Inventory.objects.get(id=inventory)
if inventory_item.user == request.user:
# some code here to use the item??
useitem = ItemUse(inventory_item.item)
else:
messages.error(request, 'You cannot use an item you do not have')
return HttpResponseRedirect(reverse('item_inventory'))
except Inventory.DoesNotExist:
messages.error(request, 'No such item')
return HttpResponseRedirect(reverse('item_inventory'))
How would I be able to write some decent stable code? Any suggestions would be much appreciated (i've not written the ItemUse bit, thats what i'm asking the advice on for)
I've worked out a solution, not sure if it makes much sense or is very scalable but here's what i've come up with:
class ItemUse:
def __init__(self, item, user):
self.item = item
self.user = user
def execute(self):
try:
getattr(self, self.item.slug)()
Inventory.objects.remove(self.user, self.item, 1)
return True
except:
return False
def foo(self):
self.user.profile.foo += 100
self.user.profile.save(update_fields=['foo'])
return True
This way I can parse the item and user and then test the execute method. Each method will correspond to the item slug value. It will do for now I guess

Django custom UsernameField form validation not working

So I created a custom form field to validate for duplicate usernames. I'm using Django + Mongoengine as my database. I have that plugged and working with the django authentication system so I'm assuming it can be accessed from forms.py? Maybe that assumption is incorrect. So I have the field
class UsernameField(CharField):
def to_python(self, value):
if not value:
return ""
return value
def validate(self, value):
super(CharField, self).validate(value)
try:
# If the user object exists it won't throw an Error/Exception
user=User.objects.get(username=value)
raise ValidationError("Username already exists")
except:
pass
But when I actually use it in my form, it always seems to validate correctly even though I've called checked if form.is_valid() is True
You're raising exceptions in the try block but then snuffing them out in the except block with pass. Try this, it will check for the existing user and only fails if it exists.
try:
# If the user object doesn't exist, it validates
user=User.objects.get(username=value)
except django.core.exceptions.DoesNotExist:
pass
else:
#user does exist, barf.
raise ValidationError("Username already exists")
Bah, it was a dumb mistake on my part. For some reason I forgot that the Error I was trying to raise would be caught by the try and I would get sent to the except route. Changing it to this works
class UsernameField(CharField):
def to_python(self, value):
if not value:
return ""
return value
def validate(self, value):
super(CharField, self).validate(value)
usernameDuplicate = False
try:
# If the user object exists it won't throw an Error/Exception
user=User.objects.get(username=value)
usernameDuplicate = True
except:
pass
if usernameDuplicate==True:
raise ValidationError("Username already exists")

Custom Form Field in Django

I am trying to create a custom form field in Django.
class CustomTypedMultipleChoiceField(MultipleChoiceField):
def __init__(self, *args, **kwargs):
self.coerce = kwargs.pop('coerce', lambda val: val)
self.empty_value = kwargs.pop('empty_value', [])
super(CustomTypedMultipleChoiceField, self).__init__(*args, **kwargs)
def to_python(self, value):
"""
Validates that the values are in self.choices and can be coerced to the
right type.
"""
value = super(CustomTypedMultipleChoiceField, self).to_python(value)
if value == self.empty_value or value in self.empty_values:
return self.empty_value
new_value = []
for choice in value:
try:
new_value.append(self.coerce(choice))
except (ValueError, TypeError, ValidationError):
raise ValidationError(self.error_messages['invalid_choice'] % {'value': choice})
return new_value
def validate(self, value):
if value != self.empty_value:
super(CustomTypedMultipleChoiceField, self).validate(value)
elif self.required:
raise ValidationError(self.error_messages['required'])
I am getting the error CustomTypedMultipleChoiceField has no attribute empty_values. This is the exact same code that Django in built TypedMultipleChoiceField is built with. So I dont understand why I am getting this error.
I also thought of sub-classing the TypedMultipleChoiceField, but I wanted its error to be different in to_python method and didn't want to return the value thing, so opted for this method.
Please help me.
I don't know if it's a typo or you intended that way but actually empty_values (in plural) is not defined in your code anywhere. I also take a look at the source code of the super class MultipleChoiceField and is not defined there either.
What I could find in the super super class of your class (ChoiceField) was a reference to validator.EMPTY_VALUES and of course, it is in capital letters.
The line more alike yours in the source code was this one:
if value == self.empty_value or value in validators.EMPTY_VALUES:
Take a look deep in your code and see if that was what you intended to do.
Hope this helps!