Bug 15332 - layerindex: Branch Comparison throws Internal Server Error
Summary: layerindex: Branch Comparison throws Internal Server Error
Status: IN PROGRESS REVIEW
Alias: None
Product: Layer Index
Classification: Yocto Project Subprojects
Component: Layer Index (show other bugs)
Version: 5.0
Hardware: x86 Multiple
: Medium+ major
Target Milestone: 6.2 M1
Assignee: Tim Orling
QA Contact: apoorv sangal
URL:
Whiteboard:
Depends on:
Blocks:
 
Reported: 2023-12-28 05:14 UTC by Tim Orling
Modified: 2026-10-18 02:20 UTC (History)
1 user (show)

See Also:
OS type for building Yocto: ---
Type of Regression: ---
Verified:
Documentation change: No (bug/feature does not impact docs)


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Tim Orling 2023-12-28 05:14:20 UTC
If you try to compare e.g. "master" to "nanbield", an Internal Server Error is thrown.

The debug output gives us a strong hint:

NameError at /layerindex/branch_comparison/
name 'parser_version' is not defined
...
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/dist-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.10/dist-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.10/dist-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/django/views/generic/base.py", line 143, in dispatch
    return handler(request, *args, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/django/views/generic/edit.py", line 144, in get
    return self.render_to_response(self.get_context_data())
  File "/opt/layerindex/layerindex/views.py", line 1850, in get_context_data
    context['added'], context['changed'], context['removed'] = self.get_recipes(from_branch, to_branch, layer_ids)
  File "/opt/layerindex/layerindex/views.py", line 1810, in get_recipes
    recipe.to_versions = sorted(recipe.to_versions, key=lambda item: parser_version(item.pv))
  File "/opt/layerindex/layerindex/views.py", line 1810, in <lambda>
    recipe.to_versions = sorted(recipe.to_versions, key=lambda item: parser_version(item.pv))

Exception Type: NameError at /layerindex/branch_comparison/
Exception Value: name 'parser_version' is not defined
Raised during: layerindex.views.BranchCompareView
...
Comment 1 Tim Orling 2023-12-28 05:19:40 UTC
The issue is a typo in:
https://git.yoctoproject.org/layerindex-web/tree/layerindex/views.py#n1794

recipe.to_versions = sorted(recipe.to_versions, key=lambda item: parser_version(item.pv))

Where parser_version should be parse_version.
Comment 2 Tim Orling 2023-12-30 05:58:52 UTC
https://patchwork.yoctoproject.org/project/yocto/patch/01094d0e2a2c4f8df9feea877b2159677c642ab8.1703911977.git.tim.orling@konsulko.com/

This one was subtle. VSCode syntax highlighting is to be credited with actually catching the fix (although in hindsight the debug log was fairly obvious).
Comment 4 Tim Orling 2024-02-09 00:33:27 UTC
If you try any other layer, such as meta-oe, you will still get an Internal Server Error:

Environment:


Request Method: GET
Request URL: http://<local instance>/layerindex/branch_comparison/?from_branch=29&to_branch=1&layers=2%2C1&l=2&l=1

Django Version: 4.2.9
Python Version: 3.10.12
Installed Applications:
('django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.sites',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.admin',
 'django.contrib.admindocs',
 'layerindex',
 'rrs',
 'django_registration',
 'reversion',
 'reversion_compare',
 'captcha',
 'axes',
 'rest_framework',
 'corsheaders')
Installed Middleware:
('corsheaders.middleware.CorsMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'axes.middleware.AxesMiddleware',
 'layerindex.middleware.NonAtomicRevisionMiddleware')



Traceback (most recent call last):
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/fields/__init__.py", line 2053, in get_prep_value
    return int(value)

The above exception (invalid literal for int() with base 10: ',') was the direct cause of the following exception:
  File "/usr/local/lib/python3.10/dist-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.10/dist-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.10/dist-packages/django/views/generic/base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/django/views/generic/base.py", line 143, in dispatch
    return handler(request, *args, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/django/views/generic/edit.py", line 144, in get
    return self.render_to_response(self.get_context_data())
  File "/opt/layerindex/layerindex/views.py", line 1875, in get_context_data
    context['added'], context['changed'], context['removed'] = self.get_recipes(from_branch, to_branch, layer_ids)
  File "/opt/layerindex/layerindex/views.py", line 1811, in get_recipes
    from_recipes = from_recipes.filter(layerbranch__layer__in=layer_ids)
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/query.py", line 1436, in filter
    return self._filter_or_exclude(False, args, kwargs)
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/query.py", line 1454, in _filter_or_exclude
    clone._filter_or_exclude_inplace(negate, args, kwargs)
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/query.py", line 1461, in _filter_or_exclude_inplace
    self._query.add_q(Q(*args, **kwargs))
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/sql/query.py", line 1546, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/sql/query.py", line 1577, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/sql/query.py", line 1492, in build_filter
    condition = self.build_lookup(lookups, col, value)
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/sql/query.py", line 1319, in build_lookup
    lookup = lookup_class(lhs, rhs)
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/lookups.py", line 27, in __init__
    self.rhs = self.get_prep_lookup()
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/fields/related_lookups.py", line 92, in get_prep_lookup
    self.rhs = [target_field.get_prep_value(v) for v in self.rhs]
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/fields/related_lookups.py", line 92, in <listcomp>
    self.rhs = [target_field.get_prep_value(v) for v in self.rhs]
  File "/usr/local/lib/python3.10/dist-packages/django/db/models/fields/__init__.py", line 2055, in get_prep_value
    raise e.__class__(

Exception Type: ValueError at /layerindex/branch_comparison/
Exception Value: Field 'id' expected a number but got ','.
Comment 5 Tim Orling 2024-02-09 00:40:41 UTC
If you clear 'openembedded-core' and add only 'meta-oe' the comparison succeeds. So it looks like multiple selections are not being split() properly or something along those lines.

Successful:
https://layers.openembedded.org/layerindex/branch_comparison/?from_branch=29&to_branch=1&layers=2&l=2

vs.

Failure:
https://layers.openembedded.org/layerindex/branch_comparison/?from_branch=29&to_branch=1&layers=2%2C1&l=2&l=1

So the way that URL is being combined is not being handled as expected.
Comment 6 Tim Orling 2024-02-09 00:51:24 UTC
Also, for some reason, meta-ros2-humble cannot be compared from nanbield to master (even though both LayerBranches exist):

https://layers.openembedded.org/layerindex/branch_comparison/?from_branch=29&to_branch=1&layers=602&l=602
Comment 7 Tim Orling 2024-08-10 21:12:55 UTC
Something is wrong with the layer_id parsing, because if you hand code an url, like
https://layers.openembedded.org/layerindex/branch_comparison/?from_branch=14&to_branch=30&layers=2

It properly shows the changes from sumo to scarthgap for meta-oe (layer_id = 2).

If you click on the Plain Text button, you can see only one layer was selected:
"
From sumo (Yocto Project 2.5) to scarthgap (Yocto Project 5.0) (layers: meta-oe)


Added
-----
abseil-cpp 20240116.2
ace 6.5.19
ace-cloud-editor 02.07.17+git
...
"

If you choose any layer with more than 1 digit, such as meta-virtualization (which appears to be layer_id = 59) you instead see layer_id = 5 (e100-bsp) and layer_id = 9 (meta-gumstix) chosen!

https://layers.openembedded.net/layerindex/branch_comparison/?from_branch=14&to_branch=30&layers=59

"
From sumo (Yocto Project 2.5) to scarthgap (Yocto Project 5.0) (layers: e100-bsp, meta-gumstix)
...
"

If you use the GUI to select only meta-virtualization in the filter, the resulting URL is:
https://layers.openembedded.net/layerindex/branch_comparison/?from_branch=14&to_branch=30&layers=59&l=59

Which still resolves to the same two layers above (layer_id = 5 and layer_id = 9)

If you start with the default GUI (where oe-core is selected) and then add meta-virtualization, the resulting URL throws an Internal Server Error (expecting a layer_id and gets a ','):

https://layers.moto-timo.net/layerindex/branch_comparison/?from_branch=14&to_branch=30&layers=59%2C1&l=59&l=1

So the drop down menu is not creating a proper URL, and more than single digit layer_id is not being parsed properly.
Comment 8 Tim Orling 2024-08-16 03:44:54 UTC
The answer is rather obvious in hindsight. If you want a list of items to iterate on, ask for a list. (self.request.GET.getlist NOT self.request.GET.get). The first gives you a list, the second gives you a string which if you iterate over it, you get char.

diff --git a/layerindex/views.py b/layerindex/views.py
index 49bda30..908dfe1 100644
--- a/layerindex/views.py
+++ b/layerindex/views.py
@@ -1858,7 +1858,7 @@ class BranchCompareView(FormView):
         to_branch_id = self.request.GET.get('to_branch', None)
         if to_branch_id is not None:
             initial['to_branch'] = get_object_or_404(Branch, id=to_branch_id)
-        initial['layers'] = self.request.GET.get('layers', int(LayerItem.objects.get(name=settings.CORE_LAYER_NAME).id))
+        initial['layers'] = self.request.GET.getlist('layers', int(LayerItem.objects.get(name=settings.CORE_LAYER_NAME).id))
         logger.debug(f"initial['layers']: {initial['layers']}")
         return initial

@@ -1867,7 +1867,7 @@ class BranchCompareView(FormView):
         from_branch_id = self.request.GET.get('from_branch', None)
         to_branch_id = self.request.GET.get('to_branch', None)

-        layer_ids = list(self.request.GET.get('layers', int(LayerItem.objects.get(name=settings.CORE_LAYER_NAME).id)))
+        layer_ids = self.request.GET.getlist('layers', int(LayerItem.objects.get(name=settings.CORE_LAYER_NAME).id))
         from_branch = None
         if from_branch_id is not None:
             from_branch = get_object_or_404(Branch, id=from_branch_id)