diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31d92daf..044edd5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: matrix: os: [ubuntu-20.04] python-version: ['3.8'] - toxenv: [quality, docs, pii_check, django32, django40] + toxenv: [quality, docs, pii_check, django42] steps: - uses: actions/checkout@v3 @@ -37,7 +37,7 @@ jobs: run: tox - name: Run coverage - if: matrix.python-version == '3.8' && matrix.toxenv == 'django32' + if: matrix.python-version == '3.8' uses: codecov/codecov-action@v4 with: flags: unittests diff --git a/forum.egg-info/entry_points.txt b/forum.egg-info/entry_points.txt new file mode 100644 index 00000000..37c7a03c --- /dev/null +++ b/forum.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[lms.djangoapp] +forum = forum.apps:ForumConfig \ No newline at end of file diff --git a/forum/apps.py b/forum/apps.py index 822fea57..cd03bb3e 100644 --- a/forum/apps.py +++ b/forum/apps.py @@ -10,4 +10,20 @@ class ForumConfig(AppConfig): Configuration for the forum Django application. """ - name = 'forum' + name = "forum" + + plugin_app = { + "url_config": { + "lms.djangoapp": { + "namespace": "forum", + "regex": r"^forum", + "relative_path": "urls", + } + }, + "settings_config": { + "lms.djangoapp": { + "common": {"relative_path": "settings.common"}, + "production": {"relative_path": "settings.production"}, + } + }, + } diff --git a/forum/settings/__init__.py b/forum/settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/forum/settings/common.py b/forum/settings/common.py new file mode 100644 index 00000000..c8489287 --- /dev/null +++ b/forum/settings/common.py @@ -0,0 +1,11 @@ +""" +Common settings for forum app. +""" + + +def plugin_settings(settings): + """ + Common settings for forum app + Set these variables in the Tutor Config or lms.yml for local testing + """ + settings.FORUM_PORT = "4567" diff --git a/forum/settings/production.py b/forum/settings/production.py new file mode 100644 index 00000000..647082fe --- /dev/null +++ b/forum/settings/production.py @@ -0,0 +1,10 @@ +""" +Production settings for forum app. +""" + + +def plugin_settings(settings): + """ + Production settings for forum app + """ + settings.FORUM_PORT = settings.ENV_TOKENS.get("FORUM_PORT", settings.FORUM_PORT) diff --git a/forum/urls.py b/forum/urls.py index 88e71d2d..730b3612 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -1,10 +1,11 @@ """ URLs for forum. """ -from django.urls import re_path # pylint: disable=unused-import -from django.views.generic import TemplateView # pylint: disable=unused-import + +from django.urls import path + +from forum.views import ForumProxyAPIView urlpatterns = [ - # TODO: Fill in URL patterns and views here. - # re_path(r'', TemplateView.as_view(template_name="forum/base.html")), + path("/forum_proxy/", ForumProxyAPIView.as_view(), name="forum_proxy"), ] diff --git a/forum/views.py b/forum/views.py new file mode 100644 index 00000000..bb0453ff --- /dev/null +++ b/forum/views.py @@ -0,0 +1,143 @@ +"""Forum Views.""" + +import logging + +import requests +from django.conf import settings +from rest_framework.permissions import AllowAny +from rest_framework.response import Response +from rest_framework.views import APIView + +logger = logging.getLogger(__name__) + + +class ForumProxyAPIView(APIView): + """ + An Proxy API View to Redirect All API requests to forum/cs_comments_service urls. + """ + + permission_classes = (AllowAny,) + COMMENTS_SERVICE_URL = f"http://forum:{settings.FORUM_PORT}" + + def post(self, request, suffix): + """ + Catches post requests and sends it to forum/cs_comments_service post urls. + """ + request_headers = { + "X-Edx-Api-Key": request.headers.get("X-Edx-Api-Key"), + "Accept-Language": request.headers.get("Accept-Language"), + } + request_data = request.data + url = self.COMMENTS_SERVICE_URL + suffix + + """ + Will be removed once start migrating the endpoints. + login on postman before using this curl request. + sample curl request to /:commentable_id/threads POST: + curl --location 'http://local.edly.io:8000/forum/forum_proxy/api/v1/course123/threads/' \ + --header 'X-Csrftoken: kjGJPW6nPDGpHd23bBtlPhlQYooctsDDuH9SycovPI7vdWODBAstmbT1HaGWgX7Z' \ + --header 'X-Edx-Api-Key: forumapikey' \ + --header 'Accept-Language: en' \ + --header 'Content-Type: application/json' \ + --data '{ + "body": "

test post request 1

