Possible ecommerce plugin python bug leading to error 500

I enabled the tutor ecommerce plugin when I thought it was required for course certificates (but now I suspect it’s not required.) However, I started getting HTTP 500 errors when I would try to go to the About page for any of the courses except the first test one I had set up in ecommerce. From the logs it looks like there’s a python bug related to trying to look up the price of a course (when that course isn’t managed by ecommerce) and that price being empty. See the below log.

lms_1               | 2021-03-17 11:21:25,989 INFO 6 [tracking] [user 3] [ip 173.73.80.226] logger.py:42 - {"name": "/courses/course-v1:TestOrg+TestCourse+2021_V1/about", "context": {"course_id": "course-v1:TestOrg+TestCourse+2021_V1", "course_user_tags": {}, "user_id": 3, "path": "/courses/course-v1:TestOrg+TestCourse+2021_V1/about", "org_id": "TestOrg"}, "username": "User", "session": "3fc229763de1a25fdb35125cf9da2fbd", "ip": "173.73.80.226", "agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36", "host": "x.my.domain", "referer": "https://studio.x.my.domain/", "accept_language": "en-US,en;q=0.9", "event": "{\"GET\": {}, \"POST\": {}}", "time": "2021-03-17T11:21:25.989016+00:00", "event_type": "/courses/course-v1:TestOrg+TestCourse+2021_V1/about", "event_source": "server", "page": null}
lms_1               | 2021-03-17 11:21:26,008 INFO 6 [openedx.core.djangoapps.cors_csrf.helpers] [user 3] [ip 173.73.80.226] helpers.py:61 - Domain 'studio.x.my.domain' is not on the cross domain whitelist.  Add the domain to `CORS_ORIGIN_WHITELIST` or set `CORS_ORIGIN_ALLOW_ALL` to True in the settings.
lms_1               | 2021-03-17 11:21:26,088 ERROR 6 [root] [user None] [ip None] signals.py:22 - Uncaught exception from None
lms_1               | Traceback (most recent call last):
lms_1               |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
lms_1               |     response = get_response(request)
lms_1               |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
lms_1               |     response = self.process_exception_by_middleware(e, request)
lms_1               |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
lms_1               |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
lms_1               |   File "/opt/pyenv/versions/3.8.6/lib/python3.8/contextlib.py", line 75, in inner
lms_1               |     return func(*args, **kwds)
lms_1               |   File "/openedx/venv/lib/python3.8/site-packages/django/utils/decorators.py", line 142, in _wrapped_view
lms_1               |     response = view_func(request, *args, **kwargs)
lms_1               |   File "/openedx/edx-platform/common/djangoapps/util/views.py", line 43, in inner
lms_1               |     response = view_func(request, *args, **kwargs)
lms_1               |   File "/openedx/edx-platform/common/djangoapps/util/cache.py", line 91, in wrapper
lms_1               |     return view_func(request, *args, **kwargs)
lms_1               |   File "./lms/djangoapps/courseware/views/views.py", line 925, in course_about
lms_1               |     registration_price, course_price = get_course_prices(course)
lms_1               |   File "/openedx/edx-platform/common/djangoapps/course_modes/models.py", line 852, in get_course_prices
lms_1               |     registration_price = CourseMode.min_course_price_for_currency(
lms_1               |   File "/openedx/edx-platform/common/djangoapps/course_modes/models.py", line 769, in min_course_price_for_currency
lms_1               |     return min(mode.min_price for mode in modes if mode.currency.lower() == currency.lower())
lms_1               | ValueError: min() arg is an empty sequence
lms_1               | 2021-03-17 11:21:26,214 ERROR 6 [django.request] [user 3] [ip 173.73.80.226] log.py:222 - Internal Server Error: /courses/course-v1:TestOrg+TestCourse+2021_V1/about
lms_1               | Traceback (most recent call last):
lms_1               |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
lms_1               |     response = get_response(request)
lms_1               |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
lms_1               |     response = self.process_exception_by_middleware(e, request)
lms_1               |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
lms_1               |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
lms_1               |   File "/opt/pyenv/versions/3.8.6/lib/python3.8/contextlib.py", line 75, in inner
lms_1               |     return func(*args, **kwds)
lms_1               |   File "/openedx/venv/lib/python3.8/site-packages/django/utils/decorators.py", line 142, in _wrapped_view
lms_1               |     response = view_func(request, *args, **kwargs)
lms_1               |   File "/openedx/edx-platform/common/djangoapps/util/views.py", line 43, in inner
lms_1               |     response = view_func(request, *args, **kwargs)
lms_1               |   File "/openedx/edx-platform/common/djangoapps/util/cache.py", line 91, in wrapper
lms_1               |     return view_func(request, *args, **kwargs)
lms_1               |   File "./lms/djangoapps/courseware/views/views.py", line 925, in course_about
lms_1               |     registration_price, course_price = get_course_prices(course)
lms_1               |   File "/openedx/edx-platform/common/djangoapps/course_modes/models.py", line 852, in get_course_prices
lms_1               |     registration_price = CourseMode.min_course_price_for_currency(
lms_1               |   File "/openedx/edx-platform/common/djangoapps/course_modes/models.py", line 769, in min_course_price_for_currency
lms_1               |     return min(mode.min_price for mode in modes if mode.currency.lower() == currency.lower())
lms_1               | ValueError: min() arg is an empty sequence
lms_1               | [pid: 6|app: 0|req: 590/1399] 172.18.0.15 () {58 vars in 1860 bytes} [Wed Mar 17 11:21:25 2021] GET /courses/course-v1:TestOrg+TestCourse+2021_V1/about => generated 8976 bytes in 358 msecs (HTTP/1.0 500) 7 headers in 518 bytes (1 switches on core 0)
nginx_1             | 172.18.0.3 - - [17/Mar/2021:11:21:26 +0000] http://x.my.domain "GET /courses/course-v1:TestOrg+TestCourse+2021_V1/about HTTP/1.1" 500 8976 "https://studio.x.my.domain/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "173.73.80.226"

However, I just went and did

tutor plugins disable discovery ecommerce
tutor config save
tutor local quickstart

But I seem to still be getting the error…

Edit: So on a hunch I went and added a “course mode” for the afflicted course, setting it to Honor. But that also sets a notional price of 0. After setting the course mode, then the About page would show up fine without an error 500. So basically I expect I’ll need to do this for all my courses. But is this expected or is it a bug?

@oedx Unfortunately no, this behaviour is not expected :sweat_smile: I had a look at the code, and I’m pretty sure you should not be creating course modes for your course if you don’t use ecommerce. According to the source code of the modes_for_course class method, when no course mode is associated to your course, the default one will be used (Audit, on my vanilla installation).

However, the default mode might be filtered out by the currency condition:

min(mode.min_price for mode in modes if mode.currency.lower() == currency.lower())

Can you please check the values of these two settings:

tutor local run lms ./manage.py lms shell
from django.conf import settings
print(settings.PAID_COURSE_REGISTRATION_CURRENCY)
print(settings.COURSE_MODE_DEFAULTS['currency'])

These two values should match. If they don’t, we have a problem.

This guide suggests that in order to get certificates set up I need to set a course mode:

Also I further set a default course mode of Honor per this:

from django.conf import settings
print(settings.PAID_COURSE_REGISTRATION_CURRENCY)
[‘usd’, ‘$’]
print(settings.COURSE_MODE_DEFAULTS[‘currency’])
eur

So yeah, that seems to be a problem. Is the “PAID_COURSE_REGISTRATION_CURRENCY” possibly left over even after I disabled the ecommerce plugin or is it a side effect of setting a course mode? How can I change that?

Wait, how come this is “eur”? Neither tutor nor tutor-ecommerce change this value (and this is an issue btw) and the default in edx-platform, is ‘usd’. Are you running a fork of edx-platform, or do you have custom edx-platform settings, or a custom plugin?

Ah ha. That custom plugin which I said I just copied and pasted from

without reading since it seemed to just work, has eur set! So I’ll change that to usd

1 Like

But what if we need to use something that is not USD for COURSE_MODE_DEFAULTS[‘currency’] ?

Where is the most appropriate place to set it for CAD for example ?

This is set by the ECOMMERCE_CURRENCY Tutor setting.

Thank you. It worked. I guess I didn’t read it or spot it in the tutor-ecommerce README file.