From 6a164fa35a2c4ae8faad5eddaf321b662e224bb5 Mon Sep 17 00:00:00 2001 From: John Nagro Date: Tue, 24 Oct 2023 20:05:49 +0000 Subject: [PATCH] feat: add lms_user_email and content_tile to Transactions --- CHANGELOG.rst | 4 + Dockerfile | 98 +++++++++++++++++++ Makefile | 5 + docker-compose.yml | 25 +++++ openedx_ledger/__init__.py | 2 +- openedx_ledger/api.py | 10 ++ ...009_add_email_and_title_to_transactions.py | 33 +++++++ openedx_ledger/models.py | 20 ++++ openedx_ledger/test_utils/factories.py | 3 + tests/test_models.py | 2 + 10 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 openedx_ledger/migrations/0009_add_email_and_title_to_transactions.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 57c9eda..cd61322 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,10 @@ Unreleased ********** * Nothing unreleased +[1.3.0] +******* +* Add optional ``lms_user_email`` and ``content_title`` to the ``Transaction`` model + [1.2.0] ******* * Add an ``Adjustment`` model diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ea6c1bc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,98 @@ +# Docker in this repo is only supported for running tests locally +# as an alternative to virtualenv natively +FROM ubuntu:focal as app +MAINTAINER sre@edx.org + + +# Packages installed: +# git; Used to pull in particular requirements from github rather than pypi, +# and to check the sha of the code checkout. + +# build-essentials; so we can use make with the docker container + +# language-pack-en locales; ubuntu locale support so that system utilities have a consistent +# language and time zone. + +# python; ubuntu doesnt ship with python, so this is the python we will use to run the application + +# python3-pip; install pip to install application requirements.txt files + +# pkg-config +# mysqlclient>=2.2.0 requires this (https://github.com/PyMySQL/mysqlclient/issues/620) + +# libmysqlclient-dev; to install header files needed to use native C implementation for +# MySQL-python for performance gains. + +# libssl-dev; # mysqlclient wont install without this. + +# python3-dev; to install header files for python extensions; much wheel-building depends on this + +# gcc; for compiling python extensions distributed with python packages like mysql-client + +# If you add a package here please include a comment above describing what it is used for +RUN apt-get update && apt-get -qy install --no-install-recommends \ + language-pack-en \ + locales \ + python3.8 \ + python3-pip \ + python3.8-venv \ + pkg-config \ + libmysqlclient-dev \ + libssl-dev \ + python3-dev \ + gcc \ + build-essential \ + git \ + curl + + +RUN pip install --upgrade pip setuptools +# delete apt package lists because we do not need them inflating our image +RUN rm -rf /var/lib/apt/lists/* + +RUN ln -s /usr/bin/python3 /usr/bin/python + +RUN locale-gen en_US.UTF-8 +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 +ENV DJANGO_SETTINGS_MODULE enterprise.settings.test + +# Env vars: path +ENV VIRTUAL_ENV='/edx/app/venvs/openedx-ledger' +ENV PATH="$VIRTUAL_ENV/bin:$PATH" +ENV PATH="/edx/app/openedx-ledger/node_modules/.bin:${PATH}" +ENV PATH="/edx/app/openedx-ledger/bin:${PATH}" +ENV PATH="/edx/app/nodeenv/bin:${PATH}" + +RUN useradd -m --shell /bin/false app + +WORKDIR /edx/app/openedx-ledger + +RUN python3.8 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" + +# Copy the requirements explicitly even though we copy everything below +# this prevents the image cache from busting unless the dependencies have changed. +COPY requirements/ /edx/app/openedx-ledger/requirements/ + +# Dependencies are installed as root so they cannot be modified by the application user. +RUN pip install -r requirements/dev.txt +RUN pip install nodeenv + +# Set up a Node environment and install Node requirements. +# Must be done after Python requirements, since nodeenv is installed +# via pip. +# The node environment is already 'activated' because its .../bin was put on $PATH. +RUN nodeenv /edx/app/nodeenv --node=18.15.0 --prebuilt + +RUN mkdir -p /edx/var/log + +# Code is owned by root so it cannot be modified by the application user. +# So we copy it before changing users. +USER app + +# This line is after the requirements so that changes to the code will not +# bust the image cache +COPY . /edx/app/openedx-ledger + diff --git a/Makefile b/Makefile index 1198ea2..373f063 100644 --- a/Makefile +++ b/Makefile @@ -87,6 +87,11 @@ style: lint: pylint openedx_ledger tests manage.py setup.py +## Docker in this repo is only supported for running tests locally +## as an alternative to virtualenv natively +test-shell: ## Run a shell, as root, on the specified service container + docker-compose run -u 0 test-shell env TERM=$(TERM) /bin/bash + ## Localization targets extract_translations: ## extract strings to be translated, outputting .mo files diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d411fae --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +# Docker in this repo is only supported for running tests locally +# as an alternative to virtualenv natively - johnnagro 2022-02-11 +version: "2.1" +services: + test-shell: + build: + context: . + dockerfile: Dockerfile + container_name: openedx-ledger.test.app + hostname: app.test.openedx-ledger + volumes: + - .:/edx/app/openedx-ledger + + networks: + - devstack_default + # Allows attachment to this container using 'docker attach '. + stdin_open: true + tty: true + environment: + DJANGO_SETTINGS_MODULE: test_settings + +networks: + devstack_default: + external: true + diff --git a/openedx_ledger/__init__.py b/openedx_ledger/__init__.py index 912eef9..48c601f 100644 --- a/openedx_ledger/__init__.py +++ b/openedx_ledger/__init__.py @@ -1,4 +1,4 @@ """ A library that records transactions against a ledger, denominated in units of value. """ -__version__ = "1.2.0" +__version__ = "1.3.0" diff --git a/openedx_ledger/api.py b/openedx_ledger/api.py index 8f1bd1f..51e32ad 100644 --- a/openedx_ledger/api.py +++ b/openedx_ledger/api.py @@ -42,7 +42,9 @@ def create_transaction( quantity, idempotency_key, lms_user_id=None, + lms_user_email=None, content_key=None, + content_tile=None, subsidy_access_policy_uuid=None, state=models.TransactionStateChoices.CREATED, **metadata @@ -57,9 +59,15 @@ def create_transaction( lms_user_id (int, Optional): The lms_user_id representing the learner who is enrolling. Skip if this does not represent a policy enrolling a learner into content. + lms_user_email (str, Optional): + The lms_user_email representing the learner who is enrolling. Skip if this does not represent a policy + enrolling a learner into content or if the email is not readily available. content_key (str, Optional): The identifier of the content into which the learner is enrolling. Skip if this does not represent a policy enrolling a learner into content. + content_title (str, Optional): + The title of the content into which the learner is enrolling. Skip if this does not represent a policy + enrolling a learner into content or if the title is not readily available. subsidy_access_policy_uuid (str, Optional): The policy which permitted the creation of the new Transaction. Skip if this does not represent a policy enrolling a learner into content. @@ -88,7 +96,9 @@ def create_transaction( defaults={ "quantity": quantity, "content_key": content_key, + "content_title": content_tile, "lms_user_id": lms_user_id, + "lms_user_email": lms_user_email, "subsidy_access_policy_uuid": subsidy_access_policy_uuid, "state": state, "metadata": metadata, diff --git a/openedx_ledger/migrations/0009_add_email_and_title_to_transactions.py b/openedx_ledger/migrations/0009_add_email_and_title_to_transactions.py new file mode 100644 index 0000000..575409d --- /dev/null +++ b/openedx_ledger/migrations/0009_add_email_and_title_to_transactions.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.18 on 2023-10-24 19:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('openedx_ledger', '0008_adjustment_model'), + ] + + operations = [ + migrations.AddField( + model_name='historicaltransaction', + name='content_title', + field=models.CharField(blank=True, db_index=True, help_text='The title of the content associated with this Transaction.The title is captured at the time the Transaction is created and may not be up to date.', max_length=255, null=True), + ), + migrations.AddField( + model_name='historicaltransaction', + name='lms_user_email', + field=models.CharField(blank=True, db_index=True, help_text='The email of the Open edX LMS user record with which this Transaction is associated.The email is captured at the time the Transaction is created and may not be up to date.', max_length=255, null=True), + ), + migrations.AddField( + model_name='transaction', + name='content_title', + field=models.CharField(blank=True, db_index=True, help_text='The title of the content associated with this Transaction.The title is captured at the time the Transaction is created and may not be up to date.', max_length=255, null=True), + ), + migrations.AddField( + model_name='transaction', + name='lms_user_email', + field=models.CharField(blank=True, db_index=True, help_text='The email of the Open edX LMS user record with which this Transaction is associated.The email is captured at the time the Transaction is created and may not be up to date.', max_length=255, null=True), + ), + ] diff --git a/openedx_ledger/models.py b/openedx_ledger/models.py index b515fdd..232aded 100644 --- a/openedx_ledger/models.py +++ b/openedx_ledger/models.py @@ -344,6 +344,16 @@ class Meta: "The id of the Open edX LMS user record with which this Transaction is associated." ), ) + lms_user_email = models.CharField( + max_length=255, + blank=True, + null=True, + db_index=True, + help_text=( + "The email of the Open edX LMS user record with which this Transaction is associated." + "The email is captured at the time the Transaction is created and may not be up to date." + ) + ) content_key = models.CharField( max_length=255, blank=True, @@ -353,6 +363,16 @@ class Meta: "The globally unique content identifier. Joinable with ContentMetadata.content_key in enterprise-catalog." ) ) + content_title = models.CharField( + max_length=255, + blank=True, + null=True, + db_index=True, + help_text=( + "The title of the content associated with this Transaction." + "The title is captured at the time the Transaction is created and may not be up to date." + ) + ) fulfillment_identifier = models.CharField( max_length=255, blank=True, diff --git a/openedx_ledger/test_utils/factories.py b/openedx_ledger/test_utils/factories.py index 564e2d7..713cd5e 100644 --- a/openedx_ledger/test_utils/factories.py +++ b/openedx_ledger/test_utils/factories.py @@ -44,6 +44,9 @@ class Meta: quantity = factory.Faker("random_int", min=-100000, max=-100) ledger = factory.Iterator(Ledger.objects.all()) lms_user_id = factory.Faker("random_int", min=1, max=1000) + lms_user_email = factory.Faker("email") + content_key = factory.Faker("lexify", text="???+?????101") + content_title = factory.Faker("lexify", text="???: ?????? ???") class ExternalFulfillmentProviderFactory(factory.django.DjangoModelFactory): diff --git a/tests/test_models.py b/tests/test_models.py index f86cac2..9c62377 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -41,7 +41,9 @@ def setUp(self): self.transaction_5 = TransactionFactory( ledger=self.ledger, lms_user_id=3, + lms_user_email='user@example.com', content_key="course-v1:edX+test+course.3", + content_title="Edx: test course 3", quantity=-10, state=models.TransactionStateChoices.PENDING, )