I have been really struggling with this code for a while and I can't seem to see what is wrong with it. I have been trying to implement 2 model forms in one view here. So the ShoesForm only shows when the user wants to create or edit a shoe product. (I hide the form using javascript). The fields in the model Shoes is all blank=True so the field.cleaned_data is always confirmed.

However, whenever I try to edit an existing non-shoe item, I got an error that says UnboundLocalError: local variable 'pid' referenced before assignment. I know this means that there is a case where pid is not assigned but I can't see it. Any suggestions?

views.py

def create_or_update_inventory(request, product_id=None):
    """Combined form view for BaseInventory and Shoes model

    The two available forms in the context are:
        1. product_form: linked to BaseInventory model
        2. shoes_form: linked to Shoes model

    If the form is submitted, it will first create the product for the BaseInventory model.
    If any of the forms inside the shoes_form is filled, it will take the product and link
    it to the inventory field in the Shoes model and then it will save the shoes_form.

    This vies uses the inventory_list/product_detail.html as its template.
    """
    context = {}

    # if form is posted, this happens
    # TODO: fix pid not found when editing non-shoe item
    if request.method == 'POST':

        try:
            instance = get_object_or_404(Product, product_id=product_id)
            instance2 = get_object_or_404(Shoes, inventory__product_id=product_id)

            product_form = ProductForm(request.POST, instance=instance)
            shoes_form = ShoesForm(request.POST, instance=instance2)
            pid = instance.product_id

        except:
            product_form = ProductForm(request.POST)
            shoes_form = ShoesForm(request.POST)


        if product_form.is_valid() and shoes_form.is_valid():
            product_form.save()
            pid = product_form.instance.product_id
            product = Product.objects.get(product_id=pid)

            # if the shoes_form is filled, save to Shoes model, else ignore this
            if shoes_form.cleaned_data['collection'] or \
                    shoes_form.cleaned_data['material'] or \
                    shoes_form.cleaned_data['ground_type']:
                shoes_form.cleaned_data['inventory'] = product
                shoes_form.instance.inventory = product
                shoes_form.save()

        # redirect to view all fields
        return HttpResponseRedirect(reverse('inventory_list:product-detail', kwargs={'product_id': pid}))


    else:
        if product_id:
            # if the user wants to update product, fill in with preexisting values
            item = Product.objects.get(product_id=product_id)
            pid = item.product_id
            product_form = ProductForm(
                initial={
                    'product_id': item.product_id,
                    'name': item.name,
                    'color_primary': item.color_primary,
                    'color_secondary': item.color_secondary,
                    'category': item.category,
                    'description': item.description,
                    'gender': item.gender,
                    'active': item.active,
                }
            )
            if item.category == Product.SHOES:
                shoes_form = ShoesForm(
                    initial={
                        'collection': item.shoes.collection,
                        'material': item.shoes.material,
                        'ground_type': item.shoes.ground_type,
                    }
                )
            else:
                shoes_form = ShoesForm()

        else:
            # if the user wants to create product, create empty form
            product_form = ProductForm()
            shoes_form = ShoesForm()

    # the list of contexts for the front end
    context.update({
        'product_form': product_form,
        'shoes_form': shoes_form,
        'colors': Color.objects.all(),
    })

    return render(request, 'inventory_list/product_detail.html', context)

EDIT: the error log

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/django/contrib/staticfiles/handlers.py", line 66, in __call__
    return self.application(environ, start_response)
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/wsgi.py", line 146, in __call__
    response = self.get_response(request)
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 81, in get_response
    response = self._middleware_chain(request)
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 37, in inner
    response = response_for_exception(request, exc)
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 87, in response_for_exception
    response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 122, in handle_uncaught_exception
    return debug.technical_500_response(request, *exc_info)
  File "/usr/local/lib/python3.6/site-packages/django_extensions/management/technical_response.py", line 37, in null_technical_500_response
    six.reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.6/site-packages/six.py", line 692, in reraise
    raise value.with_traceback(tb)
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 35, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 128, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 126, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.6/contextlib.py", line 52, in inner
    return func(*args, **kwds)
  File "/app/inventory_list/views.py", line 133, in create_or_update_inventory
    return HttpResponseRedirect(reverse('inventory_list:product-detail', kwargs={'product_id': pid}))
