Django model's clean method multiple error - python

I have been playing around with my test project
I have this clean method in my model
class SomeModel(models.Model):
f1 = models.IntegerField()
f2 = models.IntegerField()
def clean(self):
if self.f1 > self.f2:
raise ValidationError({'f1': ['Should be greater than f1',]})
if self.f2 == 100:
raise ValidationError({'f2': ['That's too much',]})
I don't really know how to raise both errors and show it in the admin page because even if the two if is True, only the first if error is shown(obviously) how do I show both errors?

You could build a dict of errors and raise a ValidationError when you are done (if necessary):
class SomeModel(models.Model):
f1 = models.IntegerField()
f2 = models.IntegerField()
def clean(self):
error_dict = {}
if self.f1 > self.f2:
error_dict['f1'] = ValidationError("Should be greater than f1") # this should probably belong to f2 as well
if self.f2 == 100:
error_dict['f2'] = ValidationError("That's too much")
if error_dict:
raise ValidationError(error_dict)

I would expand the accepted answer by setting a classmethod like this:
#classmethod
def add_error(cls, errordict, error):
for key, value in error.items():
if key in errordict:
if isinstance(errordict[key], str) or isinstance(errordict[key], ValidationError):
errordict.update({key: [errordict[key], value]})
elif isinstance(errordict[key], list):
errordict[key].append(value)
else:
errordict.update(error)
return errordict
That way, if a key already exists in your error dict, it will be casted in a list. That way, all the errors will be raised, even if there are multiple errors per field.
adderror(error_dict, {'field', 'error message'})

Related

django - simplest way to customize admin search

I'm using django 1.8.
What I need is to do case insensitive admin-search in multiple fields and allow the user to use the AND, OR and NOT operators and some how group words either with parentheses or quotes.
Search Example:
cotton and (red or "dark blue")
I've already discovered django-advanced-filter and django-filter...
They are filters! I also want to allow the user to type in the keys in the search box.
I know that get_search_results allows us to override the search behaviour, but before I write a code for this, I want to ask is there a package that would do this for me?
Note that I feel that making a custom search with haystack is pretty complex.
This answer seems to work for me after performing the little edit mentioned in my comment. Yet, I have no idea whether this is the "correct" way of doing it.
Here is the updated code that works on django 1.8:
from django.contrib import admin
from django.db import models
from bookstore.models import Book
from django.contrib.admin.views.main import ChangeList
import operator
class MyChangeList(ChangeList):
def __init__(self, *a):
super(MyChangeList, self).__init__(*a)
def get_queryset(self, request):
print dir(self)
# First, we collect all the declared list filters.
(self.filter_specs, self.has_filters, remaining_lookup_params,
use_distinct) = self.get_filters(request)
# Then, we let every list filter modify the queryset to its liking.
qs = self.root_queryset
for filter_spec in self.filter_specs:
new_qs = filter_spec.queryset(request, qs)
if new_qs is not None:
qs = new_qs
try:
# Finally, we apply the remaining lookup parameters from the query
# string (i.e. those that haven't already been processed by the
# filters).
qs = qs.filter(**remaining_lookup_params)
except (SuspiciousOperation, ImproperlyConfigured):
# Allow certain types of errors to be re-raised as-is so that the
# caller can treat them in a special way.
raise
except Exception, e:
# Every other error is caught with a naked except, because we don't
# have any other way of validating lookup parameters. They might be
# invalid if the keyword arguments are incorrect, or if the values
# are not in the correct type, so we might get FieldError,
# ValueError, ValidationError, or ?.
raise IncorrectLookupParameters(e)
# Use select_related() if one of the list_display options is a field
# with a relationship and the provided queryset doesn't already have
# select_related defined.
if not qs.query.select_related:
if self.list_select_related:
qs = qs.select_related()
else:
for field_name in self.list_display:
try:
field = self.lookup_opts.get_field(field_name)
except Exception as ex:# models.FieldDoesNotExist:
print ex
pass
else:
if isinstance(field.rel, models.ManyToOneRel):
qs = qs.select_related()
break
# Set ordering.
ordering = self.get_ordering(request, qs)
qs = qs.order_by(*ordering)
# Apply keyword searches.
def construct_search(field_name):
if field_name.startswith('^'):
return "%s__istartswith" % field_name[1:]
elif field_name.startswith('='):
return "%s__iexact" % field_name[1:]
elif field_name.startswith('#'):
return "%s__search" % field_name[1:]
else:
return "%s__icontains" % field_name
if self.search_fields and self.query:
orm_lookups = [construct_search(str(search_field))
for search_field in self.search_fields]
or_queries = []
for bit in self.query.split():
or_queries += [models.Q(**{orm_lookup: bit})
for orm_lookup in orm_lookups]
if len(or_queries) > 0:
qs = qs.filter(reduce(operator.or_, or_queries))
if not use_distinct:
for search_spec in orm_lookups:
if admin.utils.lookup_needs_distinct(self.lookup_opts, search_spec):
use_distinct = True
break
if use_distinct:
return qs.distinct()
else:
return qs
#admin.register(Book)
class AdminBookstore(admin.ModelAdmin):
list_display = ('title', 'author', 'description')
search_fields = ('title', 'author', 'description')
def get_changelist(*a, **k):
return MyChangeList

using property for django fields causes infinite loop

I'm trying to make fields in my model write once and read only there after. The solution I came up with is using the property decorators. Please tell me if there is a better solution, I'm new to django. I get into an infinite loop when I try to instantiate the model in the django shell.
class MapPointable(models.Model):
loc_latitude = models.FloatField(null = True)
loc_longtitude = models.FloatField(null = True)
#property
def loc_latitude(self):
return self.loc_latitude
#loc_latitude.setter
def loc_latitude(self, value):
if self.loc_latitude == None:
self.loc_latitude = value
else:
raise ValueError("Read-only field, the value cannot be set")
#property
def loc_longtitude(self):
return self.loc_longtitude
#loc_longtitude.setter
def loc_longtitude(self, value):
if self.loc_longtitude == None:
self.loc_longtitude = value
else:
raise ValueError("Read-only field, the value cannot be set")
your property's name is the same name as the model field. it should be:
#property
def loc_latitude_prop(self):
return self.loc_latitude
#loc_latitude_prop.setter
def set_loc_latitude(self, value):
#...
otherwise they start calling each other and you get stuck in infinite loop..
but I would not use properties in django, because django does not recognize these in ORM, it only knows django fields

django form custom clean gives keyerror

I am using a custom clean on a form I made. But then some weird things happen
If my input data is valid. it works fine. However when it is not correct, I get a KeyError at key_ID in the clean method.
Also, if I add print statements in the clean_key_ID and clean_verification_code, these don't show up in my console.
My form:
class ApiForm(forms.Form):
key_ID = forms.CharField(min_length=7, max_length=7, required=True)
verification_code = forms.CharField(
min_length=64,
max_length=64,
required=True,
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
super(ApiForm, self).__init__(*args, **kwargs)
def clean_key_ID(self):
data = self.cleaned_data['key_ID']
try:
int(data)
except ValueError:
raise forms.ValidationError("Should contain 7 numbers")
return data
def clean_verification_code(self):
data = self.cleaned_data['verification_code']
if not re.match("^[A-Za-z0-9]*$", data):
raise forms.ValidationError(
"Should only contain 64 letters and numbers"
)
return data
def clean(self):
key = self.cleaned_data['key_ID']
vcode = self.cleaned_data['verification_code']
if Api.objects.filter(key=key, vcode=vcode, user=self.user).exists():
raise forms.ValidationError(
"This key has already been entered, try to update it"
)
#connect with api and validate key
api = api_connect()
auth = api.auth(keyID=key, vCode=vcode)
try:
keyinfo = auth.account.APIKeyInfo()
except RuntimeError:
raise forms.ValidationError("Invallid data, cannot connect to api")
in my view I get my form in the normal way:
view:
api_form = ApiForm(request.POST or None, user=request.user)
if request.POST and api_form.is_valid():
#do things with form
I am lost and have no clue where this error is coming from. My form is rendered in the normal way {{api_form}}.
I have already tried removing the underscores in my field names. But the error remains
If key_ID is invalid, then it will not be in self.cleaned_data in your clean method. You have to change your clean method to handle this, for example:
def clean(self):
key = self.cleaned_data.get('key_ID')
if not key:
# early return
return
# rest of method as before
...

Django/Python DRY: how to avoid repeat code when working with Q objects and model attributes

I'm trying to build a search page that will allow a user to find any instances of a model which meet certain threshold criteria and am having trouble avoiding seriously redundant code. I'm hoping there's a better way to do it. Here's a slightly contrived example that should illustrate what I'm trying to do, with the relevant code adjusted at the end. The user will interact with the search with checkboxes.
models.py:
class Icecream(models.Model()):
name = models.CharField()
bad_threshold = models.IntegerField()
okay_threshold = models.IntegerField()
tasty_threshold = models.IntegerField()
delicious_threshold = models.IntegerField()
views.py:
def search_icecreams(request):
user = request.user
q_search = None
if 'taste_search' in request.GET:
q_search = taste_threshold_set(request, user, q_search)
if q_search == None:
icecream_list = Icecream.objects.order_by('name')
else:
icecream_list = College.objects.filter(q_search)
context = { 'icecream_list' : icecream_list }
return render(request, '/icecream/icecreamsearch.html', context)
The relevant code that I want to cut down is as follows, this is pretty much straight from my project, with the names changed.
def taste_threshold_set(request, user, q_search):
threshold = request.GET.getlist('taste_search')
user_type_tolerance = user.profile.get_tolerance_of(icea
# 1-5 are the various thresholds. They are abbreviated to cut down on the
# length of the url.
if '1' in threshold:
new_q = Q(bad_threshold__gt = user.profile.taste_tolerance)
if q_search == None:
q_search = new_q
else:
q_search = (q_search) | (new_q)
if '2' in threshold:
new_q = Q(bad_threshold__lte=user.profile.taste_tolerance) & \
~Q(okay_threshold__lte=user.profile.taste_tolerance)
if q_search == None:
q_search = new_q
else:
q_search = (q_search) | (new_q)
if '3' in threshold:
new_q = Q(okay_threshold_3__lte=user.profile.taste_tolerance) & \
~Q(tasty_threshold__lte=user.profile.taste_tolerance)
if q_search == None:
q_search = new_q
else:
q_search = (q_search) | (new_q)
if '4' in threshold:
new_q = Q(tasty_threshold__lte=user.profile.taste_tolerance) & \
~Q(delicious_threshold__lte=user.profile.taste_tolerance)
if q_search == None:
q_search = new_q
else:
q_search = (q_search) | (new_q)
if '5' in threshold:
new_q = Q(delicious_threshold__lte = user.profile.taste_tolerance)
if q_search == None:
q_search = new_q
else:
q_search = (q_search) | (new_q)
return q_search
Basically I want the user to be able to find all instances of a certain object which meet a given threshold level. So, for example, all icecreams that they would find bad and all icecreams that they would find delicious.
There are a number of things I'm not happy with about this code. I don't like checking to see if the Q object hasn't been instantiated yet for each possible threshold, but don't see a way around it. Further, if this were a non-django problem I'd use a loop to check each of the given thresholds, instead of writing each one out. But again, I'm not sure how to do that.
Finally, the biggest problem is, I need to check thresholds for probably 20 different attributes of the model. As it stands, I'd have to write a new threshold checker for each one, each only slightly different than the other (the name of the attribute they're checking). I'd love to be able to write a generic checker, then pass it the specific attribute. Is there any way to solve this, or my other two problems?
Thanks!
How about this approach?
query_arg = ['bad_threshold__lte', 'bad_threshold__lte', 'okay_threshold_3__lte', 'tasty_threshold__lte', 'delicious_threshold__lte']
Q(**{query_arg[int(threshold) - 1]: user.profile.taste_tolerance})
You should use own QuerySet for the models instead def taste_threshold_set(...)
Example:
models.py:
...
from django.db.models.query import QuerySet
...
class IcecreamManager(models.Manager):
def get_query_set(self):
return self.model.QuerySet(self.model)
def __getattr__(self, attr, *args):
try:
return getattr(self.__class__, attr, *args)
except AttributeError:
return getattr(self.get_query_set(), attr, *args)
class Icecream(models.Model()):
name = models.CharField()
bad_threshold = models.IntegerField()
okay_threshold = models.IntegerField()
tasty_threshold = models.IntegerField()
delicious_threshold = models.IntegerField()
objects = IcecreamManager()
class QuerySet(QuerySet):
def name_custom_method(self, arg1, argN):
# you must rewrite for you solution
return self.exclude(
time_end__gt=now()
).filter(
Q(...) | Q(...)
)
def name_custom_method2(...)
...
These should give you abilities build of chains querys for your issues.

Django model class decorator

I have a need to track changes on Django model instances. I'm aware of solutions like django-reversion but they are overkill for my cause.
I had the idea to create a parameterized class decorator to fit this purpose. The arguments are the field names and a callback function. Here is the code I have at this time:
def audit_fields(fields, callback_fx):
def __init__(self, *args, **kwargs):
self.__old_init(*args, **kwargs)
self.__old_state = self.__get_state_helper()
def save(self, *args, **kwargs):
new_state = self.__get_state_helper()
for k,v in new_state.items():
if (self.__old_state[k] != v):
callback_fx(self, k, self.__old_state[k], v)
val = self.__old_save(*args, **kwargs)
self.__old_state = self.__get_state_helper()
return val
def __get_state_helper(self):
# make a list of field/values.
state_dict = dict()
for k,v in [(field.name, field.value_to_string(self)) for field in self._meta.fields if field.name in fields]:
state_dict[k] = v
return state_dict
def fx(clazz):
# Stash originals
clazz.__old_init = clazz.__init__
clazz.__old_save = clazz.save
# Override (and add helper)
clazz.__init__ = __init__
clazz.__get_state_helper = __get_state_helper
clazz.save = save
return clazz
return fx
And use it as follows (only relevant part):
#audit_fields(["status"], fx)
class Order(models.Model):
BASKET = "BASKET"
OPEN = "OPEN"
PAID = "PAID"
SHIPPED = "SHIPPED"
CANCELED = "CANCELED"
ORDER_STATES = ( (BASKET, 'BASKET'),
(OPEN, 'OPEN'),
(PAID, 'PAID'),
(SHIPPED, 'SHIPPED'),
(CANCELED, 'CANCELED') )
status = models.CharField(max_length=16, choices=ORDER_STATES, default=BASKET)
And test on the Django shell with:
from store.models import Order
o=Order()
o.status=Order.OPEN
o.save()
The error I receive then is:
TypeError: int() argument must be a string or a number, not 'Order'
The full stacktrace is here: https://gist.github.com/4020212
Thanks in advance and let me know if you would need more info!
EDIT: Question answered by randomhuman, code edited and usable as shown!
You do not need to explicitly pass a reference to self on this line:
val = self.__old_save(self, *args, **kwargs)
It is a method being called on an object reference. Passing it explicitly in this way is causing it to be seen as one of the other parameters of the save method, one which is expected to be a string or a number.

Categories