How do you make a lowercase field in a django model? - django

With this method, I can make a field save as lowercase, but this does not
change the field in the existing model (that is in memory).
def get_prep_value(self, value):
value = super(LowercaseField, self).get_prep_value(value)
if value is not None:
value = value.lower()
return value
I'm having a hard time figuring out how to force this field to lowercase without overriding save and doing the change there. But that splits the logic for this lowercase field. I'd like all of it in the field. What do I override so that setting this value forces lowercase in memory AND on in the DB?
I don't want to change a form, I want all the lowercase logic contained inside the field class.
I've found a partial work around like so:
def pre_save(self, model_instance, add):
""" Returns field's value just before saving. """
attr = getattr(model_instance, self.attname)
if attr is not None:
attr = attr.lower()
setattr(model_instance, self.attname, attr)
return attr
def get_prep_value(self, value):
value = super(LowercaseField, self).get_prep_value(value)
if value is not None:
value = value.lower()
return value
It has a bit of a code smell, and does not handle checking the value before a save, but I don't see how to do that without overriding setattr on the actual model class and catching dealing with that inside the model class itself.

You can override the "save" method in Django by adding the following code in your models.py file
def save(self, *args, **kwargs):
self.yourfiled = self.yourfield.lower()
return super(ModelsName, self).save(*args, **kwargs)
Of course is possible to handle all params with a loop.
For all existing record you can create a Management command that can convert all strings to lowercase
here the docs:
Writing custom django-admin commands
If you don't want to change the Save method, just add to the form the "|lower" tag that will be convert all string to lowercase in UI
{{ value|lower }}

Related

Normalize Django Field's Value

I wrote a custom django field to normalize the urls our system received. However, the url will only return normalized value after reload.
from django.db import models
def _rewrite_internal_url(url):
#
return 'http://www.google.com/1.jpg'
class NormalizedURLField(models.URLField):
def to_python(self, value):
value = super().to_python(value)
return _rewrite_internal_url(value)
def from_db_value(self, value, expression, connection):
if value is None:
return value
return _rewrite_internal_url(value)
class DjangoTest(models.Model):
url = NormalizedURLField()
instance = DjangoTest.objects.create(url="http://www.google.com/2.jpg")
print(instance.url) # still http://www.google.com/2.jpg
instance.referesh_from_db()
print(instance.url) # update to http://www.google.com/1.jpg
If you do not have non-normalized urls saved in the NormalizedURLField fields, overriding to_python and from_db_value is unnecessary. Instead override pre_save to transform and update the value on the model instance when saving:
class NormalizedURLField(models.URLField):
def pre_save(self, model_instance, add):
attr = super().pre_save(model_instance, add)
rewritten = _rewrite_internal_url(attr)
# update the value on the model instance
setattr(model_instance, self.attname, rewritten)
# return the rewritten value
return rewritten
If you already have non-normalized values in your db that you want converted when fetching from the database then you will still want to implement from_db_value as you have done.

Cycle thru a ModelForm fields and identify FileField and ImageField

I'm creating a generic save method for a form.
In the save I want to iterate over the field and if the field is an instance of FileField to to some operations
def save(self, commit=True):
obj = super().save(commit=False)
for field in self.fields:
if isinstance(field, forms.FileField) and obj:
This is not working because, if I check the type of field is str. How can I get the real model or declared type ?
I found the answer: the self.fields is an OrderedDictionary, so I was iteraing over the keys instead of iterating over the values:
for field,value in self.fields.items():
if isinstance(value, forms.FileField) and obj:

Django: Modifying field values before they're submitted