", + "anonymous": false, + "anonymous_to_peers": false, + "course_id": "course-v1:Arbisoft+SE002+2024_S2", + "commentable_id": "course", + "thread_type": "discussion", + "title": "Test" + }' + Uncomment below lines of code if want to test above curl request, + when called from edx-platform suffix should have required params + """ + # user_id = request.user.id + # url = url + (f"&user_id={user_id}" if "?" in url else f"?user_id={user_id}") + + logger.info(f"Post Request to cs_comments_service url: {url}") + response = requests.post(url, data=request_data, headers=request_headers) + return Response(data=response.json(), status=response.status_code) + + def get(self, request, suffix): + """ + Catches get requests and sends it to forum/cs_comments_service get urls. + """ + request_headers = { + "X-Edx-Api-Key": request.headers.get("X-Edx-Api-Key"), + "Accept-Language": request.headers.get("Accept-Language"), + } + request_data = request.data + url = self.COMMENTS_SERVICE_URL + suffix + + """ + Will be removed once start migrating the endpoints. + sample curl request to /:commentable_id/threads GET: + curl --location --request GET 'http://local.edly.io:8000/forum/forum_proxy/api/v1/course123/threads/' \ + --header 'X-Edx-Api-Key: forumapikey' \ + --header 'Accept-Language: en' \ + --header 'Content-Type: application/json' \ + --data '{ + "course_id": "course-v1:Arbisoft+SE002+2024_S2", + "commentable_id": "course123" + }' + Uncomment below lines of code if want to test above curl request, + when called from edx-platform suffix should have required params + """ + # course_id = request_data.get("course_id") + # url = url + (f"&course_id={course_id}" if "?" in url else f"?course_id={course_id}") + + logger.info(f"Get Request to cs_comments_service url: {url}") + response = requests.get(url, data=request_data, headers=request_headers) + return Response(data=response.json(), status=response.status_code) + + def delete(self, request, suffix): + """ + Catches delete requests and sends it to forum/cs_comments_service delete urls. + """ + request_headers = { + "X-Edx-Api-Key": request.headers.get("X-Edx-Api-Key"), + "Accept-Language": request.headers.get("Accept-Language"), + } + request_data = request.data + url = self.COMMENTS_SERVICE_URL + suffix + + """ + Will be removed once start migrating the endpoints. + sample curl request to /:commentable_id/threads DELETE: + curl --location --request DELETE 'http://local.edly.io:8000/forum/forum_proxy/api/v1/course123/threads/' \ + --header 'X-Edx-Api-Key: forumapikey' \ + --header 'Accept-Language: en' \ + --data '' + """ + + logger.info(f"Get Request to cs_comments_service url: {url}") + response = requests.delete(url, data=request_data, headers=request_headers) + return Response(data=response.json(), status=response.status_code) + + def put(self, request, suffix): + """ + Catches post requests and sends it to forum/cs_comments_service post urls. + """ + request_headers = { + "X-Edx-Api-Key": request.headers.get("X-Edx-Api-Key"), + "Accept-Language": request.headers.get("Accept-Language"), + } + request_data = request.data + url = self.COMMENTS_SERVICE_URL + suffix + + """ + Will be removed once start migrating the endpoints. + sample curl request to /threads/:thread_id PUT: + curl --location --request PUT + 'http://local.edly.io:8000/forum/forum_proxy/api/v1/threads/66a9d1cca99edf001d4c5f77/' \ + --header 'X-Edx-Api-Key: forumapikey' \ + --header 'Accept-Language: en' \ + --header 'Content-Type: application/json' \ + --data '{ + "body": "

test post request 11

