diegojromerolopez / django-async-include

Asynchronous inclusion of Django templates
MIT License
21 stars 7 forks source link

IndexError: list index out of range #6

Closed eriktelepovsky closed 6 years ago

eriktelepovsky commented 6 years ago

Any idea why do I get this? :/

{% async_include 'statistics/widgets/commissions.html' commission_list=commission_list %}
[25/Jan/2018 18:59:33] "POST /async_include/get HTTP/1.1" 200 20193
Internal Server Error: /async_include/get
Traceback (most recent call last):
  File "/Users/erik/env/swida/lib/python3.5/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/Users/erik/src/django/swida/source/async_include/views.py", line 24, in get_template
    json_body = jsonpickle.loads(request.body.decode('utf-8'))
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/__init__.py", line 152, in decode
    return unpickler.decode(string, backend=backend, keys=keys)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 27, in decode
    return context.restore(backend.decode(string), reset=reset)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 120, in restore
    value = self._restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 419, in _restore_dict
    data[k] = self._restore(v)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 419, in _restore_dict
    data[k] = self._restore(v)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 419, in _restore_dict
    data[k] = self._restore(v)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 251, in _restore_object
    return self._restore_object_instance(obj, cls)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 316, in _restore_object_instance
    return self._restore_object_instance_variables(obj, instance)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 361, in _restore_object_instance_variables
    instance = self._restore_state(obj, instance)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 366, in _restore_state
    state = self._restore(obj[tags.STATE])
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 419, in _restore_dict
    data[k] = self._restore(v)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 394, in _restore_list
    children = [self._restore(v) for v in obj]
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 394, in <listcomp>
    children = [self._restore(v) for v in obj]
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 181, in _restore_reduce
    f, args, state, listitems, dictitems = map(self._restore, reduce_val)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 419, in _restore_dict
    data[k] = self._restore(v)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 181, in _restore_reduce
    f, args, state, listitems, dictitems = map(self._restore, reduce_val)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 419, in _restore_dict
    data[k] = self._restore(v)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 243, in _restore_object
    instance = handler(self).restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/handlers.py", line 182, in restore
    params = (value,) + tuple([restore(i, reset=False) for i in args[1:]])
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/handlers.py", line 182, in <listcomp>
    params = (value,) + tuple([restore(i, reset=False) for i in args[1:]])
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 120, in restore
    value = self._restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 162, in _restore
    return restore(obj)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/jsonpickle/unpickler.py", line 218, in _restore_id
    return self._objs[obj[tags.ID]]
IndexError: list index out of range
diegojromerolopez commented 6 years ago

Hi Erik.

I would say that error is an userialization error. Could you try a debug and check if the sent serialized list of object is right?

eriktelepovsky commented 6 years ago

Hi Diego.

Thank you for your reply. It works fine, when I use Django's native include:

{% include 'statistics/widgets/commissions.html' with commission_list=commission_list %}

How exactly can I help you with the debugging? Let me know what you need to understand the issue better, please.

diegojromerolopez commented 6 years ago

Hello Erik.

Is commission_list of type "list"? Try using a QuerySet instead.

If you can, send me the model and the content of the HTTP request and will try it.

eriktelepovsky commented 6 years ago

Hi. commission_list is actually a QuerySet from ListView. I also tried object_list, the result is same. I also tried to truncate the content of the commissions.html file, didn't help.

My model:

class Commission(models.Model):
    ROLE_SALESMAN = 'SALESMAN'
    ROLE_DISPATCHER = 'DISPATCHER'
    ROLE_OTHER = 'OTHER'
    ROLES = (
        (ROLE_SALESMAN, _('salesman')),
        (ROLE_DISPATCHER, _('dispatcher')),
        (ROLE_OTHER, _('other')),
    )

    order = models.ForeignKey(Offer, on_delete=models.CASCADE)
    role = models.CharField(verbose_name=_('role'), choices=ROLES, max_length=10)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'))
    rate = models.DecimalField(_('rate'), help_text='%', max_digits=4, decimal_places=1, validators=[MinValueValidator(0), MaxValueValidator(100)], default=100)
    amount = models.DecimalField(_('amount'), help_text='€', max_digits=7, decimal_places=2, db_index=True)
    extra = models.DecimalField(_('extra'), help_text='€', max_digits=7, decimal_places=2, db_index=True)
    income = models.DecimalField(_('income'), help_text='€', max_digits=7, decimal_places=2, db_index=True, default=0)
    quota = models.DecimalField(_('quota'), help_text='€', max_digits=7, decimal_places=2, validators=[MinValueValidator(0)], default=0)
    quota_fulfillment = models.DecimalField(_('quota'), help_text='€', max_digits=8, decimal_places=2, validators=[MinValueValidator(0)], default=0)
    wallet = models.DecimalField(_('wallet'), help_text='€', max_digits=7, decimal_places=2, db_index=True, default=0)
    created = models.DateTimeField(_('created'), auto_now_add=True)
    modified = models.DateTimeField(_('modified'), auto_now=True)
    objects = CommissionQuerySet.as_manager()
    history = AuditlogHistoryField()

    class Meta:
        verbose_name = _('commission')
        verbose_name_plural = _('commissions')
        ordering = ('created',)
        unique_together = (('order', 'user'),)

    def __str__(self):
        return '[{}] {}: {}€'.format(self.order.order_number, self.user, self.total)

    def get_absolute_url(self):
        return self.order.get_absolute_url()
diegojromerolopez commented 6 years ago

Hello again.

It seems that the (un)serializer is broken. Try to do the following:

