trying to learn custom validators

30 views
Skip to first unread message

clavie...@gmail.com

unread,
Jul 16, 2018, 4:01:49 PM7/16/18
to Django users
I have a legacy database that we want to replace, and Django is still new to us. There are some 40,000 items in the old db, and each item has an item_number that is not necessarily the primary key; that way, the users can maintain their own sku number system and we can maintain data integrity without either of us getting in the other's way. And it also helps guarantee data integrity as we migrate the data from the old db to our new db, since Django will assign PK's and manage FKs as it goes, and leave the item_numbers intact. 

I'm running into trouble though, because Django Views and Forms all assume that, when performing a lookup, the input is going to be a primary key. For example, I have an item where item.item_number = '515874'. So when I put that item_number in, Django looks for an item where item.pk = 515874 and raises a ValidationError--because there is no such item. The actual PK for item '515874' is 41293. So I need to take the user's input, use it to get the right item based on its item_number, and give it back to Django to use instead. 

CreateView and {{ form.as_table }} default to populating a dropdown, which would mean that the whole item_num/pk problem would go away... but with Django querying *all* 40,000 items, the page literally took 20 seconds to load. So the users are going to need to type in item_numbers which I'll have to convert to PKs. 

Overriding form_valid() and clean() weren't working, because the no-such-item ValidationError had already been raised before either method could be called. Defining a custom field and overriding that field's validate method seems to be the right way to go, but I'm still running into issues. The validate method is being called and is returning the correct value, but Django seems to be throwing it away. When I step through it with the debugger, I see the item where (item.id = 41293 and item.item_number = '515874') is correctly being returned. But when Django calls clean() next, that item is nowhere to be found and it's busy cleaning '515874', which was the original input. And then it breaks.

This particular view/ModelForm is a simple tool for non-programmers to be able to evaluate a small part of a background process.

class ItemsTestCollection(models.Model):
"""An item to be tested in Box Opt."""
item = models.ForeignKey('item.Item', on_delete=models.CASCADE)
qty = models.IntegerField()
case = models.ForeignKey('EvaluateTestCase', on_delete=models.CASCADE)
box = models.ForeignKey('BoxResults', on_delete=models.CASCADE, null=True)

class ItemNumField(forms.CharField):

def validate(self, value):
# item = Item.objects.get(item_number=value)
# value = item.id
# return value
return Item.objects.get(item_number=value)

class AddItem(forms.ModelForm):

item = ItemNumField()

class Meta:
model = ItemsTestCollection
fields = ['item', 'qty', 'case']

class AddItemsToEvaluateCreateView(CreateView):
model = ItemsTestCollection
form_class = AddItem
template_name = 'utils\evaluate.html'

def get_queryset(self):
return ItemsTestCollection.objects.filter(case_id=self.kwargs['pk'])

def get_object(self, queryset=None):
return EvaluateTestCase.objects.get(pk=self.kwargs['pk'])

def get_success_url(self):
if 'add_more' in self.request.POST:
return reverse('utils:evaluate', kwargs={'pk': self.kwargs['pk']})
elif 'optimize' in self.request.POST:
return reverse('utils:results', kwargs={'pk': self.kwargs['pk']})
else:
return '/'

def get_context_data(self, **kwargs):
context = super().get_context_data()
context['case_id'] = self.kwargs['pk']
context['items_in_order'] = ItemsTestCollection.objects.filter(case_id=self.kwargs['pk'])
return context

ValueError at /utils/box-optimization/add-items/118

Cannot assign "'515874'": "ItemsTestCollection.item" must be a "Item" instance.
Request Method:POST
Request URL:http://localhost:8000/utils/box-optimization/add-items/118
Django Version:2.0.5
Exception Type:ValueError
Exception Value:
Cannot assign "'515874'": "ItemsTestCollection.item" must be a "Item" instance.
Exception Location:C:\miniconda3\envs\django\lib\site-packages\django\db\models\fields\related_descriptors.py in __set__, line 197
Python Executable:C:\miniconda3\envs\django\python.exe
Python Version:3.6.5
Python Path:
['C:\\WMS Repository\\Warehouse Management System',
 'C:\\Program Files\\JetBrains\\PyCharm 2017.3.3\\helpers\\pydev',
 'C:\\WMS Repository\\Warehouse Management System',
 'C:\\Program Files\\JetBrains\\PyCharm 2017.3.3\\helpers\\pydev',
 'C:\\Users\\heast\\.PyCharm2018.1\\system\\cythonExtensions',
 'C:\\miniconda3\\envs\\django\\python36.zip',
 'C:\\miniconda3\\envs\\django\\DLLs',
 'C:\\miniconda3\\envs\\django\\lib',
 'C:\\miniconda3\\envs\\django',
 'C:\\Users\\heast\\AppData\\Roaming\\Python\\Python36\\site-packages',
 'C:\\miniconda3\\envs\\django\\lib\\site-packages',
 'C:\\Program Files\\JetBrains\\PyCharm '
 '2017.3.3\\helpers\\pycharm_matplotlib_backend']
