I'm trying to create a custom field for volume made up of 4 parts (length, width, height and units). I'm thinking that extending the models.JSONField class makes the most sense.
Here is what I have so far.
inventory/models.py
from django.db import models
from tpt.fields import VolumeField
class Measurement(models.Model):
volumne = VolumeField()
tpt/fields.py
from django.db import models
from tpt import forms
class VolumeField(models.JSONField):
description = 'Package volume field in 3 dimensions'
def __init__(self, length=None, width=None, height=None, units=None, *args, **kwargs):
self.widget_args = {
"length": length,
"width": width,
"height": height,
"unit_choices": units,
}
super(VolumeField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {"form_class": forms.VolumeWidgetField}
defaults.update(kwargs)
defaults.update(self.widget_args)
return super(VolumeField, self).formfield(**defaults)
tpt/forms.py
import json
from django import forms
class VolumeWidget(forms.widgets.MultiWidget):
def __init__(self, attrs=None):
widgets = [forms.NumberInput(),
forms.NumberInput(),
forms.NumberInput(),
forms.TextInput()]
super(VolumeWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return json.loads(value)
else:
return [0, 0, 0, '']
class VolumeWidgetField(forms.fields.MultiValueField):
widget = VolumeWidget
def __init__(self, *args, **kwargs):
list_fields = [forms.fields.CharField(max_length=8),
forms.fields.CharField(max_length=8),
forms.fields.CharField(max_length=8),
forms.fields.CharField(max_length=4)]
super(VolumeWidgetField, self).__init__(list_fields, *args, **kwargs)
def compress(self, values):
return json.dumps(values)
I'm able to run the server but when I try and add a new entry to the Measurement Model in Admin I get this error:
[13/May/2024 11:57:59] "GET /admin/inventory/measurement/ HTTP/1.1" 200 12250
Internal Server Error: /admin/inventory/measurement/add/
Traceback (most recent call last):
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/contrib/admin/options.py", line 716, in wrapper
return self.admin_site.admin_view(view)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/utils/decorators.py", line 188, in _view_wrapper
result = _process_exception(request, e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/utils/decorators.py", line 186, in _view_wrapper
response = view_func(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/views/decorators/cache.py", line 80, in _view_wrapper
response = view_func(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/contrib/admin/sites.py", line 240, in inner
return view(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/contrib/admin/options.py", line 1945, in add_view
return self.changeform_view(request, None, form_url, extra_context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/utils/decorators.py", line 48, in _wrapper
return bound_method(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/utils/decorators.py", line 188, in _view_wrapper
result = _process_exception(request, e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/utils/decorators.py", line 186, in _view_wrapper
response = view_func(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/contrib/admin/options.py", line 1804, in changeform_view
return self._changeform_view(request, object_id, form_url, extra_context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/contrib/admin/options.py", line 1838, in _changeform_view
fieldsets = self.get_fieldsets(request, obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/contrib/admin/options.py", line 404, in get_fieldsets
return [(None, {"fields": self.get_fields(request, obj)})]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/contrib/admin/options.py", line 395, in get_fields
form = self._get_form_for_get_fields(request, obj)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/contrib/admin/options.py", line 786, in _get_form_for_get_fields
return self.get_form(request, obj, fields=None)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/contrib/admin/options.py", line 837, in get_form
return modelform_factory(self.model, **defaults)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/forms/models.py", line 652, in modelform_factory
return type(form)(class_name, (form,), form_class_attrs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/forms/models.py", line 310, in __new__
fields = fields_for_model(
^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/forms/models.py", line 239, in fields_for_model
formfield = formfield_callback(f, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/contrib/admin/options.py", line 228, in formfield_for_dbfield
return db_field.formfield(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/Development/django/tpt/tpt/fields.py", line 22, in formfield
return super(VolumeField, self).formfield(**defaults)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/db/models/fields/json.py", line 159, in formfield
return super().formfield(
^^^^^^^^^^^^^^^^^^
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/db/models/fields/__init__.py", line 1145, in formfield
return form_class(**defaults)
^^^^^^^^^^^^^^^^^^^^^^
File "/Users/stu/Development/django/tpt/tpt/forms.py", line 26, in __init__
super(VolumeWidgetField, self).__init__(list_fields, *args, **kwargs)
File "/Users/stu/.local/share/virtualenvs/storefront-QvD1zd1X/lib/python3.12/site-packages/django/forms/fields.py", line 1087, in __init__
super().__init__(**kwargs)
TypeError: Field.__init__() got an unexpected keyword argument 'encoder'
[13/May/2024 11:58:01] "GET /admin/inventory/measurement/add/ HTTP/1.1" 500 179729
I've tried extending models.CharField to test but I'm then getting an error CharFields must define a 'max_length' attribute.
and when I do specify a max_length I get a similar result to the JSONField but with max_length
instead of encode
: TypeError: Field.__init__() got an unexpected keyword argument 'max_length'
Any ideas what I'm doing wrong?
Thanks
After a little investigation I found that passing kwargs back to the VolumeWidgetField
class was the cause of my issue. Simply removing this allowed the code to run as expected, but I think a better way to deal with this is to just remove the unwanted attributes from kwargs.
Here is the updated tpt/forms.py
import json
from django import forms
class VolumeWidget(forms.widgets.MultiWidget):
template_name = 'stock/volume_template.html'
def __init__(self, attrs=None):
widgets = {'length': forms.NumberInput(),
'width': forms.NumberInput(),
'height': forms.NumberInput(),
'units': forms.Select(attrs=attrs, choices=[('mm','mm'), ('cm','cm'), ('m','m')])}
super(VolumeWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
data_list = json.loads(value)
return data_list
else:
return [0, 0, 0, '']
def get_context(self, name, value, attrs=None):
if value:
if not isinstance(value,list):
values = json.loads(value)
else:
values = value
else:
values = [0, 0, 0, 'cm']
return {'widget': {
'name': name,
'length': values[0],
'width': values[1],
'height': values[2],
'units': values[3],
'options': ['mm','cm','m']
}}
class VolumeWidgetField(forms.fields.MultiValueField):
widget = VolumeWidget
def __init__(self, *args, **kwargs):
clean_kwargs = {k: v for k, v in kwargs.items()
if k != 'encoder'
and k != 'decoder'
and k != 'length'
and k != 'width'
and k != 'height'
and k != 'units'
}
list_fields = [forms.fields.CharField(max_length=32),
forms.fields.CharField(max_length=32),
forms.fields.CharField(max_length=32),
forms.fields.CharField(max_length=16)]
super(VolumeWidgetField, self).__init__(list_fields, *args, **clean_kwargs)
def compress(self, data_list):
if not data_list:
return None
return json.dumps(data_list)
Note the clean_kwargs variable.
I hope this helps.