" + }' + """ + + logger.info(f"Put Request to cs_comments_service url: {url}") + response = requests.put(url, data=request_data, headers=request_headers) + return Response(data=response.json(), status=response.status_code) diff --git a/pylintrc b/pylintrc index 7b10d967..7d754f61 100644 --- a/pylintrc +++ b/pylintrc @@ -74,7 +74,6 @@ load-plugins = edx_lint.pylint,pylint_django,pylint_celery [MESSAGES CONTROL] enable = blacklisted-name, - line-too-long, abstract-class-instantiated, abstract-method, @@ -149,7 +148,6 @@ enable = not-context-manager, not-in-loop, pointless-statement, - pointless-string-statement, raising-bad-type, raising-non-exception, redefined-builtin, @@ -266,8 +264,10 @@ disable = fixme, global-statement, invalid-name, + line-too-long, locally-disabled, no-else-return, + pointless-string-statement, suppressed-message, too-few-public-methods, too-many-ancestors, @@ -291,6 +291,8 @@ disable = django-not-configured, consider-using-with, bad-option-value, + missing-timeout, + useless-suppression, [REPORTS] output-format = text diff --git a/requirements/base.in b/requirements/base.in index 9f4002ee..a8b3e0f8 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -4,4 +4,6 @@ Django # Web application framework +djangorestframework openedx-atlas +requests diff --git a/requirements/base.txt b/requirements/base.txt index 5e51daeb..fb91250c 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,14 +7,29 @@ asgiref==3.8.1 # via django backports-zoneinfo==0.2.1 - # via django + # via + # django + # djangorestframework +certifi==2024.7.4 + # via requests +charset-normalizer==3.3.2 + # via requests django==4.2.14 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/base.in + # djangorestframework +djangorestframework==3.15.2 + # via -r requirements/base.in +idna==3.7 + # via requests openedx-atlas==0.6.1 # via -r requirements/base.in +requests==2.32.3 + # via -r requirements/base.in sqlparse==0.5.1 # via django typing-extensions==4.12.2 # via asgiref +urllib3==2.2.2 + # via requests diff --git a/requirements/dev.txt b/requirements/dev.txt index b9184866..51aece2f 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -17,6 +17,7 @@ backports-zoneinfo==0.2.1 # via # -r requirements/quality.txt # django + # djangorestframework build==1.2.1 # via # -r requirements/pip-tools.txt @@ -26,12 +27,20 @@ cachetools==5.4.0 # -r requirements/ci.txt # -r requirements/quality.txt # tox +certifi==2024.7.4 + # via + # -r requirements/quality.txt + # requests chardet==5.2.0 # via # -r requirements/ci.txt # -r requirements/quality.txt # diff-cover # tox +charset-normalizer==3.3.2 + # via + # -r requirements/quality.txt + # requests click==8.1.7 # via # -r requirements/pip-tools.txt @@ -57,7 +66,7 @@ coverage[toml]==7.6.0 # via # -r requirements/quality.txt # pytest-cov -diff-cover==9.1.1 +diff-cover==7.7.0 # via -r requirements/dev.in dill==0.3.8 # via @@ -72,8 +81,11 @@ django==4.2.14 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/quality.txt + # djangorestframework # edx-i18n-tools -edx-i18n-tools==1.6.0 +djangorestframework==3.15.2 + # via -r requirements/quality.txt +edx-i18n-tools==1.6.2 # via -r requirements/dev.in edx-lint==5.3.7 # via -r requirements/quality.txt @@ -87,6 +99,10 @@ filelock==3.15.4 # -r requirements/quality.txt # tox # virtualenv +idna==3.7 + # via + # -r requirements/quality.txt + # requests importlib-metadata==6.11.0 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt @@ -105,12 +121,8 @@ jinja2==3.1.4 # -r requirements/quality.txt # code-annotations # diff-cover -lxml[html-clean,html_clean]==5.2.2 - # via - # edx-i18n-tools - # lxml-html-clean -lxml-html-clean==0.1.1 - # via lxml +lxml==5.2.2 + # via edx-i18n-tools markupsafe==2.1.5 # via # -r requirements/quality.txt @@ -130,7 +142,7 @@ packaging==24.1 # pyproject-api # pytest # tox -path==16.14.0 +path==16.16.0 # via edx-i18n-tools pbr==6.0.0 # via @@ -208,6 +220,8 @@ pyyaml==6.0.1 # -r requirements/quality.txt # code-annotations # edx-i18n-tools +requests==2.32.3 + # via -r requirements/quality.txt six==1.16.0 # via # -r requirements/quality.txt @@ -254,6 +268,10 @@ typing-extensions==4.12.2 # asgiref # astroid # pylint +urllib3==2.2.2 + # via + # -r requirements/quality.txt + # requests virtualenv==20.26.3 # via # -r requirements/ci.txt diff --git a/requirements/doc.txt b/requirements/doc.txt index f96eaa2c..bb5d5c91 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -22,6 +22,7 @@ backports-zoneinfo==0.2.1 # via # -r requirements/test.txt # django + # djangorestframework beautifulsoup4==4.12.3 # via pydata-sphinx-theme build==1.2.1 @@ -31,13 +32,17 @@ cachetools==5.4.0 # -r requirements/test.txt # tox certifi==2024.7.4 - # via requests + # via + # -r requirements/test.txt + # requests chardet==5.2.0 # via # -r requirements/test.txt # tox charset-normalizer==3.3.2 - # via requests + # via + # -r requirements/test.txt + # requests click==8.1.7 # via # -r requirements/test.txt @@ -60,6 +65,9 @@ django==4.2.14 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/test.txt + # djangorestframework +djangorestframework==3.15.2 + # via -r requirements/test.txt doc8==1.1.1 # via -r requirements/doc.in docutils==0.19 @@ -79,7 +87,9 @@ filelock==3.15.4 # tox # virtualenv idna==3.7 - # via requests + # via + # -r requirements/test.txt + # requests imagesize==1.4.1 # via sphinx importlib-metadata==6.11.0 @@ -188,6 +198,7 @@ readme-renderer==43.0 # via twine requests==2.32.3 # via + # -r requirements/test.txt # requests-toolbelt # sphinx # twine @@ -256,6 +267,7 @@ typing-extensions==4.12.2 # rich urllib3==2.2.2 # via + # -r requirements/test.txt # requests # twine virtualenv==20.26.3 diff --git a/requirements/pip.txt b/requirements/pip.txt index ebe14bbe..7b187b8d 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -8,7 +8,7 @@ wheel==0.43.0 # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: -pip==24.1.2 +pip==24.2 # via -r requirements/pip.in -setuptools==71.1.0 +setuptools==72.1.0 # via -r requirements/pip.in diff --git a/requirements/quality.txt b/requirements/quality.txt index a0cae47a..8260aa51 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -16,14 +16,23 @@ backports-zoneinfo==0.2.1 # via # -r requirements/test.txt # django + # djangorestframework cachetools==5.4.0 # via # -r requirements/test.txt # tox +certifi==2024.7.4 + # via + # -r requirements/test.txt + # requests chardet==5.2.0 # via # -r requirements/test.txt # tox +charset-normalizer==3.3.2 + # via + # -r requirements/test.txt + # requests click==8.1.7 # via # -r requirements/test.txt @@ -54,6 +63,9 @@ django==4.2.14 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/test.txt + # djangorestframework +djangorestframework==3.15.2 + # via -r requirements/test.txt edx-lint==5.3.7 # via -r requirements/quality.in exceptiongroup==1.2.2 @@ -65,6 +77,10 @@ filelock==3.15.4 # -r requirements/test.txt # tox # virtualenv +idna==3.7 + # via + # -r requirements/test.txt + # requests iniconfig==2.0.0 # via # -r requirements/test.txt @@ -145,6 +161,8 @@ pyyaml==6.0.1 # via # -r requirements/test.txt # code-annotations +requests==2.32.3 + # via -r requirements/test.txt six==1.16.0 # via edx-lint snowballstemmer==2.2.0 @@ -179,6 +197,10 @@ typing-extensions==4.12.2 # asgiref # astroid # pylint +urllib3==2.2.2 + # via + # -r requirements/test.txt + # requests virtualenv==20.26.3 # via # -r requirements/test.txt diff --git a/requirements/test.txt b/requirements/test.txt index bc401d88..4e0414cf 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -12,10 +12,19 @@ backports-zoneinfo==0.2.1 # via # -r requirements/base.txt # django + # djangorestframework cachetools==5.4.0 # via tox +certifi==2024.7.4 + # via + # -r requirements/base.txt + # requests chardet==5.2.0 # via tox +charset-normalizer==3.3.2 + # via + # -r requirements/base.txt + # requests click==8.1.7 # via code-annotations code-annotations==1.8.0 @@ -29,12 +38,19 @@ distlib==0.3.8 # via # -c https://raw.githubusercontent.com/edx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/base.txt + # djangorestframework +djangorestframework==3.15.2 + # via -r requirements/base.txt exceptiongroup==1.2.2 # via pytest filelock==3.15.4 # via # tox # virtualenv +idna==3.7 + # via + # -r requirements/base.txt + # requests iniconfig==2.0.0 # via pytest jinja2==3.1.4 @@ -72,6 +88,8 @@ python-slugify==8.0.4 # via code-annotations pyyaml==6.0.1 # via code-annotations +requests==2.32.3 + # via -r requirements/base.txt sqlparse==0.5.1 # via # -r requirements/base.txt @@ -92,5 +110,9 @@ typing-extensions==4.12.2 # via # -r requirements/base.txt # asgiref +urllib3==2.2.2 + # via + # -r requirements/base.txt + # requests virtualenv==20.26.3 # via tox diff --git a/setup.py b/setup.py index a6c3b9eb..9420fe9b 100755 --- a/setup.py +++ b/setup.py @@ -157,4 +157,9 @@ def is_requirement(line): 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.8', ], + entry_points={ + 'lms.djangoapp': [ + 'forum = forum.apps:ForumConfig', + ], + }, ) diff --git a/test_settings.py b/test_settings.py index f4b86952..ad6b6aa5 100644 --- a/test_settings.py +++ b/test_settings.py @@ -59,3 +59,5 @@ def root(*args): ], }, }] + +FORUM_PORT = "4567" diff --git a/tox.ini b/tox.ini index 4ddee513..ea41bd8f 100644 --- a/tox.ini +++ b/tox.ini @@ -36,8 +36,7 @@ norecursedirs = .* docs requirements site-packages [testenv] deps = - django32: Django>=3.2,<4.0 - django40: Django>=4.0,<4.1 + django42: Django>=4.2,<4.3 -r{toxinidir}/requirements/test.txt commands = python manage.py check