Create a test case in your project with the following (pseudo)code. The idea of this test is to check that the serialization is working well for the queryset Comission.

Other idea that I'm thinking is checking by hand if the serialization is done right.

from __future__ import unicode_literals

import jsonpickle
import uuid

from django.conf import settings
from django import template
from django.contrib.contenttypes.models import ContentType
from django.db.models.query import QuerySet
from django.template import loader, Context
from django.utils.text import slugify

from __future__ import unicode_literals

from Crypto.Cipher import AES

def encrypt(key, data):
    cipher = AES.new(key.encode("utf-8"), AES.MODE_EAX)
    encrypted_data, tag = cipher.encrypt_and_digest(data.encode("utf-8"))
    return cipher.nonce, encrypted_data, tag

def decrypt(key, nonce, encrypted_data, tag):
    cipher = AES.new(key.encode("utf-8"), AES.MODE_EAX, nonce.encode("latin-1"))
    data = cipher.decrypt_and_verify(encrypted_data.encode("latin-1"), tag.encode("latin-1"))
    return data.encode("utf-8")

class PickleTest(unittest.TestCase):
    """
    Tests the pickle for several objects.
    """

    def __init__(self, *args, **kwargs):
        super(PickleTest, self).__init__(*args, **kwargs)

    def test_pickle(self):
       # QuerySet that is not working
       comissions = Commission.objects.filter(**your_condition)

       model = Comission
       model_name = model.__name__
       app_name = ContentType.objects.get_for_model(model).app_label
       sql_query, params = comissions.query.sql_with_params()
       nonce, encrypted_sql, tag = crypto.encrypt(key="test_passwd", data=sql_query)
       comission_context = {
                "type": "QuerySet",
                "query": encrypted_sql.decode("latin-1"),
                "params": params,
                "nonce": nonce.decode("latin-1"),
                "tag": tag.decode("latin-1"),
                "app_name": app_name,
                "model": model_name,
            }
      self.assertEqual(
          jsonpickle.loads(jsonpickle.dumps(comission_context)), comission_context
      )

Try this and let's see if it works.

eriktelepovsky commented 6 years ago

Thanks for reply.

Here is my tests.py file:

from __future__ import unicode_literals

import unittest
import django
import jsonpickle
from async_include import crypto
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from swida.core.logistics.models import Commission

class PickleTest(unittest.TestCase):
    """
    Tests the pickle for several objects.
    """

    def __init__(self, *args, **kwargs):
        super(PickleTest, self).__init__(*args, **kwargs)

    def test_pickle(self):
        # QuerySet that is not working
        commissions = Commission.objects.all()

        model = Commission
        model_name = model.__name__
        app_name = ContentType.objects.get_for_model(model).app_label
        sql_query, params = commissions.query.sql_with_params()
        nonce, encrypted_sql, tag = crypto.encrypt(key=settings.SECRET_KEY[:16], data=sql_query)
        commission_context = {
            "type": "QuerySet",
            "query": encrypted_sql.decode("latin-1"),
            "params": params,
            "nonce": nonce.decode("latin-1"),
            "tag": tag.decode("latin-1"),
            "app_name": app_name,
            "model": model_name,
        }

        self.assertEqual(
            jsonpickle.loads(jsonpickle.dumps(commission_context)), commission_context
        )

Test passed OK :/

(swida) Eriks-MacBook-Pro:swida erik$ python ../manage.py test swida.core.logistics --keepdb --settings=swida.settings.dev
Using existing test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.003s

OK
Preserving test database for alias 'default'...

I confirm the issue is same for any Commission queryset (regardless the filter condition).

diegojromerolopez commented 6 years ago

Hello Erik.

Try removing the manager (objects field), I don't know if it's related, but I'm at loss. If that doesn't work, try leaving only one field and adding field by field trying to check in what field it doesn't work.

Forget it, it is not related because only the primary key is sent through the AJAX request.

Other test you can do is unserialize the AJAX request. That is:

Please, keep me posted with your progress.

eriktelepovsky commented 6 years ago

Actually, when I remove the manager, I get a different exception:

Internal Server Error: /async_include/get
Traceback (most recent call last):
  File "/Users/erik/env/swida/lib/python3.5/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/erik/env/swida/lib/python3.5/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
    return view_func(*args, **kwargs)
  File "/Users/erik/src/django/swida/source/async_include/views.py", line 69, in get_template
    tag=tag
  File "/Users/erik/src/django/swida/source/async_include/crypto.py", line 17, in decrypt
    return data.encode("utf-8")
AttributeError: 'bytes' object has no attribute 'encode'
eriktelepovsky commented 6 years ago

When I change line 17 in crypto.py file from

return data.encode("utf-8")

to:

return data

it works for me. It seems to be Python 3 compatibility issue.

eriktelepovsky commented 6 years ago

But the previous change fixes AttributeError: 'bytes' object has no attribute 'encode' exception only. I am going to debug the IndexError: list index out of range more deeply.

eriktelepovsky commented 6 years ago

It seems I found the issue. I updated jsonpickle library from 0.9.4 to 0.9.5 and it started working. Even with return data.encode("utf-8") in the crypto.py file which is weird to me, but it finally works! Thank you for your help.

diegojromerolopez commented 6 years ago

Excellent! Thank you for your error reports.

I just upgraded the requirements of this package in 0.5.3 version.

You can upgrade your project dependencies and check if everything works as intended, please confirm it indeed works in the 0.5.3 version so I can close this issue.

eriktelepovsky commented 6 years ago

I confirm. It works correctly in 0.5.3 version ;)

vibe78 commented 5 years ago

not helpful