UnboundLocalError: local variable 'pid' referenced before assignment

2 Answers

0
Rabin Lama Dong On

You have to first understand how exception handling works in general. It's clear. In your above code, when an error occurs in your try block, the except block goes into action, making the pid variable undefined. And after that again if your data does not pass the if product_form.is_valid() and shoes_form.is_valid(): test you will be left with undefined pid var which you are using in your response.

So, if you are just looking to create an object if it does not exist, then check this out get_or_create()

0
xxbinxx On

The very first try except is throwing 404 exception. On catching it in except block you're not defining any pid so when the control goes to your reverse it throws UnboundLocalError for pid.

updated some other part too as I felt the code was redundant.

try this (I wrote this in hurry please don't mind if it has any grammar errors, but you will get the idea of what to do) I'll update the answer with better explanation if it works.

def create_or_update_inventory(request, product_id=None):
"""Combined form view for BaseInventory and Shoes model

The two available forms in the context are:
    1. product_form: linked to BaseInventory model
    2. shoes_form: linked to Shoes model

If the form is submitted, it will first create the product for the BaseInventory model.
If any of the forms inside the shoes_form is filled, it will take the product and link
it to the inventory field in the Shoes model and then it will save the shoes_form.

This vies uses the inventory_list/product_detail.html as its template.
"""
context = {}

# if form is posted, this happens
# TODO: fix pid not found when editing non-shoe item
if request.method == 'POST':

    try:
        product_instance = Product.objects.get(product_id=product_id)
        product_form = ProductForm(request.POST, instance=product_instance)
    except Product.DoesNotExist:
        product_form = ProductForm(request.POST)

    try:
        shoes_instance = Shoes.objects.get(inventory__product_id=product_id)
        shoes_form = ShoesForm(request.POST, instance=shoes_instance)
    except Shoes.DoesNotExist:
        shoes_form = ShoesForm(request.POST)

    if product_form.is_valid() and shoes_form.is_valid():
        product_form.save()
        product = Product.objects.get(product_id=product_id)

        # if the shoes_form is filled, save to Shoes model, else ignore this
        if shoes_form.cleaned_data['collection'] or \
                shoes_form.cleaned_data['material'] or \
                shoes_form.cleaned_data['ground_type']:
            shoes_form.cleaned_data['inventory'] = product
            shoes_form.instance.inventory = product
            shoes_form.save()

    if product_id:
        # redirect to view all fields
        return HttpResponseRedirect(reverse('inventory_list:product-detail', kwargs={'product_id': product_id}))

elif product_id:
        # if the user wants to update product, fill in with preexisting values
        item = Product.objects.get(product_id=product_id)
        pid = item.product_id
        product_form = ProductForm(
            initial={
                'product_id': item.product_id,
                'name': item.name,
                'color_primary': item.color_primary,
                'color_secondary': item.color_secondary,
                'category': item.category,
                'description': item.description,
                'gender': item.gender,
                'active': item.active,
            }
        )
        if item.category == Product.SHOES:
            shoes_form = ShoesForm(
                initial={
                    'collection': item.shoes.collection,
                    'material': item.shoes.material,
                    'ground_type': item.shoes.ground_type,
                }
            )
        else:
            shoes_form = ShoesForm()

else:
    # if the user wants to create product, create empty form
    product_form = ProductForm()
    shoes_form = ShoesForm()

# the list of contexts for the front end
context.update({
    'product_form': product_form,
    'shoes_form': shoes_form,
    'colors': Color.objects.all(),
})

return render(request, 'inventory_list/product_detail.html', context)

Let me know what do you think