To workaround issues with Taggit, I'm trying to add quotes around values in the tag field before they're transferred into a model. This is what I have so far but it's not working. What am I doing wrong?
class TagField(models.CharField):
description = "Simplifies entering tags w/ taggit"
def __init__(self, *args, **kwargs):
super(TagField, self).__init__(self, *args, **kwargs)
# Adds quotes to the value if there are no commas
def to_python(self, value):
if ',' in value:
return value
else:
return '"' + value + '"'
class CaseForm(forms.ModelForm):
class Meta:
model = Case
fields = ['title', 'file', 'tags']
labels = {
'file': 'Link to File',
'tags': 'Categories'
}
widgets = {
'tags': TagField()
}
You are subclassing models.CharField, instead you should subclass forms.CharField, you're specifying for widget attribute in the form but you're trying to create a form field subclass.
The reason this is not working is you are defining a custom model field and then trying to specify it as a widget in the form. If you indeed want a custom widget, you need to actually provide a widget instance, not a model field instance.
But to get the behavior you want, instead you need to declare the field at the Model level as an instance of your custom field class.
Try something like -
from django.db import models
class TagField(models.CharField):
description = "Simplifies entering tags w/ taggit"
def __init__(self, *args, **kwargs):
super(TagField, self).__init__(*args, **kwargs)
# Adds quotes to the value if there are no commas
def to_python(self, value):
if any( x in value for x in (',', '"') ):
return value
else:
return "\"%s\"" % value
class ModelWithTag(models.Model):
tag = TagField(max_length = 100)
The to_python method is also called by Model.clean(), which is called during form validation, so I think this will provide the behavior you need.
Note, I also check for the presence of a double-quote in your condition in the to_python method, otherwise the quotes will continue to "stack up" every time save() is called.

Where to clean extra whitespace from form field inputs?

