Normalize Django Field's Value - django

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.

Related

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

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 }}

accessing request object in a SessionWizardView in django

I am using a django forms SessionWizardView to implement a form wizard. The url configuration is as follows;
url(r'^contact/(?P<pk>\d+)/$', views.ContactWizard.as_view([DummyForm, OtherForm]), name='contact'),
The idea here is that I am passing a primary key so that I can populate the form with some initial data. The form is loaded from another view as follows:
def populate_review_form(request):
pk = 2
return redirect('contact', pk=pk)
Here I am trying to pass a hard-coded primary key to the form.
Now, my form implementation is simple where I am overriding the get_form_initial to generate the initial value for the form as follows:
class ContactWizard(SessionWizardView):
def get_template_names(self):
return "test.html"
def get_form_initial(self, step):
pk = self.request.GET.get('pk')
# Just attempt to print the primary key
print pk, self.request.GET
return {}
def done(self, form_list, **kwargs):
return HttpResponseRedirect('index')
The issue is that this always prints None <QueryDict: {}>. So, basically my parameter is not being passed or I am unable to access the correct request object. Is it possible in Django to have this communication?
pk is stored in self.kwargs:
pk = self.kwargs['pk']

Django custom field widget and widget behaviour for a custom "ListField"

I'm creating the following custom field based off How to create list field in django
import re
from django.db import models
from django.forms.widgets import TextInput
class ListField(models.TextField):
__metaclass__ = models.SubfieldBase
description = "Stores a python list"
widget = TextInput
def __init__(self, *args, **kwargs):
super(ListField, self).__init__(*args, **kwargs)
def to_python(self, value):
if not value:
return []
return filter(None, re.split(r'\,|\s*', value))
def get_prep_value(self, value):
if value is None:
return value
return ', '.join(value)
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value)
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^cflowportal\.utils\.modelutils\.ListField"])
Basically, what I want to achieve is a field where you write something like "1, asd, asdf fdgd", it stores it as such in the database but when retrieved it should return that string as an array and when given an array it should convert it back to a comma-seperated string.
I'm still not sure if what I've written so far works, but I'm having trouble displaying it as an input field and not a textarea even if I've set widget=TextInput.
So, how do I show it in the admin with the same input used by the standard CharField?
How can I customize it so that it displays a comma-separated string when showed on such input, but is given back as a Python List when accessed elsewhere?
Thanks
The following is a method to realize what you want
from django.db import models
class Blog(models.Model):
title = models.CharField(max_length=256)
labels = models.TextField()
def get_labels(self):
return self.content.split('\n')
def set_labels(self,value):
if isinstance(value,list) or isinstance(value,tuple) or isinstance(value,set):
content = '\n'.join(value)
else:
content = value
self.content = content
You can regard labels as a ListField, set value use obj.set_labels(list) function, and get value use obj.get_labels()
It act as a List Field, and admin site will run as a normal TextField.
This is what I did, but a better solution is excepted.
and a better way to do this is using save_model in admin.py:
class BlogAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# extra data handling, prevent data convert
obj.save()

Use forms.TextArea for custom JSON field in Django admin site

I'm using a custom Django field to represent JSON encoded data:
class JSONField(models.TextField):
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if value == "":
return None
try:
if isinstance(value, basestring):
return json_decode(value)
except ValueError:
pass
return value
def get_prep_value(self, value):
if value == "":
return None
if isinstance(value, dict) or isinstance(value, dict):
value = json_encode(value)
return super(JSONField, self).get_prep_value(value)
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value,connection=None)
The field itself works fine. However editting via the admin site is not possible since the string from the database is JSON-decoded and converted to a dictionary, so when the admin site is rendered, not the actual JSON string from the database is displayed (e.g. {"foo": "bar"}), but its dictionary representation (e.g. {u'foo': u'bar'}).
Obviously this leads to problems when saving the database object, because the dictionary's string representation is not a valid JSON string.
What I'd like to have is the admin site showing the actual database value (i.e. the string as it is saved in the database), instead of the string representation of the Python object returned by to_python.
My attempt would be to write a custom widget for this that just calls json_encode again on the dictionary - but is there a better way?
value_from_object will help solve the problem. It's implementation depends on what serializer has been used, but for simplejson should looks like:
from django.utils import simplejson as json
from django.core.serializers.json import DjangoJSONEncoder
class JSONField(models.TextField):
....
def value_from_object(self, obj):
return json.dumps(super(JSONField, self).value_from_object(obj))

Read-only form field formatting

I want to display a field as read only in a ModelAdmin form, so I added it to the readonly_fields attribute.
However, since the field contains a currency, stored as an integer, I want to apply some nice formatting it. I've created a custom ModelForm for my ModelAdmin, trying to apply the formatting in the overridden __init__ method.
The problem is, I cannot find the value. The field is not present in the self.fields attribute.
Does anyone know where the values for the readonly_fields are kept, or is there a better/different approach?
Just do something like:
class MyAdmin(admin.ModelAdmin):
readonly_fields = ('foo',)
def foo(self, obj):
return '${0}'.format(obj.amount)
An alternate approach, which works for all types of forms is to create a widget to represent a read only field. Here is one that I wrote for my own use. You can change the <span %s>%s</span> to suit your own requirements.
from django import forms
from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode
class ReadOnlyWidget(forms.TextInput):
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'<span%s />%s</span>' % (flatatt(final_attrs),value))
Once you have that added, simply do this:
class MyAdmin(admin.ModelAdmin):
foo = models.TextField(widget=ReadOnlyWidget(attrs={'class':'read-only'}
initial="$50")
Then in your CSS, do some styling for a read-only class, or you can adjust the attributes accordingly.
Another, more appropriate solution, works in Django 2.1.2:
ModelAdmin renders read-only fields via special wrapper AdminReadonlyField (django/contrib/admin/helpers.py) if we look at contents method, we can see
the code
if getattr(widget, 'read_only', False):
return widget.render(field, value)
It means that if a widget has read_only attribute with True value
then the read-only field will invoke widget's render method.
Hence, you can use render method to format your value.
For example:
class CustomDateInput(widgets.DateInput):
read_only = True
def _render(self, template_name, context, renderer=None):
return 'you value'
class CustomForm(forms.ModelForm):
some_field = forms.DateTimeField(widget=CustomDateInput())
#admin.register(SomeModel)
class SomeModelAdmin(admin.ModelAdmin):
form = CustomForm
readonly_fields = ['some_field']