from django.core.exceptions import FieldError
#This is a method of a class
def _order_item_list(self, item_list, order_items_by, previous_order_by):
if order_items_by == previous_order_by:
order_items_by = '-' + order_items_by
try:
result = item_list.order_by(order_items_by)
except FieldError:
result = item_list
return result, order_items_by
Now when I order by valid fields following the generated link,everything works perfect. When I edit a link and add some dummy fieldnames for ordering, it should be catched by this exception and the original list should be returned. But it is not happening, instead I always get a FieldError from django.
FieldError at ...
Cannot resolve keyword u'fgsdffds' into field. Choices are: ...
The reason the exception is not caught is because the QuerySet has not been evaluated yet.
To validate an arbitrary (user-specified) value used for a model field or order_by value, simply check to see if that model has a field by that name.
For example, say you have a model called Ticket and an arbitrary GET parameter called field_name. Here's how you might handle creating a valid QuerySet in views.py:
from django.db.models import FieldDoesNotExist
from myapp.models import Ticket
def index(request):
default_field = 'id'
field_name = request.GET.get('field_name', default_field)
try:
Ticket._meta.get_field_by_name(field_name)
except FieldDoesNotExist:
field_name = default_field
tickets = Ticket.objects.all().order_by(field_name)
return ...
This means there's a typo, or the exception happens elsewhere. Insert a debug line:
import pdb; pdb.set_trace()
before the try-except and see how the code is executed. Try PUDB or IPDB debuggers instead of the standard one. Many questions disappear when you have a debugger and can see exactly what goes wrong.
I faced the same problem and surely it was because the exception is later. In order to raise exception in try-catch block I modified the code in following manner:
try:
result = item_list.order_by(order_items_by)
**result = list(result)**
except FieldError:
result = item_list
This worked for me.
Related
I have an integer field in a form that must be in the range 100 to 1000. However, if a value other than 100, 200, 381 (don't ask), 525 or 1000 is entered, it is quite probable that this is erroneous.
What I'd like to do is what I'm calling "soft validation" -- I don't know if there is any standard terminology. The first time, the form would raise errors and then "Confirm?" boolean field(s) would be added, default false. If it comes back again with the confirm field(s) true, then unlikely data will be accepted.
The answer Warnings (or even info messages) instead of only errors in Django shows how to accomplish this with custom code added field-by-field and form-by-form -- but I'd rather not do it this way. Does anybody know whether this has already been done in open-source code in a generalized, re-usable way? In my dreams, I would raise ValidationWarning ... (which doesn't exist) and the rest would look after itself.
I don't know what to search for with Google. I can't find anything obvious at https://djangopackages.org/
Here you find a minimal working example similar to the one given in the link in the comment. You only have to be carefully to not include the soft validation in the clean_MagicNumber(self) function - this is prone to race conditions because the clean_data attributes are only set.
Instead use the overwritten clean(self) or validate_unique(self) function.
from django.core.exceptions import ValidationError
from django.db import models
from django.contrib import admin
from django import forms
from django.db import models
class Autor(models.Model):
Name = models.CharField(max_length=64)
MagicNumber = models.IntegerField()
class AutorAdminForm(forms.ModelForm):
ConfirmMagicNumber = forms.BooleanField(label="I confirm it is the right magic number.",
required=False,
widget=forms.HiddenInput)
def _field_is_active(self, field):
if self._meta.exclude is not None and field in self._meta.exclude:
return False
if self._meta.fields is not None and field in self._meta.fields:
return True
return False
def clean_MagicNumber(self):
if self.cleaned_data['MagicNumber'] < 100 or self.cleaned_data['MagicNumber'] > 1000:
raise ValidationError("Magic number outside of allowed range between 100 to 1000.")
return self.cleaned_data['MagicNumber']
def validate_unique(self):
"""
We use validate unique because it is quite late in the validation process,
after we got the values of all fields and
actually I used it, because I was really warning for a possible duplicate
"""
super().validate_unique()
# only check if number does not have errors and is in form data
if 'MagicNumber' not in self._errors and 'MagicNumber':
# only check if confirm checkbox is given and not checked
if ('ConfirmMagicNumber' in self.cleaned_data and 'ConfirmMagicNumber' in self.fields
and self.cleaned_data['ConfirmMagicNumber'] is not True and self._field_is_active('ConfirmMagicNumber')):
if self.cleaned_data['MagicNumber'] not in (100, 200, 381, 525, 1000):
error_msg = "The magic number looks wrong. Please confirm it below."
self.add_error('MagicNumber', error_msg)
self.fields['ConfirmMagicNumber'].widget = forms.CheckboxInput()
self.fields['ConfirmMagicNumber'].required = True
#admin.register(Autor)
class AutorAdmin(admin.ModelAdmin):
form = AutorAdminForm
According to https://docs.djangoproject.com/en/dev/ref/forms/validation/
# Good
ValidationError(
_('Invalid value: %(value)s'),
params={'value': '42'},
)
# Bad
ValidationError(_('Invalid value: %s') % value)
The docs doesnt really explain why it is bad / good. Can someone give a concrete example?
Furthermore, when I inspect form.errors, I get something like 'Invalid: %(value)s'. How do I get the params from the Validation error and interpolate them into the error msg?
Edited
So is this considered good?
ValidationError(
_('Invalid value: %(value)s') % {'value': '42'},
)
I think the real question is: why pass the variables separately via the params argument? Why not interpolate directly into the error msg (ignore named or positional interpolation for now)???
Edited
Ok, From the source # https://github.com/django/django/blob/stable/1.5.x/django/forms/forms.py
I don't think there is any way to retrieve ValidationError's params since the Form does not even save the ValidationError object itself. See code below.
class ValidationError(Exception):
"""An error while validating data."""
def __init__(self, message, code=None, params=None):
import operator
from django.utils.encoding import force_text
"""
ValidationError can be passed any object that can be printed (usually
a string), a list of objects or a dictionary.
"""
if isinstance(message, dict):
self.message_dict = message
# Reduce each list of messages into a single list.
message = reduce(operator.add, message.values())
if isinstance(message, list):
self.messages = [force_text(msg) for msg in message]
else:
self.code = code
self.params = params
message = force_text(message)
self.messages = [message]
class Form:
....
def _clean_fields(...):
....
except ValidationError as e:
self._errors[name] = self.error_class(e.messages) # Save messages ONLY
if name in self.cleaned_data:
del self.cleaned_data[name]
If you have multiple parameters, they might appear in a different order when you translate the error message.
Named arguments allow you to change the order in which the arguments appear, without changing params. With a tuple of arguments, the order is fixed.
Note that you are linking to the development version of the Django docs. The validation error is not interpolating the parameters because you are using Django 1.5 or earlier. If you try your code in the 1.6 beta, then the parameters are interpolated into the error message.
ValidationError is caught by the form validation routine and though it can just show a message, it's better to save the possibility of getting params of error; eg. field name, value that caused error and so on. It's stated just before the example you've provided.
In order to make error messages flexible and easy to override
I'm trying to create a UnitTest to verify that an object has been deleted.
from django.utils import unittest
def test_z_Kallie_can_delete_discussion_response(self):
...snip...
self._driver.get("http://localhost:8000/questions/3/want-a-discussion")
self.assertRaises(Answer.DoesNotExist, Answer.objects.get(body__exact = '<p>User can reply to discussion.</p>'))
I keep getting the error:
DoesNotExist: Answer matching query does not exist.
You can also import ObjectDoesNotExist from django.core.exceptions, if you want a generic, model-independent way to catch the exception:
from django.core.exceptions import ObjectDoesNotExist
try:
SomeModel.objects.get(pk=1)
except ObjectDoesNotExist:
print 'Does Not Exist!'
You don't need to import it - as you've already correctly written, DoesNotExist is a property of the model itself, in this case Answer.
Your problem is that you are calling the get method - which raises the exception - before it is passed to assertRaises. You need to separate the arguments from the callable, as described in the unittest documentation:
self.assertRaises(Answer.DoesNotExist, Answer.objects.get, body__exact='<p>User can reply to discussion.</p>')
or better:
with self.assertRaises(Answer.DoesNotExist):
Answer.objects.get(body__exact='<p>User can reply to discussion.</p>')
DoesNotExist is always a property of the model that does not exist. In this case it would be Answer.DoesNotExist.
One thing to watch out for is that the second parameter to assertRaises needs to be a callable - not just a property. For instance, I had difficulties with this statement:
self.assertRaises(AP.DoesNotExist, self.fma.ap)
but this worked fine:
self.assertRaises(AP.DoesNotExist, lambda: self.fma.ap)
self.assertFalse(Answer.objects.filter(body__exact='<p>User...discussion.</p>').exists())
This is how I do such a test.
from foo.models import Answer
def test_z_Kallie_can_delete_discussion_response(self):
...snip...
self._driver.get("http://localhost:8000/questions/3/want-a-discussion")
try:
answer = Answer.objects.get(body__exact = '<p>User can reply to discussion.</p>'))
self.fail("Should not have reached here! Expected no Answer object. Found %s" % answer
except Answer.DoesNotExist:
pass # all is as expected
Newbie question:
I need to accept a parameter in a form from a method in views.py but it gave me troubles. In the view I created a method with following snippet:
def scan_page(request):
myClient = request.user.get_profile().client
form = WirelessScanForm(client = myClient) # pass parameter to the form
and in the forms.py I defined the following form:
class WirelessScanForm(forms.ModelForm):
time = forms.DateTimeField(label="Schedule Time", widget=AdminSplitDateTime())
def __init__(self,*args,**kwargs):
myClient = kwargs.pop("client") # client is the parameter passed from views.py
super(WirelessScanForm, self).__init__(*args,**kwargs)
prob = forms.ChoiceField(label="Sniffer", choices=[ x.sniffer.plug_ip for x in Sniffer.objects.filter(client = myClient) ])
But django keeps giving me error saying: TemplateSyntaxError: Caught NameError while rendering: name 'myClient' is not defined(This error happens in the query)
I'm afraid it would be something stupid missing here, but I cannot really figure out why. Please help, thanks.
Assuming I've corrected your formatting properly, you have an indentation issue: prob is outside __init__, so doesn't have access to the local myClient variable.
However if you bring it inside the method, it still won't work, as there are two other issues: first, simply assigning a field to a variable won't set it on the form; and second, the choices attribute needs a list of 2-tuples, not just a flat list. What you need is this:
def __init__(self,*args,**kwargs):
myClient = kwargs.pop("client") # client is the parameter passed from views.py
super(WirelessScanForm, self).__init__(*args,**kwargs)
self.fields['prob'] = forms.ChoiceField(label="Sniffer", choices=[(x.plug_ip, x.MY_DESCRIPTIVE_FIELD) for x in Sniffer.objects.filter(client = myClient)])
Obviously replace MY_DESCRIPTIVE_FIELD with the actual field you want displayed in the choices.
Is it possible to get object with comments related to it? Right now django comment framework creates query for every object which has related comments and another queries for comments owners. Can I somehow avoid this? I use django 1.4 so prefetch_related is allowed.
You could create a function that caches the count:
from django.contrib.contenttypes.models import ContentType
from django.contrib import comments
def get_comment_count_key(model):
content_type = ContentType.objects.get_for_model(model)
return 'comment_count_%s_%s' % (content_type.pk, model.pk)
def get_comment_count(model):
key = get_comment_count_key(model)
value = cache.get(key)
if value is None:
value = comments.get_model().objects.filter(
content_type = ContentType.objects.get_for_model(model),
object_pk = model.pk,
site__pk = settings.SITE_ID
).count()
cache.set(key, value)
return value
You could extend the Comment model and add get_comment_count there. Or put get_comment_count as a template filter. It doesn't matter.
Of course, you would also need cache invalidation when a new comment is posted:
from django.db.models import signals
from django.contrib import comments
def refresh_comment_count(sender, instance, **kwargs):
cache.delete(get_comment_count_key(instance.content_object))
get_comment_count(instance.content_object)
post_save.connect(refresh_comment_count, sender=comments.get_model())
post_delete.connect(refresh_comment_count, sender=comments.get_model())
You could improve this last snippet, by using cache.incr() on comment_was_posted, and cache.decr() on post_delete but that's left as an exercise for you :)