I've just discovered that Django doesn't automatically strip out extra whitespace from form field inputs, and I think I understand the rationale ('frameworks shouldn't be altering user input').
I think I know how to remove the excess whitespace using python's re:
#data = re.sub('\A\s+|\s+\Z', '', data)
data = data.strip()
data = re.sub('\s+', ' ', data)
The question is where should I do this? Presumably this should happen in one of the form's clean stages, but which one? Ideally, I would like to clean all my fields of extra whitespace. If it should be done in the clean_field() method, that would mean I would have to have a lot of clean_field() methods that basically do the same thing, which seems like a lot of repetition.
If not the form's cleaning stages, then perhaps in the model that the form is based on?
My approach is borrowed from here. But instead of subclassing django.forms.Form, I use a mixin. That way I can use it with both Form and ModelForm. The method defined here overrides BaseForm's _clean_fields method.
class StripWhitespaceMixin(object):
def _clean_fields(self):
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.initial.get(name, field.initial)
value = field.clean(value, initial)
else:
if isinstance(value, basestring):
value = field.clean(value.strip())
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self._errors[name] = self.error_class(e.messages)
if name in self.cleaned_data:
del self.cleaned_data[name]
To use, simply add the mixin to your form
class MyForm(StripeWhitespaceMixin, ModelForm):
...
Also, if you want to trim whitespace when saving models that do not have a form you can use the following mixin. Models without forms aren't validated by default. I use this when I create objects based off of json data returned from external rest api call.
class ValidateModelMixin(object):
def clean(self):
for field in self._meta.fields:
value = getattr(self, field.name)
if value:
# ducktyping attempt to strip whitespace
try:
setattr(self, field.name, value.strip())
except Exception:
pass
def save(self, *args, **kwargs):
self.full_clean()
super(ValidateModelMixin, self).save(*args, **kwargs)
Then in your models.py
class MyModel(ValidateModelMixin, Model):
....
Create a custom model field so that your custom form field will be used automatically.
class TrimmedCharFormField(forms.CharField):
def clean(self, value):
if value:
value = value.strip()
return super(TrimmedCharFormField, self).clean(value)
# (If you use South) add_introspection_rules([], ["^common\.fields\.TrimmedCharField"])
class TrimmedCharField(models.CharField):
__metaclass__ = models.SubfieldBase
def formfield(self, **kwargs):
return super(TrimmedCharField, self).formfield(form_class=TrimmedCharFormField, **kwargs)
Then in your models just replace django.db.models.CharField with TrimmedCharField
How about adding that to the def clean(self): in the form?
For further documentation see:
https://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other
Your method could look something like this:
def clean(self):
cleaned_data = self.cleaned_data
for k in self.cleaned_data:
data = re.sub('\A\s+', '', self.cleaned_data[k])
data = re.sub('\s+\Z', '', data)
data = re.sub('\s+', ' ', data)
cleaned_data[k]=data
return cleaned_data
Use the following mixin:
class StripWhitespaceMixin(object):
def full_clean(self):
# self.data can be dict (usually empty) or QueryDict here.
self.data = self.data.copy()
is_querydict = hasattr(self.data, 'setlist')
strip = lambda val: val.strip()
for k in list(self.data.keys()):
if is_querydict:
self.data.setlist(k, map(strip, self.data.getlist(k)))
else:
self.data[k] = strip(self.data[k])
super(StripWhitespaceMixin, self).full_clean()
Add this as a mixin to your form e.g.:
class MyForm(StripWhitespaceMixin, Form):
pass
This is similar to pymarco's answer, but doesn't involve copy-pasting and then modifying Django code (the contents of the _clean_fields method).
Instead, it overrides full_clean but calls the original full_clean method after making some adjustments to the input data. This makes it less dependent on implementation details of Django's Form class that might change (and in fact have changed since that answer).
Since Django 1.9 you can use the strip keyword argument in the field of your form definition :
stripĀ¶
New in Django 1.9.
If True (default), the value will be stripped of leading and trailing whitespace.
Which should give something like :
class MyForm(forms.Form):
myfield = forms.CharField(min_length=42, strip=True)
And since its default value is True this should be automatic with django>=1.9.
It's also relevant with RegexField.
In this case, it could be useful to create your own form field (it's not that hard as it sounds). In the clean() method you would remove that extra whitespaces.
Quoting the documentation:
You can easily create custom Field classes. To do this, just create a
subclass of django.forms.Field. Its only requirements are that it
implement a clean() method and that its __init__() method accept the
core arguments (required, label, initial, widget,
help_text).
More about it: https://docs.djangoproject.com/en/1.3/ref/forms/fields/#creating-custom-fields
One way to do this is to specify custom form widget that strips whitespace:
>>> from django import forms
>>> class StripTextField(forms.CharField):
... def clean(self,value):
... return value.strip()
...
>>> f = StripTextField()
>>> f.clean(' hello ')
'hello'
Then to use this in your ModelForm:
class MyForm(ModelForm):
strip_field = StripTextField()
class Meta:
model = MyModel
However, the best place to do this is in your view after the form has been validated; before you do any inserts into the db or other manipulation of data if you are using ModelForms.
You can always create your own non-ModelForm forms and control every aspect of the field and validation that way.
ModelForm's validation adds checks for values that would violate the db constraints; so if the field can accept ' hello ' as a valid input, ModelForm's is_valid() would have no reason to strip the whitespaces (as it wouldn't make for arbitrary clean logic, in addition to what you mentioned "frameworks shouldn't alter user's input").
If you want to strip() every CharField in your project; it may be simplest to monkeypatch CharField's default cleaning method.
within: monkey_patch/__init__.py
from django.forms.fields import CharField
def new_clean(self, value):
""" Strip leading and trailing whitespace on all CharField's """
if value:
# We try/catch here, because other fields subclass CharField. So I'm not totally certain that value will always be stripable.
try:
value = value.strip()
except:
pass
return super(CharField, self).clean(value)
CharField.clean = new_clean

Django: Custom widget that can pre-fill from POST/GET data

Updated with my final solution, below.
I wrote a custom Django form widget to create a range query. It renders two input fields to define the min and max values for a query.
With well-crafted forms and widgets, fields can be filled with the values from the last query like so:
form = my_form(request.GET)
However, I cannot figure out a way to fill the values of those fields in my custom widget. Here is the widget code:
class MinMax(Widget):
input_type = None # Subclasses must define this.
def _format_value(self, value):
if self.is_localized:
return formats.localize_input(value)
return value
def render(self, name, value, attrs=None):
if value is None:
value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(self._format_value(value))
return mark_safe(u'<input type="text" name="min-%s" /> to
<input type="text" name="max-%s" />' % (name, name) )
Probably because of the custom input field names, the values are not accessible. Is there a way to route them, or a way to rewrite the widget to include this useful functionality? One non-widget solution I can think of is some simple jquery logic, but that's less than optimal.
Here's the code I ended up using:
class MinMax(MultiWidget):
def __init__(self, attrs=None):
""" pass all these parameters to their respective widget constructors..."""
widgets = (forms.TextInput(attrs=attrs), forms.TextInput(attrs=attrs) )
super(MinMax, self).__init__(widgets, attrs)
def decompress(self, value):
return value or ''
def value_from_datadict(self, data, files, name):
value = ''
for key, value in data.items():
value += value
def format_output(self, rendered_widgets):
"""
Given a list of rendered widgets (as strings), it inserts stuff
between them.
Returns a Unicode string representing the HTML for the whole lot.
"""
rendered_widgets.insert(-1, ' to ')
return u''.join(rendered_widgets)
Note that these fields are returned as fieldname_0, fieldname_1 (and so on if you add additional widgets).