Server time:Mon, 16 Jul 2018 15:46:11 -0400
Environment:


Request Method: POST

Django Version: 2.0.5
Python Version: 3.6.5
Installed Applications:
['item.apps.ItemConfig',
 'inventory.apps.InventoryConfig',
 'fulfillment.apps.FulfillmentConfig',
 'manifest.apps.ManifestConfig',
 'order.apps.OrderConfig',
 'utils.apps.UtilsConfig',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback:

File "C:\miniconda3\envs\django\lib\site-packages\django\core\handlers\exception.py" in inner
  35.             response = get_response(request)

File "C:\miniconda3\envs\django\lib\site-packages\django\core\handlers\base.py" in _get_response
  128.                 response = self.process_exception_by_middleware(e, request)

File "C:\miniconda3\envs\django\lib\site-packages\django\core\handlers\base.py" in _get_response
  126.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "C:\miniconda3\envs\django\lib\site-packages\django\views\generic\base.py" in view
  69.             return self.dispatch(request, *args, **kwargs)

File "C:\miniconda3\envs\django\lib\site-packages\django\views\generic\base.py" in dispatch
  89.         return handler(request, *args, **kwargs)

File "C:\miniconda3\envs\django\lib\site-packages\django\views\generic\edit.py" in post
  172.         return super().post(request, *args, **kwargs)

File "C:\miniconda3\envs\django\lib\site-packages\django\views\generic\edit.py" in post
  141.         if form.is_valid():

File "C:\miniconda3\envs\django\lib\site-packages\django\forms\forms.py" in is_valid
  179.         return self.is_bound and not self.errors

File "C:\miniconda3\envs\django\lib\site-packages\django\forms\forms.py" in errors
  174.             self.full_clean()

File "C:\miniconda3\envs\django\lib\site-packages\django\forms\forms.py" in full_clean
  378.         self._post_clean()

File "C:\miniconda3\envs\django\lib\site-packages\django\forms\models.py" in _post_clean
  396.             self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)

File "C:\miniconda3\envs\django\lib\site-packages\django\forms\models.py" in construct_instance
  60.             f.save_form_data(instance, cleaned_data[f.name])

File "C:\miniconda3\envs\django\lib\site-packages\django\db\models\fields\__init__.py" in save_form_data
  838.         setattr(instance, self.name, data)

File "C:\miniconda3\envs\django\lib\site-packages\django\db\models\fields\related_descriptors.py" in __set__
  197.                     self.field.remote_field.model._meta.object_name,

Exception Type: ValueError at /utils/box-optimization/add-items/118
Exception Value: Cannot assign "'515874'": "ItemsTestCollection.item" must be a "Item" instance.

Matthew Pava

unread,
Jul 16, 2018, 5:12:40 PM7/16/18
to django...@googlegroups.com

What I would do in that situation is add a custom field to the model form that holds the item number.  Then, in the save method, convert that to the Item object.

 

class AddItem(forms.ModelForm):

 

    item_number = IntegerField()

 

    class Meta:

        model = ItemsTestCollection

        fields = ['qty', 'case']

 

    def save(self, *args, **kwargs):

              instance = super().save(commit=False)

        instance.item = Item.objects.get(number=self.cleaned_data.get(‘item_number’))

        instance.save()

        return instance

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/16a9d28f-a23a-4112-acc5-f7be9bef4d12%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

clavie...@gmail.com

unread,
Jul 17, 2018, 9:34:09 AM7/17/18
to Django users
Oh, this is perfect! Works exactly the way I need it. Thank you so much for your help!

To post to this group, send email to djang...@googlegroups.com.

Reply all
Reply to author
Forward
0 new messages