From 4b5b6ce54705f1628af9a084fa168dd16314c5a3 Mon Sep 17 00:00:00 2001 From: CP Date: Wed, 19 Feb 2025 13:23:52 -0500 Subject: [PATCH 1/7] hold --- courses/views/v1/__init__.py | 12 +- courses/views/v2/__init__.py | 9 +- main/settings.py | 9 + openapi/__init__.py | 0 openapi/apps.py | 10 + openapi/exceptions.py | 5 + openapi/hooks.py | 72 + openapi/management/__init__.py | 0 openapi/management/commands/__init__.py | 0 .../commands/generate_openapi_spec.py | 50 + openapi/settings_spectacular.py | 19 + openapi/specs/v1.yaml | 8280 +++++++++++++++++ openapi/urls.py | 26 + poetry.lock | 202 +- pyproject.toml | 1 + schema.yml | 0 scripts/generate_openapi.sh | 27 + .../templates/modelEnum.mustache | 25 + .../openapi-configs/typescript-axios-v0.yaml | 9 + .../0025_alter_userprofile_company_size.py | 18 + ...0026_alter_userprofile_years_experience.py | 18 + users/models.py | 50 +- 22 files changed, 8831 insertions(+), 11 deletions(-) create mode 100644 openapi/__init__.py create mode 100644 openapi/apps.py create mode 100644 openapi/exceptions.py create mode 100644 openapi/hooks.py create mode 100644 openapi/management/__init__.py create mode 100644 openapi/management/commands/__init__.py create mode 100644 openapi/management/commands/generate_openapi_spec.py create mode 100644 openapi/settings_spectacular.py create mode 100644 openapi/specs/v1.yaml create mode 100644 openapi/urls.py create mode 100644 schema.yml create mode 100755 scripts/generate_openapi.sh create mode 100644 scripts/openapi-configs/templates/modelEnum.mustache create mode 100644 scripts/openapi-configs/typescript-axios-v0.yaml create mode 100644 users/migrations/0025_alter_userprofile_company_size.py create mode 100644 users/migrations/0026_alter_userprofile_years_experience.py diff --git a/courses/views/v1/__init__.py b/courses/views/v1/__init__.py index 6d8965cc03..a4ef164763 100644 --- a/courses/views/v1/__init__.py +++ b/courses/views/v1/__init__.py @@ -2,6 +2,7 @@ import logging from typing import Optional, Tuple, Union # noqa: UP035 +from drf_spectacular.utils import extend_schema import django_filters from django.contrib.auth.models import User @@ -165,8 +166,9 @@ def get_queryset(self): def get_serializer_context(self): added_context = {} - if self.request.query_params.get("readable_id", None): - added_context["all_runs"] = True + if self.request and self.request.query_params: + if self.request.query_params.get("readable_id", None): + added_context["all_runs"] = True return {**super().get_serializer_context(), **added_context} @@ -210,7 +212,7 @@ def get_queryset(self): def get_serializer_context(self): added_context = {} - if self.request.query_params.get("relevant_to", None): + if self.request and self.request.query_params and self.request.query_params.get("relevant_to", None): added_context["include_enrolled_flag"] = True return {**super().get_serializer_context(), **added_context} @@ -335,6 +337,10 @@ def respond(data, status=True): # noqa: FBT002 return resp +@extend_schema( + request=CourseRunEnrollmentSerializer, + responses={200: CourseRunEnrollmentSerializer}, +) class UserEnrollmentsApiViewSet( mixins.CreateModelMixin, mixins.ListModelMixin, diff --git a/courses/views/v2/__init__.py b/courses/views/v2/__init__.py index ae089acc64..3b19d33946 100644 --- a/courses/views/v2/__init__.py +++ b/courses/views/v2/__init__.py @@ -96,10 +96,11 @@ def get_queryset(self): def get_serializer_context(self): added_context = {} - if self.request.query_params.get("readable_id", None): - added_context["all_runs"] = True - if self.request.query_params.get("include_approved_financial_aid", None): - added_context["include_approved_financial_aid"] = True + if self.request and self.request.query_params: + if self.request.query_params.get("readable_id", None): + added_context["all_runs"] = True + if self.request.query_params.get("include_approved_financial_aid", None): + added_context["include_approved_financial_aid"] = True return {**super().get_serializer_context(), **added_context} diff --git a/main/settings.py b/main/settings.py index 1f20a56dc9..f621d1b454 100644 --- a/main/settings.py +++ b/main/settings.py @@ -25,6 +25,7 @@ from mitol.google_sheets_deferrals.settings.google_sheets_deferrals import * # noqa: F403 from mitol.google_sheets_refunds.settings.google_sheets_refunds import * # noqa: F403 from redbeat import RedBeatScheduler +from openapi.settings_spectacular import open_spectacular_settings from main.celery_utils import OffsettingSchedule from main.sentry import init_sentry @@ -214,6 +215,8 @@ "mitol.olposthog.apps.OlPosthog", # "mitol.oauth_toolkit_extensions.apps.OAuthToolkitExtensionsApp", "viewflow", + "openapi", + "drf_spectacular", ) # Only include the seed data app if this isn't running in prod # if ENVIRONMENT not in ("production", "prod"): @@ -947,6 +950,10 @@ "EXCEPTION_HANDLER": "main.exceptions.exception_handler", "TEST_REQUEST_DEFAULT_FORMAT": "json", "DEFAULT_VERSIONING": "rest_framework.versioning.NamespaceVersioning", + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", + "ALLOWED_VERSIONS": [ + "v1" + ], } # Relative URL to be used by Djoser for the link in the password reset email @@ -1207,3 +1214,5 @@ default="http://ue.odl.local:9080/", description="The base URL for Unified Ecommerce.", ) + +SPECTACULAR_SETTINGS = open_spectacular_settings diff --git a/openapi/__init__.py b/openapi/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openapi/apps.py b/openapi/apps.py new file mode 100644 index 0000000000..21d4955ea2 --- /dev/null +++ b/openapi/apps.py @@ -0,0 +1,10 @@ +"""Config for the OpenAPI app.""" + +from django.apps import AppConfig + + +class OpenapiConfig(AppConfig): + """Config for the OpenAPI app.""" + + default_auto_field = "django.db.models.BigAutoField" + name = "openapi" diff --git a/openapi/exceptions.py b/openapi/exceptions.py new file mode 100644 index 0000000000..de890d7199 --- /dev/null +++ b/openapi/exceptions.py @@ -0,0 +1,5 @@ +"""OpenAPI exception""" + + +class EnumDescriptionError(Exception): + """Failed to compute a description""" diff --git a/openapi/hooks.py b/openapi/hooks.py new file mode 100644 index 0000000000..29d8105099 --- /dev/null +++ b/openapi/hooks.py @@ -0,0 +1,72 @@ +"""Extensions for OpenAPI schema""" + +import re + +from openapi.exceptions import EnumDescriptionError + +ENUM_DESCRIPTION_RE = re.compile(r"\w*\*\s`(?P.*)`\s\-\s(?P.*)") + + +def _iter_described_enums(schema, *, name=None, is_root=True): + """ + Create an iterator over all enums with descriptions + """ + if is_root: + for item_name, item in schema.items(): + yield from _iter_described_enums(item, name=item_name, is_root=False) + elif isinstance(schema, list): + for item in schema: + yield from _iter_described_enums(item, name=name, is_root=is_root) + elif isinstance(schema, dict): + if "enum" in schema and "description" in schema: + yield name, schema + + yield from _iter_described_enums( + schema.get("properties", []), name=name, is_root=is_root + ) + yield from _iter_described_enums( + schema.get("oneOf", []), name=name, is_root=is_root + ) + yield from _iter_described_enums( + schema.get("allOf", []), name=name, is_root=is_root + ) + yield from _iter_described_enums( + schema.get("anyOf", []), name=name, is_root=is_root + ) + + +def postprocess_x_enum_descriptions(result, generator, request, public): # noqa: ARG001 + """ + Take the drf-spectacular generated descriptions and + puts it into the x-enum-descriptions property. + """ + + # your modifications to the schema in parameter result + schemas = result.get("components", {}).get("schemas", {}) + + for name, schema in _iter_described_enums(schemas): + lines = schema["description"].splitlines() + descriptions_by_value = {} + for line in lines: + match = ENUM_DESCRIPTION_RE.match(line) + if match is None: + continue + + key = match["key"] + description = match["description"] + + # sometimes there are descriptions for empty values + # that aren't present in `"enums"` + if key in schema["enum"]: + descriptions_by_value[key] = description + + if len(descriptions_by_value.values()) != len(schema["enum"]): + msg = f"Unable to find descriptions for all enum values: {name}" + raise EnumDescriptionError(msg) + + if descriptions_by_value: + schema["x-enum-descriptions"] = [ + descriptions_by_value[value] for value in schema["enum"] + ] + + return result diff --git a/openapi/management/__init__.py b/openapi/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openapi/management/commands/__init__.py b/openapi/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openapi/management/commands/generate_openapi_spec.py b/openapi/management/commands/generate_openapi_spec.py new file mode 100644 index 0000000000..deeee78931 --- /dev/null +++ b/openapi/management/commands/generate_openapi_spec.py @@ -0,0 +1,50 @@ +""" +Management command to generate OpenAPI specs from our APIs. +""" + +from pathlib import Path + +from django.conf import settings +from django.core import management +from django.core.management import BaseCommand + + +class Command(BaseCommand): + """Generate OpenAPI specs for our APIs.""" + + help = "Generate OpenAPI specs for our APIs." + + def add_arguments(self, parser): + """Add arguments to the command""" + + parser.add_argument( + "--directory", + dest="directory", + default="openapi/specs/", + help="Directory into which output is written", + ) + parser.add_argument( + "--fail-on-warn", + dest="fail-on-warn", + action="store_true", + default=False, + help="Fail the command if there are any warnings", + ) + + super().add_arguments(parser) + + def handle(self, **options): + """Run the command""" + + directory = options["directory"] + for version in settings.REST_FRAMEWORK["ALLOWED_VERSIONS"]: + filename = version + ".yaml" + filepath = Path(directory) / filename + management.call_command( + "spectacular", + urlconf="main.urls", + file=filepath, + validate=True, + api_version=version, + fail_on_warn=options["fail-on-warn"], + ) diff --git a/openapi/settings_spectacular.py b/openapi/settings_spectacular.py new file mode 100644 index 0000000000..04a25d822e --- /dev/null +++ b/openapi/settings_spectacular.py @@ -0,0 +1,19 @@ +""" +Django settings specific to DRF Spectacular +""" + +open_spectacular_settings = { + "TITLE": "MITx Online API", + "DESCRIPTION": "MIT public API", + "VERSION": "0.0.1", + "SERVE_INCLUDE_SCHEMA": False, + "SERVE_URLCONF": "main.urls", + "ENUM_GENERATE_CHOICE_DESCRIPTION": True, + "COMPONENT_SPLIT_REQUEST": True, + "AUTHENTICATION_WHITELIST": [], + "SCHEMA_PATH_PREFIX": "/api/v[0-9]", + "POSTPROCESSING_HOOKS": [ + "drf_spectacular.hooks.postprocess_schema_enums", + "openapi.hooks.postprocess_x_enum_descriptions", + ], +} diff --git a/openapi/specs/v1.yaml b/openapi/specs/v1.yaml new file mode 100644 index 0000000000..7d32651131 --- /dev/null +++ b/openapi/specs/v1.yaml @@ -0,0 +1,8280 @@ +openapi: 3.0.3 +info: + title: MITx Online API + version: 0.0.1 (v1) + description: MIT public API +paths: + /.well-known/openid-configuration: + get: + operationId: .well_known_openid_configuration_retrieve + description: View for openid configuration + tags: + - .well-known + responses: + '200': + description: No response body + /api/auths/: + get: + operationId: api_auths_retrieve + description: View that returns a serialized list of the logged-in user's UserSocialAuth + types + tags: + - api + responses: + '200': + description: No response body + /api/baskets/: + get: + operationId: api_baskets_list + description: API view set for Basket + tags: + - api + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Basket' + description: '' + /api/baskets/{parent_lookup_basket}/discounts/: + get: + operationId: api_baskets_discounts_list + description: Applied basket discounts + parameters: + - in: path + name: parent_lookup_basket + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/BasketDiscount' + description: '' + /api/baskets/{parent_lookup_basket}/discounts/{id}/: + get: + operationId: api_baskets_discounts_retrieve + description: Applied basket discounts + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: path + name: parent_lookup_basket + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BasketDiscount' + description: '' + /api/baskets/{parent_lookup_basket}/items/: + get: + operationId: api_baskets_items_list + description: API view set for BasketItem + parameters: + - in: path + name: parent_lookup_basket + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/BasketItem' + description: '' + post: + operationId: api_baskets_items_create + description: API view set for BasketItem + parameters: + - in: path + name: parent_lookup_basket + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BasketItemRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BasketItemRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BasketItemRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/BasketItem' + description: '' + /api/baskets/{parent_lookup_basket}/items/{id}/: + delete: + operationId: api_baskets_items_destroy + description: API view set for BasketItem + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: path + name: parent_lookup_basket + schema: + type: string + required: true + tags: + - api + responses: + '204': + description: No response body + /api/baskets/{username}/: + get: + operationId: api_baskets_retrieve + description: API view set for Basket + parameters: + - in: path + name: username + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Basket' + description: '' + /api/change-emails/: + post: + operationId: api_change_emails_create + description: Viewset for creating and updating email change requests + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeEmailRequestCreateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ChangeEmailRequestCreateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ChangeEmailRequestCreateRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeEmailRequestCreate' + description: '' + /api/change-emails/{code}/: + put: + operationId: api_change_emails_update + description: Viewset for creating and updating email change requests + parameters: + - in: path + name: code + schema: + type: string + required: true + tags: + - api + responses: + '200': + description: No response body + patch: + operationId: api_change_emails_partial_update + description: Viewset for creating and updating email change requests + parameters: + - in: path + name: code + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedChangeEmailRequestUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedChangeEmailRequestUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedChangeEmailRequestUpdateRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeEmailRequestUpdate' + description: '' + /api/checkout/add_to_cart/: + post: + operationId: api_checkout_add_to_cart_create + description: Add product to the cart + tags: + - api + responses: + '200': + description: No response body + /api/checkout/cart/: + get: + operationId: api_checkout_cart_retrieve + description: Returns the current cart, with the product info embedded. + tags: + - api + responses: + '200': + description: No response body + /api/checkout/redeem_discount/: + post: + operationId: api_checkout_redeem_discount_create + description: |- + API call to redeem a discount. Discounts are attached to the basket so + they can be attached at any time in the checkout process. Later on, + they'll be converted to attach to the resulting Order (as Baskets are + ephemeral). + + Discount application is subject to these rules: + - The discount can't be flagged for use with flexible pricing. + - If the discount is tied to a product, the product must already be in + the basket. + + POST Args: + - discount (str): Discount Code to apply + + Returns: + - Success message on success + - HTTP 406 if there's no basket yet + - HTTP 404 if the discount isn't found + tags: + - api + responses: + '200': + description: No response body + /api/checkout/result/: + post: + operationId: api_checkout_result_create + description: |- + This endpoint is called by Cybersource as a server-to-server call + in order to respond with the payment details. + + Returns: + - HTTP_200_OK if the Order is found. + + Raises: + - Http404 if the Order is not found. + tags: + - api + responses: + '200': + description: No response body + /api/checkout/start_checkout/: + post: + operationId: api_checkout_start_checkout_create + description: |- + API call to start the checkout process. This assembles the basket items + into an Order with Lines for each item, applies the attached basket + discounts, and then calls the payment gateway to prepare for payment. + + Returns: + - JSON payload from the ol-django payment gateway app. The payment + gateway returns data necessary to construct a form that will + ultimately POST to the actual payment processor. + tags: + - api + responses: + '200': + description: No response body + /api/countries/: + get: + operationId: api_countries_retrieve + description: Get generator for countries/states list + tags: + - api + responses: + '200': + description: No response body + /api/course_runs/: + get: + operationId: api_course_runs_list + description: API view set for CourseRuns + parameters: + - in: query + name: id + schema: + type: integer + - in: query + name: live + schema: + type: boolean + tags: + - api + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CourseRunWithCourse' + description: '' + /api/course_runs/{id}/: + get: + operationId: api_course_runs_retrieve + description: API view set for CourseRuns + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this course run. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunWithCourse' + description: '' + /api/courses/: + get: + operationId: api_courses_list + description: API view set for Courses + parameters: + - in: query + name: courserun_is_enrollable + schema: + type: boolean + - in: query + name: id + schema: + type: integer + - in: query + name: live + schema: + type: boolean + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: page__live + schema: + type: boolean + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: readable_id + schema: + type: string + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedCourseWithCourseRunsList' + description: '' + /api/courses/{id}/: + get: + operationId: api_courses_retrieve + description: API view set for Courses + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this course. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseWithCourseRuns' + description: '' + /api/departments/: + get: + operationId: api_departments_list + description: API view set for Departments + tags: + - api + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DepartmentWithCount' + description: '' + /api/departments/{id}/: + get: + operationId: api_departments_retrieve + description: API view set for Departments + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this department. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DepartmentWithCount' + description: '' + /api/discounts/: + get: + operationId: api_discounts_list + description: API view set for Discounts + parameters: + - in: query + name: is_redeemed + schema: + type: string + enum: + - 'no' + - 'yes' + description: |- + * `yes` - yes + * `no` - no + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - in: query + name: payment_type + schema: + type: string + nullable: true + enum: + - customer-support + - financial-assistance + - legacy + - marketing + - sales + - staff + description: |- + * `marketing` - marketing + * `sales` - sales + * `financial-assistance` - financial-assistance + * `customer-support` - customer-support + * `staff` - staff + * `legacy` - legacy + - in: query + name: q + schema: + type: string + description: q + - in: query + name: redemption_type + schema: + type: string + enum: + - one-time + - one-time-per-user + - unlimited + description: |- + * `one-time` - one-time + * `one-time-per-user` - one-time-per-user + * `unlimited` - unlimited + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDiscountList' + description: '' + post: + operationId: api_discounts_create + description: API view set for Discounts + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DiscountRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Discount' + description: '' + /api/discounts/{parent_lookup_discount}/assignees/: + get: + operationId: api_discounts_assignees_list + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedUserDiscountMetaList' + description: '' + post: + operationId: api_discounts_assignees_create + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountMeta' + description: '' + /api/discounts/{parent_lookup_discount}/assignees/{id}/: + get: + operationId: api_discounts_assignees_retrieve + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountMeta' + description: '' + put: + operationId: api_discounts_assignees_update + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountMeta' + description: '' + patch: + operationId: api_discounts_assignees_partial_update + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountMeta' + description: '' + delete: + operationId: api_discounts_assignees_destroy + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '204': + description: No response body + /api/discounts/{parent_lookup_discount}/products/: + get: + operationId: api_discounts_products_list + description: API view set for Discounts + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDiscountProductList' + description: '' + post: + operationId: api_discounts_products_create + description: API view set for Discounts + parameters: + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProduct' + description: '' + /api/discounts/{parent_lookup_discount}/products/{id}/: + get: + operationId: api_discounts_products_retrieve + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount product. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProduct' + description: '' + put: + operationId: api_discounts_products_update + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount product. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProduct' + description: '' + patch: + operationId: api_discounts_products_partial_update + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount product. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedDiscountProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedDiscountProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedDiscountProductRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProduct' + description: '' + delete: + operationId: api_discounts_products_destroy + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount product. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '204': + description: No response body + /api/discounts/{parent_lookup_discount}/tiers/: + get: + operationId: api_discounts_tiers_list + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedFlexiblePriceTierList' + description: '' + post: + operationId: api_discounts_tiers_create + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTier' + description: '' + /api/discounts/{parent_lookup_discount}/tiers/{id}/: + get: + operationId: api_discounts_tiers_retrieve + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price tier. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTier' + description: '' + put: + operationId: api_discounts_tiers_update + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price tier. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTier' + description: '' + patch: + operationId: api_discounts_tiers_partial_update + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price tier. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceTierRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceTierRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceTierRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTier' + description: '' + delete: + operationId: api_discounts_tiers_destroy + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price tier. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - api + responses: + '204': + description: No response body + /api/discounts/{parent_lookup_redeemed_discount}/redemptions/: + get: + operationId: api_discounts_redemptions_list + description: API view set for Discount Redemptions + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDiscountRedemptionList' + description: '' + post: + operationId: api_discounts_redemptions_create + description: API view set for Discount Redemptions + parameters: + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - api + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRedemption' + description: '' + /api/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/: + get: + operationId: api_discounts_redemptions_retrieve + description: API view set for Discount Redemptions + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount redemption. + required: true + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRedemption' + description: '' + put: + operationId: api_discounts_redemptions_update + description: API view set for Discount Redemptions + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount redemption. + required: true + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRedemption' + description: '' + patch: + operationId: api_discounts_redemptions_partial_update + description: API view set for Discount Redemptions + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount redemption. + required: true + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRedemption' + description: '' + delete: + operationId: api_discounts_redemptions_destroy + description: API view set for Discount Redemptions + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount redemption. + required: true + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - api + responses: + '204': + description: No response body + /api/discounts/{id}/: + get: + operationId: api_discounts_retrieve + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Discount' + description: '' + put: + operationId: api_discounts_update + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DiscountRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Discount' + description: '' + patch: + operationId: api_discounts_partial_update + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedDiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedDiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedDiscountRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Discount' + description: '' + delete: + operationId: api_discounts_destroy + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount. + required: true + tags: + - api + responses: + '204': + description: No response body + /api/discounts/create_batch/: + post: + operationId: api_discounts_create_batch_create + description: |- + Create a batch of codes. This is used in the staff-dashboard. + POST arguments are the same as in generate_discount_code - look there + for details. + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DiscountRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Discount' + description: '' + /api/discounts/user/: + get: + operationId: api_discounts_user_list + description: API view set for User Discounts. This one is for working with the + set as a whole. + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedUserDiscountList' + description: '' + post: + operationId: api_discounts_user_create + description: API view set for User Discounts. This one is for working with the + set as a whole. + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscount' + description: '' + /api/discounts/user/{id}/: + get: + operationId: api_discounts_user_retrieve + description: API view set for User Discounts. This one is for working with the + set as a whole. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscount' + description: '' + put: + operationId: api_discounts_user_update + description: API view set for User Discounts. This one is for working with the + set as a whole. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscount' + description: '' + patch: + operationId: api_discounts_user_partial_update + description: API view set for User Discounts. This one is for working with the + set as a whole. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedUserDiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedUserDiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedUserDiscountRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscount' + description: '' + delete: + operationId: api_discounts_user_destroy + description: API view set for User Discounts. This one is for working with the + set as a whole. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + tags: + - api + responses: + '204': + description: No response body + /api/enrollments/: + get: + operationId: api_enrollments_list + description: API view set for user enrollments + tags: + - api + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CourseRunEnrollment' + description: '' + post: + operationId: api_enrollments_create + description: API view set for user enrollments + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollment' + description: '' + /api/enrollments/{id}/: + put: + operationId: api_enrollments_update + description: API view set for user enrollments + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollment' + description: '' + patch: + operationId: api_enrollments_partial_update + description: API view set for user enrollments + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCourseRunEnrollmentRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCourseRunEnrollmentRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCourseRunEnrollmentRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollment' + description: '' + delete: + operationId: api_enrollments_destroy + description: API view set for user enrollments + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollment' + description: '' + /api/flexible_pricing/applications/: + get: + operationId: api_flexible_pricing_applications_list + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedFlexiblePriceList' + description: '' + post: + operationId: api_flexible_pricing_applications_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePrice' + description: '' + /api/flexible_pricing/applications/{id}/: + get: + operationId: api_flexible_pricing_applications_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePrice' + description: '' + put: + operationId: api_flexible_pricing_applications_update + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePrice' + description: '' + patch: + operationId: api_flexible_pricing_applications_partial_update + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePrice' + description: '' + delete: + operationId: api_flexible_pricing_applications_destroy + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + responses: + '204': + description: No response body + /api/flexible_pricing/applications_admin/: + get: + operationId: api_flexible_pricing_applications_admin_list + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedFlexiblePriceAdminList' + description: '' + post: + operationId: api_flexible_pricing_applications_admin_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdmin' + description: '' + /api/flexible_pricing/applications_admin/{id}/: + get: + operationId: api_flexible_pricing_applications_admin_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdmin' + description: '' + put: + operationId: api_flexible_pricing_applications_admin_update + description: Update the flexible pricing status + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdmin' + description: '' + patch: + operationId: api_flexible_pricing_applications_admin_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceAdminRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceAdminRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceAdminRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdmin' + description: '' + delete: + operationId: api_flexible_pricing_applications_admin_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - api + responses: + '204': + description: No response body + /api/flexible_pricing/coursewares/: + get: + operationId: api_flexible_pricing_coursewares_list + tags: + - api + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/FlexiblePriceCoursewareAdmin' + description: '' + post: + operationId: api_flexible_pricing_coursewares_create + tags: + - api + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceCoursewareAdmin' + description: '' + /api/flexible_pricing/coursewares/{id}/: + get: + operationId: api_flexible_pricing_coursewares_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceCoursewareAdmin' + description: '' + put: + operationId: api_flexible_pricing_coursewares_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceCoursewareAdmin' + description: '' + patch: + operationId: api_flexible_pricing_coursewares_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceCoursewareAdmin' + description: '' + delete: + operationId: api_flexible_pricing_coursewares_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - api + responses: + '204': + description: No response body + /api/flexible_pricing/exchange_rates/: + get: + operationId: api_flexible_pricing_exchange_rates_list + tags: + - api + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CurrencyExchangeRate' + description: '' + post: + operationId: api_flexible_pricing_exchange_rates_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRate' + description: '' + /api/flexible_pricing/exchange_rates/{id}/: + get: + operationId: api_flexible_pricing_exchange_rates_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this currency exchange rate. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRate' + description: '' + put: + operationId: api_flexible_pricing_exchange_rates_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this currency exchange rate. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRate' + description: '' + patch: + operationId: api_flexible_pricing_exchange_rates_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this currency exchange rate. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCurrencyExchangeRateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCurrencyExchangeRateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCurrencyExchangeRateRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRate' + description: '' + delete: + operationId: api_flexible_pricing_exchange_rates_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this currency exchange rate. + required: true + tags: + - api + responses: + '204': + description: No response body + /api/flexible_pricing/income_thresholds/: + get: + operationId: api_flexible_pricing_income_thresholds_list + tags: + - api + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CountryIncomeThreshold' + description: '' + post: + operationId: api_flexible_pricing_income_thresholds_create + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThreshold' + description: '' + /api/flexible_pricing/income_thresholds/{id}/: + get: + operationId: api_flexible_pricing_income_thresholds_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this country income threshold. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThreshold' + description: '' + put: + operationId: api_flexible_pricing_income_thresholds_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this country income threshold. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThreshold' + description: '' + patch: + operationId: api_flexible_pricing_income_thresholds_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this country income threshold. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCountryIncomeThresholdRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCountryIncomeThresholdRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCountryIncomeThresholdRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThreshold' + description: '' + delete: + operationId: api_flexible_pricing_income_thresholds_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this country income threshold. + required: true + tags: + - api + responses: + '204': + description: No response body + /api/instructor/{id}/: + get: + operationId: api_instructor_retrieve + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - api + responses: + '200': + description: No response body + /api/login/email/: + post: + operationId: api_login_email_create + description: Processes a request + tags: + - api + responses: + '200': + description: No response body + /api/login/password/: + post: + operationId: api_login_password_create + description: Processes a request + tags: + - api + responses: + '200': + description: No response body + /api/orders/history/: + get: + operationId: api_orders_history_list + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOrderHistoryList' + description: '' + /api/orders/history/{id}/: + get: + operationId: api_orders_history_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrderHistory' + description: '' + /api/orders/receipt/{id}/: + get: + operationId: api_orders_receipt_retrieve + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: '' + /api/partnerschools/: + get: + operationId: api_partnerschools_list + description: API view set for PartnerSchools + tags: + - api + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PartnerSchool' + description: '' + /api/partnerschools/{id}/: + get: + operationId: api_partnerschools_retrieve + description: API view set for PartnerSchools + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this partner school. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PartnerSchool' + description: '' + /api/password_reset/: + post: + operationId: api_password_reset_create + description: Overrides the post method of a Djoser view to update session after + successful password change + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CustomSendEmailResetRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CustomSendEmailResetRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CustomSendEmailResetRequest' + required: true + security: + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CustomSendEmailReset' + description: '' + /api/password_reset/confirm/: + post: + operationId: api_password_reset_confirm_create + description: Overrides the post method of a Djoser view to update session after + successful password change + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordResetConfirmRetypeRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PasswordResetConfirmRetypeRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PasswordResetConfirmRetypeRequest' + required: true + security: + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordResetConfirmRetype' + description: '' + /api/products/: + get: + operationId: api_products_list + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProductList' + description: '' + /api/products/{id}/: + get: + operationId: api_products_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this product. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + description: '' + /api/products/all/: + get: + operationId: api_products_all_list + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProductList' + description: '' + post: + operationId: api_products_all_create + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProductRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + description: '' + /api/products/all/{id}/: + get: + operationId: api_products_all_retrieve + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this product. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + description: '' + put: + operationId: api_products_all_update + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this product. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProductRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + description: '' + patch: + operationId: api_products_all_partial_update + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this product. + required: true + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedProductRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + description: '' + delete: + operationId: api_products_all_destroy + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this product. + required: true + tags: + - api + responses: + '204': + description: No response body + /api/program_enrollments/: + get: + operationId: api_program_enrollments_retrieve + description: |- + Returns a unified set of program and course enrollments for the current + user. + tags: + - api + responses: + '200': + description: No response body + /api/program_enrollments/{id}/: + delete: + operationId: api_program_enrollments_destroy + description: |- + Unenroll the user from this program. This is simpler than the corresponding + function for CourseRunEnrollments; edX doesn't really know what programs + are so there's nothing to process there. + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + responses: + '204': + description: No response body + /api/programs/: + get: + operationId: api_programs_list + description: API view set for Programs + parameters: + - in: query + name: id + schema: + type: integer + - in: query + name: live + schema: + type: boolean + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: readable_id + schema: + type: string + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProgramList' + description: '' + /api/programs/{id}/: + get: + operationId: api_programs_retrieve + description: API view set for Programs + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this program. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Program' + description: '' + /api/records/program/{id}/: + get: + operationId: api_records_program_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + responses: + '200': + description: No response body + /api/records/program/{id}/revoke/: + post: + operationId: api_records_program_revoke_create + description: |- + Disables sharing links for the learner's record. This only applies to the + anonymous ones; shares sent to partner schools are always allowed once they + are sent. + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + responses: + '200': + description: No response body + /api/records/program/{id}/share/: + post: + operationId: api_records_program_share_create + description: |- + Sets up a sharing link for the learner's record. Returns back the entire + learner record. + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - api + responses: + '200': + description: No response body + /api/records/shared/{uuid}/: + get: + operationId: api_records_shared_retrieve + description: |- + Does mostly the same thing as get_learner_record, but sets context to skip + the partner school and sharing information. + parameters: + - in: path + name: uuid + schema: + type: string + required: true + tags: + - api + responses: + '200': + description: No response body + /api/register/confirm/: + post: + operationId: api_register_confirm_create + description: Processes a request + tags: + - api + responses: + '200': + description: No response body + /api/register/details/: + post: + operationId: api_register_details_create + description: Processes a request + tags: + - api + responses: + '200': + description: No response body + /api/register/email/: + post: + operationId: api_register_email_create + description: Verify recaptcha response before proceeding + tags: + - api + responses: + '200': + description: No response body + /api/register/extra/: + post: + operationId: api_register_extra_create + description: Processes a request + tags: + - api + responses: + '200': + description: No response body + /api/set_password/: + post: + operationId: api_set_password_create + description: |- + Overrides CustomDjoserAPIView.set_password to update the session after a successful + password change. Without this explicit refresh, the user's session would be + invalid and they would be logged out. + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SetPasswordRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/SetPasswordRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/SetPasswordRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/SetPassword' + description: '' + /api/user_search/: + get: + operationId: api_user_search_list + description: |- + Provides an API for listing system users. This is for the staff + dashboard. + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - name: search + required: false + in: query + description: A search term. + schema: + type: string + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedStaffDashboardUserList' + description: '' + /api/user_search/{id}/: + get: + operationId: api_user_search_retrieve + description: |- + Provides an API for listing system users. This is for the staff + dashboard. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/StaffDashboardUser' + description: '' + /api/users/{id}/: + get: + operationId: api_users_retrieve + description: User retrieve viewsets + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user. + required: true + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PublicUser' + description: '' + /api/users/me: + get: + operationId: api_users_me_retrieve + description: User retrieve and update viewsets for the current user + tags: + - api + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: '' + patch: + operationId: api_users_me_partial_update + description: User retrieve and update viewsets for the current user + tags: + - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedUserRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedUserRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedUserRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: '' + /api/v0/baskets/: + get: + operationId: baskets_list + description: API view set for Basket + tags: + - baskets + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Basket' + description: '' + /api/v0/baskets/{parent_lookup_basket}/discounts/: + get: + operationId: baskets_discounts_list + description: Applied basket discounts + parameters: + - in: path + name: parent_lookup_basket + schema: + type: string + required: true + tags: + - baskets + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/BasketDiscount' + description: '' + /api/v0/baskets/{parent_lookup_basket}/discounts/{id}/: + get: + operationId: baskets_discounts_retrieve + description: Applied basket discounts + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: path + name: parent_lookup_basket + schema: + type: string + required: true + tags: + - baskets + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BasketDiscount' + description: '' + /api/v0/baskets/{parent_lookup_basket}/items/: + get: + operationId: baskets_items_list + description: API view set for BasketItem + parameters: + - in: path + name: parent_lookup_basket + schema: + type: string + required: true + tags: + - baskets + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/BasketItem' + description: '' + post: + operationId: baskets_items_create + description: API view set for BasketItem + parameters: + - in: path + name: parent_lookup_basket + schema: + type: string + required: true + tags: + - baskets + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BasketItemRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BasketItemRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/BasketItemRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/BasketItem' + description: '' + /api/v0/baskets/{parent_lookup_basket}/items/{id}/: + delete: + operationId: baskets_items_destroy + description: API view set for BasketItem + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: path + name: parent_lookup_basket + schema: + type: string + required: true + tags: + - baskets + responses: + '204': + description: No response body + /api/v0/baskets/{username}/: + get: + operationId: baskets_retrieve + description: API view set for Basket + parameters: + - in: path + name: username + schema: + type: string + required: true + tags: + - baskets + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Basket' + description: '' + /api/v0/checkout/add_to_cart/: + post: + operationId: checkout_add_to_cart_create + description: Add product to the cart + tags: + - checkout + responses: + '200': + description: No response body + /api/v0/checkout/cart/: + get: + operationId: checkout_cart_retrieve + description: Returns the current cart, with the product info embedded. + tags: + - checkout + responses: + '200': + description: No response body + /api/v0/checkout/redeem_discount/: + post: + operationId: checkout_redeem_discount_create + description: |- + API call to redeem a discount. Discounts are attached to the basket so + they can be attached at any time in the checkout process. Later on, + they'll be converted to attach to the resulting Order (as Baskets are + ephemeral). + + Discount application is subject to these rules: + - The discount can't be flagged for use with flexible pricing. + - If the discount is tied to a product, the product must already be in + the basket. + + POST Args: + - discount (str): Discount Code to apply + + Returns: + - Success message on success + - HTTP 406 if there's no basket yet + - HTTP 404 if the discount isn't found + tags: + - checkout + responses: + '200': + description: No response body + /api/v0/checkout/start_checkout/: + post: + operationId: checkout_start_checkout_create + description: |- + API call to start the checkout process. This assembles the basket items + into an Order with Lines for each item, applies the attached basket + discounts, and then calls the payment gateway to prepare for payment. + + Returns: + - JSON payload from the ol-django payment gateway app. The payment + gateway returns data necessary to construct a form that will + ultimately POST to the actual payment processor. + tags: + - checkout + responses: + '200': + description: No response body + /api/v0/discounts/: + get: + operationId: discounts_list + description: API view set for Discounts + parameters: + - in: query + name: is_redeemed + schema: + type: string + enum: + - 'no' + - 'yes' + description: |- + * `yes` - yes + * `no` - no + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - in: query + name: payment_type + schema: + type: string + nullable: true + enum: + - customer-support + - financial-assistance + - legacy + - marketing + - sales + - staff + description: |- + * `marketing` - marketing + * `sales` - sales + * `financial-assistance` - financial-assistance + * `customer-support` - customer-support + * `staff` - staff + * `legacy` - legacy + - in: query + name: q + schema: + type: string + description: q + - in: query + name: redemption_type + schema: + type: string + enum: + - one-time + - one-time-per-user + - unlimited + description: |- + * `one-time` - one-time + * `one-time-per-user` - one-time-per-user + * `unlimited` - unlimited + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDiscountList' + description: '' + post: + operationId: discounts_create + description: API view set for Discounts + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DiscountRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Discount' + description: '' + /api/v0/discounts/{parent_lookup_discount}/assignees/: + get: + operationId: discounts_assignees_list + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedUserDiscountMetaList' + description: '' + post: + operationId: discounts_assignees_create + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountMeta' + description: '' + /api/v0/discounts/{parent_lookup_discount}/assignees/{id}/: + get: + operationId: discounts_assignees_retrieve + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountMeta' + description: '' + put: + operationId: discounts_assignees_update + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountMeta' + description: '' + patch: + operationId: discounts_assignees_partial_update + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountMeta' + description: '' + delete: + operationId: discounts_assignees_destroy + description: API view set for User Discounts. This one is for use within a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '204': + description: No response body + /api/v0/discounts/{parent_lookup_discount}/products/: + get: + operationId: discounts_products_list + description: API view set for Discounts + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDiscountProductList' + description: '' + post: + operationId: discounts_products_create + description: API view set for Discounts + parameters: + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProduct' + description: '' + /api/v0/discounts/{parent_lookup_discount}/products/{id}/: + get: + operationId: discounts_products_retrieve + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount product. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProduct' + description: '' + put: + operationId: discounts_products_update + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount product. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DiscountProductRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProduct' + description: '' + patch: + operationId: discounts_products_partial_update + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount product. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedDiscountProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedDiscountProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedDiscountProductRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountProduct' + description: '' + delete: + operationId: discounts_products_destroy + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount product. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '204': + description: No response body + /api/v0/discounts/{parent_lookup_discount}/tiers/: + get: + operationId: discounts_tiers_list + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedFlexiblePriceTierList' + description: '' + post: + operationId: discounts_tiers_create + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTier' + description: '' + /api/v0/discounts/{parent_lookup_discount}/tiers/{id}/: + get: + operationId: discounts_tiers_retrieve + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price tier. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTier' + description: '' + put: + operationId: discounts_tiers_update + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price tier. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceTierRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTier' + description: '' + patch: + operationId: discounts_tiers_partial_update + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price tier. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceTierRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceTierRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceTierRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceTier' + description: '' + delete: + operationId: discounts_tiers_destroy + description: API view set for Flexible Pricing Tiers. This one is for use within + a Discount. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price tier. + required: true + - in: path + name: parent_lookup_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '204': + description: No response body + /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/: + get: + operationId: discounts_redemptions_list + description: API view set for Discount Redemptions + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedDiscountRedemptionList' + description: '' + post: + operationId: discounts_redemptions_create + description: API view set for Discount Redemptions + parameters: + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRedemption' + description: '' + /api/v0/discounts/{parent_lookup_redeemed_discount}/redemptions/{id}/: + get: + operationId: discounts_redemptions_retrieve + description: API view set for Discount Redemptions + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount redemption. + required: true + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRedemption' + description: '' + put: + operationId: discounts_redemptions_update + description: API view set for Discount Redemptions + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount redemption. + required: true + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRedemption' + description: '' + patch: + operationId: discounts_redemptions_partial_update + description: API view set for Discount Redemptions + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount redemption. + required: true + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRedemption' + description: '' + delete: + operationId: discounts_redemptions_destroy + description: API view set for Discount Redemptions + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount redemption. + required: true + - in: path + name: parent_lookup_redeemed_discount + schema: + type: string + required: true + tags: + - discounts + responses: + '204': + description: No response body + /api/v0/discounts/{id}/: + get: + operationId: discounts_retrieve + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount. + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Discount' + description: '' + put: + operationId: discounts_update + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount. + required: true + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DiscountRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Discount' + description: '' + patch: + operationId: discounts_partial_update + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount. + required: true + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedDiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedDiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedDiscountRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Discount' + description: '' + delete: + operationId: discounts_destroy + description: API view set for Discounts + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this discount. + required: true + tags: + - discounts + responses: + '204': + description: No response body + /api/v0/discounts/create_batch/: + post: + operationId: discounts_create_batch_create + description: |- + Create a batch of codes. This is used in the staff-dashboard. + POST arguments are the same as in generate_discount_code - look there + for details. + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/DiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/DiscountRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Discount' + description: '' + /api/v0/discounts/user/: + get: + operationId: discounts_user_list + description: API view set for User Discounts. This one is for working with the + set as a whole. + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedUserDiscountList' + description: '' + post: + operationId: discounts_user_create + description: API view set for User Discounts. This one is for working with the + set as a whole. + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscount' + description: '' + /api/v0/discounts/user/{id}/: + get: + operationId: discounts_user_retrieve + description: API view set for User Discounts. This one is for working with the + set as a whole. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + tags: + - discounts + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscount' + description: '' + put: + operationId: discounts_user_update + description: API view set for User Discounts. This one is for working with the + set as a whole. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserDiscountRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscount' + description: '' + patch: + operationId: discounts_user_partial_update + description: API view set for User Discounts. This one is for working with the + set as a whole. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + tags: + - discounts + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedUserDiscountRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedUserDiscountRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedUserDiscountRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDiscount' + description: '' + delete: + operationId: discounts_user_destroy + description: API view set for User Discounts. This one is for working with the + set as a whole. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this user discount. + required: true + tags: + - discounts + responses: + '204': + description: No response body + /api/v0/flexible_pricing/applications/: + get: + operationId: flexible_pricing_applications_list + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedFlexiblePriceList' + description: '' + post: + operationId: flexible_pricing_applications_create + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePrice' + description: '' + /api/v0/flexible_pricing/applications/{id}/: + get: + operationId: flexible_pricing_applications_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePrice' + description: '' + put: + operationId: flexible_pricing_applications_update + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePrice' + description: '' + patch: + operationId: flexible_pricing_applications_partial_update + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePrice' + description: '' + delete: + operationId: flexible_pricing_applications_destroy + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - flexible_pricing + responses: + '204': + description: No response body + /api/v0/flexible_pricing/applications_admin/: + get: + operationId: flexible_pricing_applications_admin_list + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedFlexiblePriceAdminList' + description: '' + post: + operationId: flexible_pricing_applications_admin_create + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdmin' + description: '' + /api/v0/flexible_pricing/applications_admin/{id}/: + get: + operationId: flexible_pricing_applications_admin_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdmin' + description: '' + put: + operationId: flexible_pricing_applications_admin_update + description: Update the flexible pricing status + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/FlexiblePriceAdminRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdmin' + description: '' + patch: + operationId: flexible_pricing_applications_admin_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceAdminRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceAdminRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedFlexiblePriceAdminRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceAdmin' + description: '' + delete: + operationId: flexible_pricing_applications_admin_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - flexible_pricing + responses: + '204': + description: No response body + /api/v0/flexible_pricing/coursewares/: + get: + operationId: flexible_pricing_coursewares_list + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/FlexiblePriceCoursewareAdmin' + description: '' + post: + operationId: flexible_pricing_coursewares_create + tags: + - flexible_pricing + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceCoursewareAdmin' + description: '' + /api/v0/flexible_pricing/coursewares/{id}/: + get: + operationId: flexible_pricing_coursewares_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceCoursewareAdmin' + description: '' + put: + operationId: flexible_pricing_coursewares_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceCoursewareAdmin' + description: '' + patch: + operationId: flexible_pricing_coursewares_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/FlexiblePriceCoursewareAdmin' + description: '' + delete: + operationId: flexible_pricing_coursewares_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this flexible price. + required: true + tags: + - flexible_pricing + responses: + '204': + description: No response body + /api/v0/flexible_pricing/exchange_rates/: + get: + operationId: flexible_pricing_exchange_rates_list + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CurrencyExchangeRate' + description: '' + post: + operationId: flexible_pricing_exchange_rates_create + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRate' + description: '' + /api/v0/flexible_pricing/exchange_rates/{id}/: + get: + operationId: flexible_pricing_exchange_rates_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this currency exchange rate. + required: true + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRate' + description: '' + put: + operationId: flexible_pricing_exchange_rates_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this currency exchange rate. + required: true + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CurrencyExchangeRateRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRate' + description: '' + patch: + operationId: flexible_pricing_exchange_rates_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this currency exchange rate. + required: true + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCurrencyExchangeRateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCurrencyExchangeRateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCurrencyExchangeRateRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CurrencyExchangeRate' + description: '' + delete: + operationId: flexible_pricing_exchange_rates_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this currency exchange rate. + required: true + tags: + - flexible_pricing + responses: + '204': + description: No response body + /api/v0/flexible_pricing/income_thresholds/: + get: + operationId: flexible_pricing_income_thresholds_list + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CountryIncomeThreshold' + description: '' + post: + operationId: flexible_pricing_income_thresholds_create + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThreshold' + description: '' + /api/v0/flexible_pricing/income_thresholds/{id}/: + get: + operationId: flexible_pricing_income_thresholds_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this country income threshold. + required: true + tags: + - flexible_pricing + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThreshold' + description: '' + put: + operationId: flexible_pricing_income_thresholds_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this country income threshold. + required: true + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CountryIncomeThresholdRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThreshold' + description: '' + patch: + operationId: flexible_pricing_income_thresholds_partial_update + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this country income threshold. + required: true + tags: + - flexible_pricing + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCountryIncomeThresholdRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCountryIncomeThresholdRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCountryIncomeThresholdRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CountryIncomeThreshold' + description: '' + delete: + operationId: flexible_pricing_income_thresholds_destroy + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this country income threshold. + required: true + tags: + - flexible_pricing + responses: + '204': + description: No response body + /api/v0/orders/history/: + get: + operationId: orders_history_list + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - orders + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedOrderHistoryList' + description: '' + /api/v0/orders/history/{id}/: + get: + operationId: orders_history_retrieve + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - orders + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OrderHistory' + description: '' + /api/v0/products/: + get: + operationId: products_list + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - products + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProductList' + description: '' + /api/v0/products/{id}/: + get: + operationId: products_retrieve + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this product. + required: true + tags: + - products + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + description: '' + /api/v0/products/all/: + get: + operationId: products_all_list + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + parameters: + - name: l + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - name: o + required: false + in: query + description: The initial index from which to return the results. + schema: + type: integer + tags: + - products + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProductList' + description: '' + post: + operationId: products_all_create + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + tags: + - products + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProductRequest' + required: true + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + description: '' + /api/v0/products/all/{id}/: + get: + operationId: products_all_retrieve + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this product. + required: true + tags: + - products + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + description: '' + put: + operationId: products_all_update + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this product. + required: true + tags: + - products + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProductRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + description: '' + patch: + operationId: products_all_partial_update + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this product. + required: true + tags: + - products + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedProductRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedProductRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedProductRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + description: '' + delete: + operationId: products_all_destroy + description: |- + This doesn't filter unenrollable products out, and adds name search for + courseware object readable id. It's really for the staff dashboard. + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this product. + required: true + tags: + - products + responses: + '204': + description: No response body + /api/v1/course_runs/: + get: + operationId: course_runs_list + description: API view set for CourseRuns + parameters: + - in: query + name: id + schema: + type: integer + - in: query + name: live + schema: + type: boolean + tags: + - course_runs + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CourseRunWithCourse' + description: '' + /api/v1/course_runs/{id}/: + get: + operationId: course_runs_retrieve + description: API view set for CourseRuns + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this course run. + required: true + tags: + - course_runs + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunWithCourse' + description: '' + /api/v1/courses/: + get: + operationId: courses_list + description: API view set for Courses + parameters: + - in: query + name: courserun_is_enrollable + schema: + type: boolean + - in: query + name: id + schema: + type: integer + - in: query + name: live + schema: + type: boolean + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: page__live + schema: + type: boolean + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: readable_id + schema: + type: string + tags: + - courses + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedCourseWithCourseRunsList' + description: '' + /api/v1/courses/{id}/: + get: + operationId: courses_retrieve + description: API view set for Courses + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this course. + required: true + tags: + - courses + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseWithCourseRuns' + description: '' + /api/v1/departments/: + get: + operationId: departments_list + description: API view set for Departments + tags: + - departments + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DepartmentWithCount' + description: '' + /api/v1/departments/{id}/: + get: + operationId: departments_retrieve + description: API view set for Departments + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this department. + required: true + tags: + - departments + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DepartmentWithCount' + description: '' + /api/v1/enrollments/: + get: + operationId: enrollments_list + description: API view set for user enrollments + tags: + - enrollments + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CourseRunEnrollment' + description: '' + post: + operationId: enrollments_create + description: API view set for user enrollments + tags: + - enrollments + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollment' + description: '' + /api/v1/enrollments/{id}/: + put: + operationId: enrollments_update + description: API view set for user enrollments + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - enrollments + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CourseRunEnrollmentRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollment' + description: '' + patch: + operationId: enrollments_partial_update + description: API view set for user enrollments + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - enrollments + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedCourseRunEnrollmentRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedCourseRunEnrollmentRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedCourseRunEnrollmentRequest' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollment' + description: '' + delete: + operationId: enrollments_destroy + description: API view set for user enrollments + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - enrollments + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseRunEnrollment' + description: '' + /api/v1/partnerschools/: + get: + operationId: partnerschools_list + description: API view set for PartnerSchools + tags: + - partnerschools + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PartnerSchool' + description: '' + /api/v1/partnerschools/{id}/: + get: + operationId: partnerschools_retrieve + description: API view set for PartnerSchools + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this partner school. + required: true + tags: + - partnerschools + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PartnerSchool' + description: '' + /api/v1/program_enrollments/: + get: + operationId: program_enrollments_retrieve + description: |- + Returns a unified set of program and course enrollments for the current + user. + tags: + - program_enrollments + responses: + '200': + description: No response body + /api/v1/program_enrollments/{id}/: + delete: + operationId: program_enrollments_destroy + description: |- + Unenroll the user from this program. This is simpler than the corresponding + function for CourseRunEnrollments; edX doesn't really know what programs + are so there's nothing to process there. + parameters: + - in: path + name: id + schema: + type: string + required: true + tags: + - program_enrollments + responses: + '204': + description: No response body + /api/v1/programs/: + get: + operationId: programs_list + description: API view set for Programs + parameters: + - in: query + name: id + schema: + type: integer + - in: query + name: live + schema: + type: boolean + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: readable_id + schema: + type: string + tags: + - programs + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProgramList' + description: '' + /api/v1/programs/{id}/: + get: + operationId: programs_retrieve + description: API view set for Programs + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this program. + required: true + tags: + - programs + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Program' + description: '' + /api/v2/courses/: + get: + operationId: courses_list_2 + description: API view set for Courses + parameters: + - in: query + name: courserun_is_enrollable + schema: + type: boolean + description: Course Run Is Enrollable + - in: query + name: id + schema: + type: array + items: + type: integer + description: Multiple values may be separated by commas. + explode: false + style: form + - in: query + name: live + schema: + type: boolean + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: page__live + schema: + type: boolean + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: readable_id + schema: + type: string + tags: + - courses + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedCourseWithCourseRunsList' + description: '' + /api/v2/courses/{id}/: + get: + operationId: courses_retrieve_2 + description: API view set for Courses + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this course. + required: true + tags: + - courses + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/CourseWithCourseRuns' + description: '' + /api/v2/departments/: + get: + operationId: departments_list_2 + description: API view set for Departments + tags: + - departments + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DepartmentWithCoursesAndPrograms' + description: '' + /api/v2/departments/{id}/: + get: + operationId: departments_retrieve_2 + description: API view set for Departments + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this department. + required: true + tags: + - departments + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/DepartmentWithCoursesAndPrograms' + description: '' + /api/v2/programs/: + get: + operationId: programs_list_2 + description: API viewset for Programs + parameters: + - in: query + name: id + schema: + type: integer + - in: query + name: live + schema: + type: boolean + - name: page + required: false + in: query + description: A page number within the paginated result set. + schema: + type: integer + - in: query + name: page__live + schema: + type: boolean + - name: page_size + required: false + in: query + description: Number of results to return per page. + schema: + type: integer + - in: query + name: readable_id + schema: + type: string + tags: + - programs + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/PaginatedProgramList' + description: '' + /api/v2/programs/{id}/: + get: + operationId: programs_retrieve_2 + description: API viewset for Programs + parameters: + - in: path + name: id + schema: + type: integer + description: A unique integer value identifying this program. + required: true + tags: + - programs + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Program' + description: '' + /cms/api/main/documents/: + get: + operationId: cms_api_main_documents_retrieve + tags: + - cms + responses: + '200': + description: No response body + /cms/api/main/documents/{id}/: + get: + operationId: cms_api_main_documents_retrieve_2 + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - cms + responses: + '200': + description: No response body + /cms/api/main/documents/find/: + get: + operationId: cms_api_main_documents_find_retrieve + tags: + - cms + responses: + '200': + description: No response body + /cms/api/main/images/: + get: + operationId: cms_api_main_images_retrieve + tags: + - cms + responses: + '200': + description: No response body + /cms/api/main/images/{id}/: + get: + operationId: cms_api_main_images_retrieve_2 + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - cms + responses: + '200': + description: No response body + /cms/api/main/images/find/: + get: + operationId: cms_api_main_images_find_retrieve + tags: + - cms + responses: + '200': + description: No response body + /cms/api/main/pages/: + get: + operationId: cms_api_main_pages_retrieve + description: A clone of the default Wagtail admin API that additionally orders + all responses by page title alphabetically + tags: + - cms + responses: + '200': + description: No response body + /cms/api/main/pages/{id}/: + get: + operationId: cms_api_main_pages_retrieve_2 + description: A clone of the default Wagtail admin API that additionally orders + all responses by page title alphabetically + parameters: + - in: path + name: id + schema: + type: integer + required: true + tags: + - cms + responses: + '200': + description: No response body + /cms/api/main/pages/{id}/action/{action_name}/: + post: + operationId: cms_api_main_pages_action_create + description: A clone of the default Wagtail admin API that additionally orders + all responses by page title alphabetically + parameters: + - in: path + name: action_name + schema: + type: string + required: true + - in: path + name: id + schema: + type: integer + required: true + tags: + - cms + responses: + '200': + description: No response body + /cms/api/main/pages/find/: + get: + operationId: cms_api_main_pages_find_retrieve + description: A clone of the default Wagtail admin API that additionally orders + all responses by page title alphabetically + tags: + - cms + responses: + '200': + description: No response body + /enrollments/: + post: + operationId: enrollments_create_2 + description: View to handle direct POST requests to enroll in a course run + tags: + - enrollments + responses: + '200': + description: No response body +components: + schemas: + Basket: + type: object + description: Basket model serializer + properties: + id: + type: integer + readOnly: true + user: + type: integer + basket_items: + type: string + readOnly: true + required: + - basket_items + - id + - user + BasketDiscount: + type: object + description: BasketDiscount model serializer + properties: + redeemed_discount: + allOf: + - $ref: '#/components/schemas/Nested' + readOnly: true + redeemed_basket: + allOf: + - $ref: '#/components/schemas/Nested' + readOnly: true + required: + - redeemed_basket + - redeemed_discount + BasketItem: + type: object + description: BasketItem model serializer + properties: + basket: + type: integer + product: + type: integer + id: + type: integer + readOnly: true + required: + - basket + - id + - product + BasketItemRequest: + type: object + description: BasketItem model serializer + properties: + basket: + type: integer + product: + type: integer + required: + - basket + - product + BlankEnum: + enum: + - '' + ChangeEmailRequestCreate: + type: object + description: Serializer for starting a user email change + properties: + new_email: + type: string + format: email + required: + - new_email + ChangeEmailRequestCreateRequest: + type: object + description: Serializer for starting a user email change + properties: + new_email: + type: string + format: email + minLength: 1 + password: + type: string + writeOnly: true + minLength: 1 + required: + - new_email + - password + ChangeEmailRequestUpdate: + type: object + description: Serializer for confirming a user email change + properties: + confirmed: + type: boolean + required: + - confirmed + CompanySizeEnum: + enum: + - '1' + - '9' + - '99' + - '999' + - '9999' + - '10000' + - '0' + type: string + description: |- + * `1` - Small/Start-up (1+ employees) + * `9` - Small/Home office (1-9 employees) + * `99` - Small (10-99 employees) + * `999` - Small to medium-sized (100-999 employees) + * `9999` - Medium-sized (1000-9999 employees) + * `10000` - Large Enterprise (10,000+ employees) + * `0` - Other (N/A or Don't know) + x-enum-descriptions: + - Small/Start-up (1+ employees) + - Small/Home office (1-9 employees) + - Small (10-99 employees) + - Small to medium-sized (100-999 employees) + - Medium-sized (1000-9999 employees) + - Large Enterprise (10,000+ employees) + - Other (N/A or Don't know) + CountryIncomeThreshold: + type: object + properties: + id: + type: integer + readOnly: true + country_code: + type: string + maxLength: 2 + income_threshold: + type: integer + maximum: 2147483647 + minimum: -2147483648 + required: + - country_code + - id + - income_threshold + CountryIncomeThresholdRequest: + type: object + properties: + country_code: + type: string + minLength: 1 + maxLength: 2 + income_threshold: + type: integer + maximum: 2147483647 + minimum: -2147483648 + required: + - country_code + - income_threshold + Course: + type: object + description: Course model serializer + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + readable_id: + type: string + pattern: ^[\w\-+:\.]+$ + maxLength: 255 + next_run_id: + type: string + readOnly: true + departments: + type: array + items: + $ref: '#/components/schemas/Department' + readOnly: true + page: + allOf: + - $ref: '#/components/schemas/CoursePage' + readOnly: true + programs: + type: string + readOnly: true + required: + - departments + - id + - next_run_id + - page + - programs + - readable_id + - title + CoursePage: + type: object + description: Course page model serializer + properties: + feature_image_src: + type: string + readOnly: true + page_url: + type: string + readOnly: true + description: + type: string + readOnly: true + live: + type: boolean + readOnly: true + length: + type: string + readOnly: true + effort: + type: string + readOnly: true + financial_assistance_form_url: + type: string + readOnly: true + current_price: + type: string + readOnly: true + instructors: + type: string + readOnly: true + required: + - current_price + - description + - effort + - feature_image_src + - financial_assistance_form_url + - instructors + - length + - live + - page_url + CourseRequest: + type: object + description: Course model serializer + properties: + title: + type: string + minLength: 1 + maxLength: 255 + readable_id: + type: string + minLength: 1 + pattern: ^[\w\-+:\.]+$ + maxLength: 255 + required: + - readable_id + - title + CourseRun: + type: object + description: CourseRun model serializer + properties: + title: + type: string + description: The title of the course. This value is synced automatically + with edX studio. + maxLength: 255 + start_date: + type: string + format: date-time + nullable: true + description: The day the course begins. This value is synced automatically + with edX studio. + end_date: + type: string + format: date-time + nullable: true + description: The last day the course is active. This value is synced automatically + with edX studio. + enrollment_start: + type: string + format: date-time + nullable: true + description: The first day students can enroll. This value is synced automatically + with edX studio. + enrollment_end: + type: string + format: date-time + nullable: true + description: The last day students can enroll. This value is synced automatically + with edX studio. + expiration_date: + type: string + format: date-time + nullable: true + description: The date beyond which the learner should not see link to this + course run on their dashboard. + courseware_url: + type: string + readOnly: true + courseware_id: + type: string + maxLength: 255 + certificate_available_date: + type: string + format: date-time + nullable: true + description: The day certificates should be available to users. This value + is synced automatically with edX studio. + upgrade_deadline: + type: string + format: date-time + nullable: true + description: The date beyond which the learner can not enroll in paid course + mode. + is_upgradable: + type: string + readOnly: true + is_enrollable: + type: string + readOnly: true + is_archived: + type: string + readOnly: true + is_self_paced: + type: boolean + run_tag: + type: string + description: 'A string that identifies the set of runs that this run belongs + to (example: ''R2'')' + maxLength: 100 + id: + type: integer + readOnly: true + live: + type: boolean + course_number: + type: string + readOnly: true + products: + type: array + items: + type: string + readOnly: true + approved_flexible_price_exists: + type: string + readOnly: true + required: + - approved_flexible_price_exists + - course_number + - courseware_id + - courseware_url + - id + - is_archived + - is_enrollable + - is_upgradable + - products + - run_tag + - title + CourseRunEnrollment: + type: object + description: CourseRunEnrollment model serializer + properties: + run: + allOf: + - $ref: '#/components/schemas/CourseRunWithCourse' + readOnly: true + id: + type: integer + readOnly: true + edx_emails_subscription: + type: boolean + certificate: + type: string + readOnly: true + enrollment_mode: + allOf: + - $ref: '#/components/schemas/EnrollmentModeEnum' + readOnly: true + approved_flexible_price_exists: + type: string + readOnly: true + grades: + type: string + readOnly: true + required: + - approved_flexible_price_exists + - certificate + - enrollment_mode + - grades + - id + - run + CourseRunEnrollmentRequest: + type: object + description: CourseRunEnrollment model serializer + properties: + edx_emails_subscription: + type: boolean + run_id: + type: integer + writeOnly: true + required: + - run_id + CourseRunWithCourse: + type: object + description: CourseRun model serializer - also serializes the parent Course. + properties: + title: + type: string + description: The title of the course. This value is synced automatically + with edX studio. + maxLength: 255 + start_date: + type: string + format: date-time + nullable: true + description: The day the course begins. This value is synced automatically + with edX studio. + end_date: + type: string + format: date-time + nullable: true + description: The last day the course is active. This value is synced automatically + with edX studio. + enrollment_start: + type: string + format: date-time + nullable: true + description: The first day students can enroll. This value is synced automatically + with edX studio. + enrollment_end: + type: string + format: date-time + nullable: true + description: The last day students can enroll. This value is synced automatically + with edX studio. + expiration_date: + type: string + format: date-time + nullable: true + description: The date beyond which the learner should not see link to this + course run on their dashboard. + courseware_url: + type: string + readOnly: true + courseware_id: + type: string + maxLength: 255 + certificate_available_date: + type: string + format: date-time + nullable: true + description: The day certificates should be available to users. This value + is synced automatically with edX studio. + upgrade_deadline: + type: string + format: date-time + nullable: true + description: The date beyond which the learner can not enroll in paid course + mode. + is_upgradable: + type: string + readOnly: true + is_enrollable: + type: string + readOnly: true + is_archived: + type: string + readOnly: true + is_self_paced: + type: boolean + run_tag: + type: string + description: 'A string that identifies the set of runs that this run belongs + to (example: ''R2'')' + maxLength: 100 + id: + type: integer + readOnly: true + live: + type: boolean + course_number: + type: string + readOnly: true + products: + type: array + items: + type: string + readOnly: true + approved_flexible_price_exists: + type: string + readOnly: true + course: + allOf: + - $ref: '#/components/schemas/Course' + readOnly: true + required: + - approved_flexible_price_exists + - course + - course_number + - courseware_id + - courseware_url + - id + - is_archived + - is_enrollable + - is_upgradable + - products + - run_tag + - title + CourseRunWithCourseRequest: + type: object + description: CourseRun model serializer - also serializes the parent Course. + properties: + title: + type: string + minLength: 1 + description: The title of the course. This value is synced automatically + with edX studio. + maxLength: 255 + start_date: + type: string + format: date-time + nullable: true + description: The day the course begins. This value is synced automatically + with edX studio. + end_date: + type: string + format: date-time + nullable: true + description: The last day the course is active. This value is synced automatically + with edX studio. + enrollment_start: + type: string + format: date-time + nullable: true + description: The first day students can enroll. This value is synced automatically + with edX studio. + enrollment_end: + type: string + format: date-time + nullable: true + description: The last day students can enroll. This value is synced automatically + with edX studio. + expiration_date: + type: string + format: date-time + nullable: true + description: The date beyond which the learner should not see link to this + course run on their dashboard. + courseware_id: + type: string + minLength: 1 + maxLength: 255 + certificate_available_date: + type: string + format: date-time + nullable: true + description: The day certificates should be available to users. This value + is synced automatically with edX studio. + upgrade_deadline: + type: string + format: date-time + nullable: true + description: The date beyond which the learner can not enroll in paid course + mode. + is_self_paced: + type: boolean + run_tag: + type: string + minLength: 1 + description: 'A string that identifies the set of runs that this run belongs + to (example: ''R2'')' + maxLength: 100 + live: + type: boolean + required: + - courseware_id + - run_tag + - title + CourseWithCourseRuns: + type: object + description: Course model serializer - also serializes child course runs + properties: + id: + type: integer + readOnly: true + title: + type: string + maxLength: 255 + readable_id: + type: string + pattern: ^[\w\-+:\.]+$ + maxLength: 255 + next_run_id: + type: string + readOnly: true + departments: + type: array + items: + $ref: '#/components/schemas/Department' + readOnly: true + page: + allOf: + - $ref: '#/components/schemas/CoursePage' + readOnly: true + programs: + type: string + readOnly: true + courseruns: + type: array + items: + $ref: '#/components/schemas/CourseRun' + readOnly: true + required: + - courseruns + - departments + - id + - next_run_id + - page + - programs + - readable_id + - title + CurrencyExchangeRate: + type: object + properties: + id: + type: integer + readOnly: true + currency_code: + type: string + maxLength: 3 + exchange_rate: + type: number + format: double + required: + - currency_code + - exchange_rate + - id + CurrencyExchangeRateRequest: + type: object + properties: + currency_code: + type: string + minLength: 1 + maxLength: 3 + exchange_rate: + type: number + format: double + required: + - currency_code + - exchange_rate + CustomSendEmailReset: + type: object + properties: + email: + type: string + format: email + required: + - email + CustomSendEmailResetRequest: + type: object + properties: + email: + type: string + format: email + minLength: 1 + required: + - email + Department: + type: object + description: Department model serializer + properties: + name: + type: string + maxLength: 128 + required: + - name + DepartmentRequest: + type: object + description: Department model serializer + properties: + name: + type: string + minLength: 1 + maxLength: 128 + required: + - name + DepartmentWithCount: + type: object + description: CourseRun model serializer that includes the number of courses + and programs associated with each departments + properties: + name: + type: string + maxLength: 128 + courses: + type: integer + programs: + type: integer + required: + - courses + - name + - programs + DepartmentWithCoursesAndPrograms: + type: object + description: Department model serializer that includes the number of courses + and programs associated with each + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 128 + slug: + type: string + maxLength: 128 + pattern: ^[-a-zA-Z0-9_]+$ + course_ids: + type: string + readOnly: true + program_ids: + type: string + readOnly: true + required: + - course_ids + - id + - name + - program_ids + - slug + Discount: + type: object + properties: + id: + type: integer + readOnly: true + amount: + type: string + format: decimal + pattern: ^-?\d{0,15}(?:\.\d{0,5})?$ + automatic: + type: boolean + discount_type: + $ref: '#/components/schemas/DiscountTypeEnum' + redemption_type: + $ref: '#/components/schemas/RedemptionTypeEnum' + max_redemptions: + type: integer + maximum: 2147483647 + minimum: 0 + nullable: true + discount_code: + type: string + maxLength: 100 + payment_type: + nullable: true + oneOf: + - $ref: '#/components/schemas/PaymentTypeEnum' + - $ref: '#/components/schemas/NullEnum' + is_redeemed: + type: string + readOnly: true + activation_date: + type: string + format: date-time + nullable: true + description: If set, this discount code will not be redeemable before this + date. + expiration_date: + type: string + format: date-time + nullable: true + description: If set, this discount code will not be redeemable after this + date. + required: + - amount + - discount_code + - discount_type + - id + - is_redeemed + - redemption_type + DiscountProduct: + type: object + properties: + id: + type: integer + readOnly: true + discount: + $ref: '#/components/schemas/Discount' + product: + $ref: '#/components/schemas/Product' + required: + - discount + - id + - product + DiscountProductRequest: + type: object + properties: + discount: + $ref: '#/components/schemas/DiscountRequest' + product: + $ref: '#/components/schemas/ProductRequest' + required: + - discount + - product + DiscountRedemption: + type: object + properties: + id: + type: integer + readOnly: true + redemption_date: + type: string + format: date-time + readOnly: true + redeemed_by: + allOf: + - $ref: '#/components/schemas/Nested' + readOnly: true + redeemed_discount: + allOf: + - $ref: '#/components/schemas/Nested' + readOnly: true + redeemed_order: + allOf: + - $ref: '#/components/schemas/Nested' + readOnly: true + required: + - id + - redeemed_by + - redeemed_discount + - redeemed_order + - redemption_date + DiscountRequest: + type: object + properties: + amount: + type: string + format: decimal + pattern: ^-?\d{0,15}(?:\.\d{0,5})?$ + automatic: + type: boolean + discount_type: + $ref: '#/components/schemas/DiscountTypeEnum' + redemption_type: + $ref: '#/components/schemas/RedemptionTypeEnum' + max_redemptions: + type: integer + maximum: 2147483647 + minimum: 0 + nullable: true + discount_code: + type: string + minLength: 1 + maxLength: 100 + payment_type: + nullable: true + oneOf: + - $ref: '#/components/schemas/PaymentTypeEnum' + - $ref: '#/components/schemas/NullEnum' + activation_date: + type: string + format: date-time + nullable: true + description: If set, this discount code will not be redeemable before this + date. + expiration_date: + type: string + format: date-time + nullable: true + description: If set, this discount code will not be redeemable after this + date. + required: + - amount + - discount_code + - discount_type + - redemption_type + DiscountTypeEnum: + enum: + - percent-off + - dollars-off + - fixed-price + type: string + description: |- + * `percent-off` - percent-off + * `dollars-off` - dollars-off + * `fixed-price` - fixed-price + x-enum-descriptions: + - percent-off + - dollars-off + - fixed-price + EnrollmentModeEnum: + enum: + - audit + - verified + type: string + description: |- + * `audit` - audit + * `verified` - verified + x-enum-descriptions: + - audit + - verified + FlexiblePrice: + type: object + description: Serializer for flexible price requests + properties: + id: + type: integer + readOnly: true + user: + allOf: + - $ref: '#/components/schemas/User' + readOnly: true + status: + $ref: '#/components/schemas/StatusEnum' + income_usd: + type: number + format: double + nullable: true + original_income: + type: number + format: double + nullable: true + original_currency: + type: string + nullable: true + maxLength: 10 + country_of_income: + type: string + nullable: true + maxLength: 100 + date_exchange_rate: + type: string + format: date-time + nullable: true + date_documents_sent: + type: string + format: date + nullable: true + justification: + type: string + nullable: true + country_of_residence: + type: string + required: + - id + - user + FlexiblePriceAdmin: + type: object + description: Serializer for Financial Assistance Requests + properties: + id: + type: integer + readOnly: true + user: + allOf: + - $ref: '#/components/schemas/User' + readOnly: true + status: + $ref: '#/components/schemas/StatusEnum' + country_of_income: + type: string + nullable: true + maxLength: 100 + date_exchange_rate: + type: string + format: date-time + nullable: true + date_documents_sent: + type: string + format: date + nullable: true + justification: + type: string + nullable: true + country_of_residence: + type: string + courseware: + type: string + readOnly: true + discount: + type: string + readOnly: true + applicable_discounts: + type: string + readOnly: true + income: + type: string + readOnly: true + required: + - applicable_discounts + - courseware + - discount + - id + - income + - user + FlexiblePriceAdminRequest: + type: object + description: Serializer for Financial Assistance Requests + properties: + status: + $ref: '#/components/schemas/StatusEnum' + country_of_income: + type: string + nullable: true + minLength: 1 + maxLength: 100 + date_exchange_rate: + type: string + format: date-time + nullable: true + date_documents_sent: + type: string + format: date + nullable: true + justification: + type: string + nullable: true + country_of_residence: + type: string + FlexiblePriceCoursewareAdmin: + type: object + description: Serializer for coursewares in flexible price requests + properties: + id: + type: string + readOnly: true + title: + type: string + readOnly: true + readable_id: + type: string + readOnly: true + type: + type: string + readOnly: true + required: + - id + - readable_id + - title + - type + FlexiblePriceRequest: + type: object + description: Serializer for flexible price requests + properties: + status: + $ref: '#/components/schemas/StatusEnum' + income_usd: + type: number + format: double + nullable: true + original_income: + type: number + format: double + nullable: true + original_currency: + type: string + nullable: true + minLength: 1 + maxLength: 10 + country_of_income: + type: string + nullable: true + minLength: 1 + maxLength: 100 + date_exchange_rate: + type: string + format: date-time + nullable: true + date_documents_sent: + type: string + format: date + nullable: true + justification: + type: string + nullable: true + country_of_residence: + type: string + FlexiblePriceTier: + type: object + properties: + id: + type: integer + readOnly: true + courseware_object: + type: string + readOnly: true + discount: + type: integer + current: + type: boolean + income_threshold_usd: + type: number + format: double + required: + - courseware_object + - discount + - id + - income_threshold_usd + FlexiblePriceTierRequest: + type: object + properties: + discount: + type: integer + current: + type: boolean + income_threshold_usd: + type: number + format: double + required: + - discount + - income_threshold_usd + GenderEnum: + enum: + - m + - f + - t + - nb + - o + type: string + description: |- + * `m` - Male + * `f` - Female + * `t` - Transgender + * `nb` - Non-binary/non-conforming + * `o` - Other/Prefer Not to Say + x-enum-descriptions: + - Male + - Female + - Transgender + - Non-binary/non-conforming + - Other/Prefer Not to Say + HighestEducationEnum: + enum: + - Doctorate + - Master's or professional degree + - Bachelor's degree + - Associate degree + - Secondary/high school + - Junior secondary/junior high/middle school + - Elementary/primary school + - No formal education + - Other education + description: |- + * `None` - ---- + * `Doctorate` - Doctorate + * `Master's or professional degree` - Master's or professional degree + * `Bachelor's degree` - Bachelor's degree + * `Associate degree` - Associate degree + * `Secondary/high school` - Secondary/high school + * `Junior secondary/junior high/middle school` - Junior secondary/junior high/middle school + * `Elementary/primary school` - Elementary/primary school + * `No formal education` - No formal education + * `Other education` - Other education + x-enum-descriptions: + - Doctorate + - Master's or professional degree + - Bachelor's degree + - Associate degree + - Secondary/high school + - Junior secondary/junior high/middle school + - Elementary/primary school + - No formal education + - Other education + LegalAddress: + type: object + description: Serializer for legal address + properties: + first_name: + type: string + maxLength: 60 + last_name: + type: string + maxLength: 60 + country: + type: string + maxLength: 2 + state: + type: string + nullable: true + maxLength: 10 + required: + - country + - first_name + - last_name + LegalAddressRequest: + type: object + description: Serializer for legal address + properties: + first_name: + type: string + minLength: 1 + maxLength: 60 + last_name: + type: string + minLength: 1 + maxLength: 60 + country: + type: string + minLength: 1 + maxLength: 2 + state: + type: string + nullable: true + maxLength: 10 + required: + - country + - first_name + - last_name + Line: + type: object + properties: + quantity: + type: integer + maximum: 2147483647 + minimum: 0 + item_description: + type: string + readOnly: true + content_type: + type: string + readOnly: true + unit_price: + type: string + readOnly: true + total_price: + type: string + readOnly: true + id: + type: integer + readOnly: true + product: + type: string + readOnly: true + required: + - content_type + - id + - item_description + - product + - quantity + - total_price + - unit_price + Nested: + type: object + properties: + id: + type: integer + readOnly: true + created_on: + type: string + format: date-time + readOnly: true + updated_on: + type: string + format: date-time + readOnly: true + amount: + type: string + format: decimal + pattern: ^-?\d{0,15}(?:\.\d{0,5})?$ + automatic: + type: boolean + discount_type: + $ref: '#/components/schemas/DiscountTypeEnum' + redemption_type: + $ref: '#/components/schemas/RedemptionTypeEnum' + payment_type: + nullable: true + oneOf: + - $ref: '#/components/schemas/PaymentTypeEnum' + - $ref: '#/components/schemas/NullEnum' + max_redemptions: + type: integer + maximum: 2147483647 + minimum: 0 + nullable: true + discount_code: + type: string + maxLength: 100 + activation_date: + type: string + format: date-time + nullable: true + description: If set, this discount code will not be redeemable before this + date. + expiration_date: + type: string + format: date-time + nullable: true + description: If set, this discount code will not be redeemable after this + date. + is_bulk: + type: boolean + required: + - amount + - created_on + - discount_code + - discount_type + - id + - redemption_type + - updated_on + NestedRequest: + type: object + properties: + amount: + type: string + format: decimal + pattern: ^-?\d{0,15}(?:\.\d{0,5})?$ + automatic: + type: boolean + discount_type: + $ref: '#/components/schemas/DiscountTypeEnum' + redemption_type: + $ref: '#/components/schemas/RedemptionTypeEnum' + payment_type: + nullable: true + oneOf: + - $ref: '#/components/schemas/PaymentTypeEnum' + - $ref: '#/components/schemas/NullEnum' + max_redemptions: + type: integer + maximum: 2147483647 + minimum: 0 + nullable: true + discount_code: + type: string + minLength: 1 + maxLength: 100 + activation_date: + type: string + format: date-time + nullable: true + description: If set, this discount code will not be redeemable before this + date. + expiration_date: + type: string + format: date-time + nullable: true + description: If set, this discount code will not be redeemable after this + date. + is_bulk: + type: boolean + required: + - amount + - discount_code + - discount_type + - redemption_type + NullEnum: + enum: + - null + Order: + type: object + properties: + id: + type: integer + readOnly: true + state: + $ref: '#/components/schemas/StateEnum' + purchaser: + type: string + readOnly: true + total_price_paid: + type: string + format: decimal + pattern: ^-?\d{0,15}(?:\.\d{0,5})?$ + lines: + type: string + readOnly: true + discounts: + type: string + readOnly: true + refunds: + type: string + readOnly: true + reference_number: + type: string + nullable: true + maxLength: 255 + created_on: + type: string + format: date-time + readOnly: true + transactions: + type: string + readOnly: true + street_address: + type: string + readOnly: true + required: + - created_on + - discounts + - id + - lines + - purchaser + - refunds + - state + - street_address + - total_price_paid + - transactions + OrderHistory: + type: object + properties: + id: + type: integer + readOnly: true + state: + $ref: '#/components/schemas/StateEnum' + reference_number: + type: string + nullable: true + maxLength: 255 + purchaser: + allOf: + - $ref: '#/components/schemas/Nested' + readOnly: true + total_price_paid: + type: string + format: decimal + pattern: ^-?\d{0,15}(?:\.\d{0,5})?$ + lines: + type: array + items: + $ref: '#/components/schemas/Line' + created_on: + type: string + format: date-time + readOnly: true + titles: + type: string + readOnly: true + updated_on: + type: string + format: date-time + readOnly: true + required: + - created_on + - id + - lines + - purchaser + - state + - titles + - total_price_paid + - updated_on + PaginatedCourseWithCourseRunsList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/CourseWithCourseRuns' + PaginatedDiscountList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=400&l=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=200&l=100 + results: + type: array + items: + $ref: '#/components/schemas/Discount' + PaginatedDiscountProductList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=400&l=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=200&l=100 + results: + type: array + items: + $ref: '#/components/schemas/DiscountProduct' + PaginatedDiscountRedemptionList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=400&l=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=200&l=100 + results: + type: array + items: + $ref: '#/components/schemas/DiscountRedemption' + PaginatedFlexiblePriceAdminList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=400&l=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=200&l=100 + results: + type: array + items: + $ref: '#/components/schemas/FlexiblePriceAdmin' + PaginatedFlexiblePriceList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=400&l=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=200&l=100 + results: + type: array + items: + $ref: '#/components/schemas/FlexiblePrice' + PaginatedFlexiblePriceTierList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=400&l=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=200&l=100 + results: + type: array + items: + $ref: '#/components/schemas/FlexiblePriceTier' + PaginatedOrderHistoryList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=400&l=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=200&l=100 + results: + type: array + items: + $ref: '#/components/schemas/OrderHistory' + PaginatedProductList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=400&l=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=200&l=100 + results: + type: array + items: + $ref: '#/components/schemas/Product' + PaginatedProgramList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/Program' + PaginatedStaffDashboardUserList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=400&l=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=200&l=100 + results: + type: array + items: + $ref: '#/components/schemas/StaffDashboardUser' + PaginatedUserDiscountList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=400&l=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=200&l=100 + results: + type: array + items: + $ref: '#/components/schemas/UserDiscount' + PaginatedUserDiscountMetaList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=400&l=100 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?o=200&l=100 + results: + type: array + items: + $ref: '#/components/schemas/UserDiscountMeta' + PartnerSchool: + type: object + properties: + id: + type: integer + readOnly: true + created_on: + type: string + format: date-time + readOnly: true + updated_on: + type: string + format: date-time + readOnly: true + name: + type: string + maxLength: 255 + email: + type: string + required: + - created_on + - email + - id + - name + - updated_on + PasswordResetConfirmRetype: + type: object + properties: + uid: + type: string + token: + type: string + new_password: + type: string + re_new_password: + type: string + required: + - new_password + - re_new_password + - token + - uid + PasswordResetConfirmRetypeRequest: + type: object + properties: + uid: + type: string + minLength: 1 + token: + type: string + minLength: 1 + new_password: + type: string + minLength: 1 + re_new_password: + type: string + minLength: 1 + required: + - new_password + - re_new_password + - token + - uid + PatchedChangeEmailRequestUpdateRequest: + type: object + description: Serializer for confirming a user email change + properties: + confirmed: + type: boolean + PatchedCountryIncomeThresholdRequest: + type: object + properties: + country_code: + type: string + minLength: 1 + maxLength: 2 + income_threshold: + type: integer + maximum: 2147483647 + minimum: -2147483648 + PatchedCourseRunEnrollmentRequest: + type: object + description: CourseRunEnrollment model serializer + properties: + edx_emails_subscription: + type: boolean + run_id: + type: integer + writeOnly: true + PatchedCurrencyExchangeRateRequest: + type: object + properties: + currency_code: + type: string + minLength: 1 + maxLength: 3 + exchange_rate: + type: number + format: double + PatchedDiscountProductRequest: + type: object + properties: + discount: + $ref: '#/components/schemas/DiscountRequest' + product: + $ref: '#/components/schemas/ProductRequest' + PatchedDiscountRequest: + type: object + properties: + amount: + type: string + format: decimal + pattern: ^-?\d{0,15}(?:\.\d{0,5})?$ + automatic: + type: boolean + discount_type: + $ref: '#/components/schemas/DiscountTypeEnum' + redemption_type: + $ref: '#/components/schemas/RedemptionTypeEnum' + max_redemptions: + type: integer + maximum: 2147483647 + minimum: 0 + nullable: true + discount_code: + type: string + minLength: 1 + maxLength: 100 + payment_type: + nullable: true + oneOf: + - $ref: '#/components/schemas/PaymentTypeEnum' + - $ref: '#/components/schemas/NullEnum' + activation_date: + type: string + format: date-time + nullable: true + description: If set, this discount code will not be redeemable before this + date. + expiration_date: + type: string + format: date-time + nullable: true + description: If set, this discount code will not be redeemable after this + date. + PatchedFlexiblePriceAdminRequest: + type: object + description: Serializer for Financial Assistance Requests + properties: + status: + $ref: '#/components/schemas/StatusEnum' + country_of_income: + type: string + nullable: true + minLength: 1 + maxLength: 100 + date_exchange_rate: + type: string + format: date-time + nullable: true + date_documents_sent: + type: string + format: date + nullable: true + justification: + type: string + nullable: true + country_of_residence: + type: string + PatchedFlexiblePriceRequest: + type: object + description: Serializer for flexible price requests + properties: + status: + $ref: '#/components/schemas/StatusEnum' + income_usd: + type: number + format: double + nullable: true + original_income: + type: number + format: double + nullable: true + original_currency: + type: string + nullable: true + minLength: 1 + maxLength: 10 + country_of_income: + type: string + nullable: true + minLength: 1 + maxLength: 100 + date_exchange_rate: + type: string + format: date-time + nullable: true + date_documents_sent: + type: string + format: date + nullable: true + justification: + type: string + nullable: true + country_of_residence: + type: string + PatchedFlexiblePriceTierRequest: + type: object + properties: + discount: + type: integer + current: + type: boolean + income_threshold_usd: + type: number + format: double + PatchedProductRequest: + type: object + description: Simple serializer for Product without related purchasable objects + properties: + price: + type: string + format: decimal + pattern: ^-?\d{0,5}(?:\.\d{0,2})?$ + description: + type: string + minLength: 1 + is_active: + type: boolean + description: Controls visibility of the product in the app. + PatchedUserDiscountRequest: + type: object + properties: + discount: + type: integer + user: + type: integer + PatchedUserRequest: + type: object + description: Serializer for users + properties: + username: + type: string + minLength: 1 + name: + type: string + maxLength: 255 + email: + type: string + password: + type: string + writeOnly: true + minLength: 1 + legal_address: + allOf: + - $ref: '#/components/schemas/LegalAddressRequest' + nullable: true + user_profile: + allOf: + - $ref: '#/components/schemas/UserProfileRequest' + nullable: true + is_active: + type: boolean + default: true + PaymentTypeEnum: + enum: + - marketing + - sales + - financial-assistance + - customer-support + - staff + - legacy + type: string + description: |- + * `marketing` - marketing + * `sales` - sales + * `financial-assistance` - financial-assistance + * `customer-support` - customer-support + * `staff` - staff + * `legacy` - legacy + x-enum-descriptions: + - marketing + - sales + - financial-assistance + - customer-support + - staff + - legacy + Product: + type: object + description: Simple serializer for Product without related purchasable objects + properties: + id: + type: integer + readOnly: true + price: + type: string + format: decimal + pattern: ^-?\d{0,5}(?:\.\d{0,2})?$ + description: + type: string + is_active: + type: boolean + description: Controls visibility of the product in the app. + purchasable_object: + type: string + readOnly: true + required: + - description + - id + - price + - purchasable_object + ProductRequest: + type: object + description: Simple serializer for Product without related purchasable objects + properties: + price: + type: string + format: decimal + pattern: ^-?\d{0,5}(?:\.\d{0,2})?$ + description: + type: string + minLength: 1 + is_active: + type: boolean + description: Controls visibility of the product in the app. + required: + - description + - price + Program: + type: object + description: Program model serializer + properties: + title: + type: string + maxLength: 255 + readable_id: + type: string + pattern: ^[\w\-+:\.]+$ + maxLength: 255 + id: + type: integer + readOnly: true + courses: + type: string + readOnly: true + requirements: + type: string + readOnly: true + req_tree: + type: string + readOnly: true + page: + type: string + readOnly: true + program_type: + type: string + nullable: true + maxLength: 255 + departments: + type: array + items: + $ref: '#/components/schemas/Department' + readOnly: true + live: + type: boolean + required: + - courses + - departments + - id + - page + - readable_id + - req_tree + - requirements + - title + PublicUser: + type: object + description: Serializer for public user data + properties: + id: + type: integer + readOnly: true + username: + type: string + maxLength: 30 + name: + type: string + maxLength: 255 + created_on: + type: string + format: date-time + readOnly: true + updated_on: + type: string + format: date-time + readOnly: true + required: + - created_on + - id + - updated_on + - username + RedemptionTypeEnum: + enum: + - one-time + - one-time-per-user + - unlimited + type: string + description: |- + * `one-time` - one-time + * `one-time-per-user` - one-time-per-user + * `unlimited` - unlimited + x-enum-descriptions: + - one-time + - one-time-per-user + - unlimited + SetPassword: + type: object + properties: + new_password: + type: string + current_password: + type: string + required: + - current_password + - new_password + SetPasswordRequest: + type: object + properties: + new_password: + type: string + minLength: 1 + current_password: + type: string + minLength: 1 + required: + - current_password + - new_password + StaffDashboardUser: + type: object + description: Serializer for data we care about in the staff dashboard + properties: + id: + type: integer + readOnly: true + username: + type: string + maxLength: 30 + name: + type: string + maxLength: 255 + email: + type: string + format: email + maxLength: 254 + legal_address: + allOf: + - $ref: '#/components/schemas/LegalAddress' + nullable: true + is_staff: + type: boolean + description: The user can access the admin site + is_superuser: + type: boolean + title: Superuser status + description: Designates that this user has all permissions without explicitly + assigning them. + required: + - email + - id + - legal_address + - username + StateEnum: + enum: + - pending + - fulfilled + - canceled + - declined + - errored + - refunded + - review + - partially_refunded + type: string + description: |- + * `pending` - Pending + * `fulfilled` - Fulfilled + * `canceled` - Canceled + * `declined` - Declined + * `errored` - Errored + * `refunded` - Refunded + * `review` - Review + * `partially_refunded` - Partially Refunded + x-enum-descriptions: + - Pending + - Fulfilled + - Canceled + - Declined + - Errored + - Refunded + - Review + - Partially Refunded + StatusEnum: + enum: + - approved + - auto-approved + - created + - pending-manual-approval + - denied + - reset + type: string + description: |- + * `approved` - approved + * `auto-approved` - auto-approved + * `created` - created + * `pending-manual-approval` - pending-manual-approval + * `denied` - denied + * `reset` - reset + x-enum-descriptions: + - approved + - auto-approved + - created + - pending-manual-approval + - denied + - reset + User: + type: object + description: Serializer for users + properties: + id: + type: integer + readOnly: true + username: + type: string + name: + type: string + maxLength: 255 + email: + type: string + legal_address: + allOf: + - $ref: '#/components/schemas/LegalAddress' + nullable: true + user_profile: + allOf: + - $ref: '#/components/schemas/UserProfile' + nullable: true + is_anonymous: + type: string + readOnly: true + is_authenticated: + type: string + readOnly: true + is_editor: + type: boolean + description: Returns True if the user has editor permissions for the CMS + readOnly: true + is_staff: + type: boolean + readOnly: true + description: The user can access the admin site + is_superuser: + type: boolean + readOnly: true + title: Superuser status + description: Designates that this user has all permissions without explicitly + assigning them. + created_on: + type: string + format: date-time + readOnly: true + updated_on: + type: string + format: date-time + readOnly: true + grants: + type: string + readOnly: true + is_active: + type: boolean + default: true + required: + - created_on + - grants + - id + - is_anonymous + - is_authenticated + - is_editor + - is_staff + - is_superuser + - legal_address + - updated_on + UserDiscount: + type: object + properties: + id: + type: integer + readOnly: true + discount: + type: integer + user: + type: integer + required: + - discount + - id + - user + UserDiscountMeta: + type: object + properties: + id: + type: integer + readOnly: true + discount: + allOf: + - $ref: '#/components/schemas/Nested' + readOnly: true + user: + allOf: + - $ref: '#/components/schemas/Nested' + readOnly: true + required: + - discount + - id + - user + UserDiscountRequest: + type: object + properties: + discount: + type: integer + user: + type: integer + required: + - discount + - user + UserProfile: + type: object + description: Serializer for profile + properties: + gender: + nullable: true + oneOf: + - $ref: '#/components/schemas/GenderEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + year_of_birth: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true + addl_field_flag: + type: boolean + description: Flags if we've asked the user for additional information + company: + type: string + nullable: true + maxLength: 128 + job_title: + type: string + nullable: true + maxLength: 128 + industry: + type: string + nullable: true + maxLength: 60 + job_function: + type: string + nullable: true + maxLength: 60 + company_size: + nullable: true + oneOf: + - $ref: '#/components/schemas/CompanySizeEnum' + - $ref: '#/components/schemas/NullEnum' + years_experience: + nullable: true + oneOf: + - $ref: '#/components/schemas/YearsExperienceEnum' + - $ref: '#/components/schemas/NullEnum' + leadership_level: + type: string + nullable: true + maxLength: 60 + highest_education: + nullable: true + oneOf: + - $ref: '#/components/schemas/HighestEducationEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + type_is_student: + type: boolean + nullable: true + description: The learner identifies as type Student + type_is_professional: + type: boolean + nullable: true + description: The learner identifies as type Professional + type_is_educator: + type: boolean + nullable: true + description: The learner identifies as type Educator + type_is_other: + type: boolean + nullable: true + description: The learner identifies as type Other (not professional, student, + or educator) + UserProfileRequest: + type: object + description: Serializer for profile + properties: + gender: + nullable: true + oneOf: + - $ref: '#/components/schemas/GenderEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + year_of_birth: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true + addl_field_flag: + type: boolean + description: Flags if we've asked the user for additional information + company: + type: string + nullable: true + maxLength: 128 + job_title: + type: string + nullable: true + maxLength: 128 + industry: + type: string + nullable: true + maxLength: 60 + job_function: + type: string + nullable: true + maxLength: 60 + company_size: + nullable: true + oneOf: + - $ref: '#/components/schemas/CompanySizeEnum' + - $ref: '#/components/schemas/NullEnum' + years_experience: + nullable: true + oneOf: + - $ref: '#/components/schemas/YearsExperienceEnum' + - $ref: '#/components/schemas/NullEnum' + leadership_level: + type: string + nullable: true + maxLength: 60 + highest_education: + nullable: true + oneOf: + - $ref: '#/components/schemas/HighestEducationEnum' + - $ref: '#/components/schemas/BlankEnum' + - $ref: '#/components/schemas/NullEnum' + type_is_student: + type: boolean + nullable: true + description: The learner identifies as type Student + type_is_professional: + type: boolean + nullable: true + description: The learner identifies as type Professional + type_is_educator: + type: boolean + nullable: true + description: The learner identifies as type Educator + type_is_other: + type: boolean + nullable: true + description: The learner identifies as type Other (not professional, student, + or educator) + UserRequest: + type: object + description: Serializer for users + properties: + username: + type: string + minLength: 1 + name: + type: string + maxLength: 255 + email: + type: string + password: + type: string + writeOnly: true + minLength: 1 + legal_address: + allOf: + - $ref: '#/components/schemas/LegalAddressRequest' + nullable: true + user_profile: + allOf: + - $ref: '#/components/schemas/UserProfileRequest' + nullable: true + is_active: + type: boolean + default: true + required: + - legal_address + YearsExperienceEnum: + enum: + - '2' + - '5' + - '10' + - '15' + - '20' + - '21' + - '0' + type: string + description: |- + * `2` - Less than 2 years + * `5` - 2-5 years + * `10` - 6 - 10 years + * `15` - 11 - 15 years + * `20` - 16 - 20 years + * `21` - More than 20 years + * `0` - Prefer not to say + x-enum-descriptions: + - Less than 2 years + - 2-5 years + - 6 - 10 years + - 11 - 15 years + - 16 - 20 years + - More than 20 years + - Prefer not to say diff --git a/openapi/urls.py b/openapi/urls.py new file mode 100644 index 0000000000..5b90b78630 --- /dev/null +++ b/openapi/urls.py @@ -0,0 +1,26 @@ +""" +URL Configuration for schema & documentation views +""" + +from django.urls import path +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularRedocView, + SpectacularSwaggerView, +) + +urlpatterns = [ + path( + "api/v0/schema/", SpectacularAPIView.as_view(api_version="v0"), name="v0_schema" + ), + path( + "api/v0/schema/swagger-ui/", + SpectacularSwaggerView.as_view(url_name="v0_schema"), + name="v0_swagger_ui", + ), + path( + "api/v0/schema/redoc/", + SpectacularRedocView.as_view(url_name="v0_schema"), + name="v0_redoc", + ), +] diff --git a/poetry.lock b/poetry.lock index 1a95f1e572..7c27f9770c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "amqp" @@ -1347,6 +1347,30 @@ files = [ [package.dependencies] djangorestframework = ">=3.9.3" +[[package]] +name = "drf-spectacular" +version = "0.28.0" +description = "Sane and flexible OpenAPI 3 schema generation for Django REST framework" +optional = false +python-versions = ">=3.7" +files = [ + {file = "drf_spectacular-0.28.0-py3-none-any.whl", hash = "sha256:856e7edf1056e49a4245e87a61e8da4baff46c83dbc25be1da2df77f354c7cb4"}, + {file = "drf_spectacular-0.28.0.tar.gz", hash = "sha256:2c778a47a40ab2f5078a7c42e82baba07397bb35b074ae4680721b2805943061"}, +] + +[package.dependencies] +Django = ">=2.2" +djangorestframework = ">=3.10.3" +inflection = ">=0.3.1" +jsonschema = ">=2.6.0" +PyYAML = ">=5.1" +typing-extensions = {version = "*", markers = "python_version < \"3.10\""} +uritemplate = ">=2.0.0" + +[package.extras] +offline = ["drf-spectacular-sidecar"] +sidecar = ["drf-spectacular-sidecar"] + [[package]] name = "edx-api-client" version = "1.9.0" @@ -1775,6 +1799,17 @@ files = [ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] +[[package]] +name = "inflection" +version = "0.5.1" +description = "A port of Ruby on Rails inflector to Python" +optional = false +python-versions = ">=3.5" +files = [ + {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, + {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -1883,6 +1918,41 @@ files = [ {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] +[[package]] +name = "jsonschema" +version = "4.23.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.9" +files = [ + {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, + {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + [[package]] name = "jwcrypto" version = "1.5.6" @@ -3346,6 +3416,22 @@ async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2 hiredis = ["hiredis (>=1.0.0)"] ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] +[[package]] +name = "referencing" +version = "0.36.2" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, + {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" +typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} + [[package]] name = "requests" version = "2.32.3" @@ -3404,6 +3490,118 @@ urllib3 = ">=1.25.10,<3.0" [package.extras] tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] +[[package]] +name = "rpds-py" +version = "0.22.3" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "rpds_py-0.22.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967"}, + {file = "rpds_py-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec"}, + {file = "rpds_py-0.22.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c"}, + {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09"}, + {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00"}, + {file = "rpds_py-0.22.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf"}, + {file = "rpds_py-0.22.3-cp310-cp310-win32.whl", hash = "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652"}, + {file = "rpds_py-0.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8"}, + {file = "rpds_py-0.22.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f"}, + {file = "rpds_py-0.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1"}, + {file = "rpds_py-0.22.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d"}, + {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648"}, + {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74"}, + {file = "rpds_py-0.22.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a"}, + {file = "rpds_py-0.22.3-cp311-cp311-win32.whl", hash = "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64"}, + {file = "rpds_py-0.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c"}, + {file = "rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e"}, + {file = "rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15"}, + {file = "rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059"}, + {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e"}, + {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61"}, + {file = "rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7"}, + {file = "rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627"}, + {file = "rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4"}, + {file = "rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84"}, + {file = "rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518"}, + {file = "rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd"}, + {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2"}, + {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16"}, + {file = "rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f"}, + {file = "rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de"}, + {file = "rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9"}, + {file = "rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b"}, + {file = "rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3"}, + {file = "rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130"}, + {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c"}, + {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b"}, + {file = "rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333"}, + {file = "rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730"}, + {file = "rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf"}, + {file = "rpds_py-0.22.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea"}, + {file = "rpds_py-0.22.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543"}, + {file = "rpds_py-0.22.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d"}, + {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99"}, + {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831"}, + {file = "rpds_py-0.22.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520"}, + {file = "rpds_py-0.22.3-cp39-cp39-win32.whl", hash = "sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9"}, + {file = "rpds_py-0.22.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe"}, + {file = "rpds_py-0.22.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7"}, + {file = "rpds_py-0.22.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6"}, + {file = "rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d"}, +] + [[package]] name = "rsa" version = "4.9" @@ -4064,4 +4262,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "3.9.18" -content-hash = "be43cbed4b535ae9d5c4c4ca0ac035f11645bd4671ed77e21d4b1d71392e75be" +content-hash = "e66a107ecd73cbfb372eea4f3fd2891da59b0b9d609b8b3f93f6fd7804d17f21" diff --git a/pyproject.toml b/pyproject.toml index 15703d8f8c..9700ba35f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,7 @@ django-viewflow = "^2.2.7" django-reversion = "^5.1.0" django-filter = "^24.3" wagtail = "^6.2.1" +drf-spectacular = "^0.28.0" [tool.poetry.group.dev.dependencies] diff --git a/schema.yml b/schema.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scripts/generate_openapi.sh b/scripts/generate_openapi.sh new file mode 100755 index 0000000000..562cc3db5c --- /dev/null +++ b/scripts/generate_openapi.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -eo pipefail + +if [ -z "$(which docker)" ]; then + echo "Error: Docker must be available in order to run this script" + exit 1 +fi + +################################################## +# Generate OpenAPI Schema +################################################## +docker compose run --no-deps --rm web \ + ./manage.py generate_openapi_spec + +################################################## +# Generate API Client +################################################## + +GENERATOR_VERSION=v7.2.0 + +docker run --rm -v "${PWD}:/local" -w /local openapitools/openapi-generator-cli:${GENERATOR_VERSION} \ + generate -c scripts/openapi-configs/typescript-axios-v0.yaml + +# We expect pre-commit to exit with a non-zero status since it is reformatting +# the generated code. +git ls-files frontends/api/src/generated | xargs pre-commit run --files || + echo "OpenAPI generation complete." diff --git a/scripts/openapi-configs/templates/modelEnum.mustache b/scripts/openapi-configs/templates/modelEnum.mustache new file mode 100644 index 0000000000..0d5e27985a --- /dev/null +++ b/scripts/openapi-configs/templates/modelEnum.mustache @@ -0,0 +1,25 @@ +/** + * {{{description}}} + * @export + * @enum {string} + */ +{{#isBoolean}} +export type {{classname}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}} +{{/isBoolean}} + +export const {{classname}}Descriptions = { +{{#allowableValues}} + {{#enumVars}} + {{{value}}}: "{{{enumDescription}}}", + {{/enumVars}} +{{/allowableValues}} +} as const; + +{{^isBoolean}} +{{^stringEnums}} +{{>modelObjectEnum}} +{{/stringEnums}} +{{#stringEnums}} +{{>modelStringEnum}} +{{/stringEnums}} +{{/isBoolean}} diff --git a/scripts/openapi-configs/typescript-axios-v0.yaml b/scripts/openapi-configs/typescript-axios-v0.yaml new file mode 100644 index 0000000000..b24c660b0b --- /dev/null +++ b/scripts/openapi-configs/typescript-axios-v0.yaml @@ -0,0 +1,9 @@ +generatorName: typescript-axios +outputDir: frontends/api/src/generated/v0 +inputSpec: openapi/specs/v0.yaml +ignoreFileOverride: frontends/api/.openapi-generator-ignore +templateDir: scripts/openapi-configs/templates +additionalProperties: + paramNaming: original + useSingleRequestParameter: true + supportsES6: false diff --git a/users/migrations/0025_alter_userprofile_company_size.py b/users/migrations/0025_alter_userprofile_company_size.py new file mode 100644 index 0000000000..29460395b4 --- /dev/null +++ b/users/migrations/0025_alter_userprofile_company_size.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.18 on 2025-02-19 18:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0024_rename_changeemailrequest_expires_on_confirmed_code_users_chang_expires_dbd4e5_idx'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='company_size', + field=models.IntegerField(blank=True, choices=[('1', 'Small/Start-up (1+ employees)'), ('9', 'Small/Home office (1-9 employees)'), ('99', 'Small (10-99 employees)'), ('999', 'Small to medium-sized (100-999 employees)'), ('9999', 'Medium-sized (1000-9999 employees)'), ('10000', 'Large Enterprise (10,000+ employees)'), ('0', "Other (N/A or Don't know)")], null=True), + ), + ] diff --git a/users/migrations/0026_alter_userprofile_years_experience.py b/users/migrations/0026_alter_userprofile_years_experience.py new file mode 100644 index 0000000000..6fc9774aa5 --- /dev/null +++ b/users/migrations/0026_alter_userprofile_years_experience.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.18 on 2025-02-19 18:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0025_alter_userprofile_company_size'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='years_experience', + field=models.IntegerField(blank=True, choices=[('2', 'Less than 2 years'), ('5', '2-5 years'), ('10', '6 - 10 years'), ('15', '11 - 15 years'), ('20', '16 - 20 years'), ('21', 'More than 20 years'), ('0', 'Prefer not to say')], null=True), + ), + ] diff --git a/users/models.py b/users/models.py index 2214137427..520288aa5b 100644 --- a/users/models.py +++ b/users/models.py @@ -2,6 +2,7 @@ import uuid from datetime import timedelta +from enum import Enum import pycountry from django.conf import settings @@ -14,6 +15,7 @@ from mitol.common.models import TimestampedModel from mitol.common.utils import now_in_utc + from cms.constants import CMS_EDITORS_GROUP_NAME # Defined in edX Profile model @@ -118,6 +120,28 @@ (0, "Other (N/A or Don't know)"), ) +class CompanySizeEnum(str, Enum): + SMALL_STARTUP = "1" + SMALL_HOME = "9" + SMALL = "99" + SMALL_MEDIUM = "999" + MEDIUM = "9999" + LARGE = "10000" + OTHER = "0" + + @property + def description(self): + descriptions = { + self.SMALL_STARTUP: "Small/Start-up (1+ employees)", + self.SMALL_HOME: "Small/Home office (1-9 employees)", + self.SMALL: "Small (10-99 employees)", + self.SMALL_MEDIUM: "Small to medium-sized (100-999 employees)", + self.MEDIUM: "Medium-sized (1000-9999 employees)", + self.LARGE: "Large Enterprise (10,000+ employees)", + self.OTHER: "Other (N/A or Don't know)" + } + return descriptions[self.value] + YRS_EXPERIENCE_CHOICES = ( (None, "----"), (2, "Less than 2 years"), @@ -129,6 +153,28 @@ (0, "Prefer not to say"), ) +class YearsExperienceEnum(str, Enum): + LESS_THAN_2 = "2" + TWO_TO_FIVE = "5" + SIX_TO_TEN = "10" + ELEVEN_TO_FIFTEEN = "15" + SIXTEEN_TO_TWENTY = "20" + MORE_THAN_TWENTY = "21" + PREFER_NOT_TO_SAY = "0" + + @property + def description(self): + descriptions = { + self.LESS_THAN_2: "Less than 2 years", + self.TWO_TO_FIVE: "2-5 years", + self.SIX_TO_TEN: "6 - 10 years", + self.ELEVEN_TO_FIFTEEN: "11 - 15 years", + self.SIXTEEN_TO_TWENTY: "16 - 20 years", + self.MORE_THAN_TWENTY: "More than 20 years", + self.PREFER_NOT_TO_SAY: "Prefer not to say" + } + return descriptions[self.value] + HIGHEST_EDUCATION_CHOICES = ( (None, "----"), ("Doctorate", "Doctorate"), @@ -410,10 +456,10 @@ class UserProfile(TimestampedModel): industry = models.CharField(max_length=60, blank=True, null=True, default="") # noqa: DJ001 job_function = models.CharField(max_length=60, blank=True, null=True, default="") # noqa: DJ001 company_size = models.IntegerField( - null=True, blank=True, choices=COMPANY_SIZE_CHOICES + null=True, blank=True, choices=[(e.value, e.description) for e in CompanySizeEnum] ) years_experience = models.IntegerField( - null=True, blank=True, choices=YRS_EXPERIENCE_CHOICES + null=True, blank=True, choices=[(e.value, e.description) for e in YearsExperienceEnum] ) leadership_level = models.CharField( # noqa: DJ001 max_length=60, null=True, blank=True, default="" From be083e8139d4fe20da6a1f9d729d3a833f3581fb Mon Sep 17 00:00:00 2001 From: CP Date: Thu, 20 Feb 2025 14:35:37 -0500 Subject: [PATCH 2/7] warning and error fixes --- ecommerce/serializers.py | 3 + ecommerce/views/v0/__init__.py | 51 +++++++++++- main/settings.py | 2 +- openapi/specs/{v1.yaml => v0.yaml} | 126 +++++++++++++++++++++++++---- openapi/urls.py | 2 +- users/views.py | 9 ++- 6 files changed, 169 insertions(+), 24 deletions(-) rename openapi/specs/{v1.yaml => v0.yaml} (98%) diff --git a/ecommerce/serializers.py b/ecommerce/serializers.py index 2114b5e1ef..1b78cf449d 100644 --- a/ecommerce/serializers.py +++ b/ecommerce/serializers.py @@ -6,6 +6,8 @@ import pytz from rest_framework import serializers +from typing import List +from drf_spectacular.utils import extend_schema_field from cms.serializers import CoursePageSerializer from courses.models import Course, CourseRun, ProgramRun @@ -147,6 +149,7 @@ class BasketSerializer(serializers.ModelSerializer): basket_items = serializers.SerializerMethodField() + @extend_schema_field(BasketItemSerializer(many=True)) def get_basket_items(self, instance): """Get items in the basket""" return [ diff --git a/ecommerce/views/v0/__init__.py b/ecommerce/views/v0/__init__.py index f41fa312e2..b068eb96f4 100644 --- a/ecommerce/views/v0/__init__.py +++ b/ecommerce/views/v0/__init__.py @@ -35,6 +35,7 @@ ViewSet, ) from rest_framework_extensions.mixins import NestedViewSetMixin +from drf_spectacular.utils import extend_schema, OpenApiParameter from courses.models import Course, CourseRun, Program, ProgramRun from ecommerce import api @@ -187,7 +188,17 @@ def get_queryset(self): .prefetch_related("purchasable_object") ) - +@extend_schema( + parameters=[ + OpenApiParameter( + name="username", + type=str, + location=OpenApiParameter.PATH, + description="Username of the basket owner", + required=True + ) + ] +) class BasketViewSet( NestedViewSetMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet ): @@ -205,7 +216,24 @@ def get_object(self, username=None): # noqa: ARG002 def get_queryset(self): return Basket.objects.filter(user=self.request.user).all() - +@extend_schema( + parameters=[ + OpenApiParameter( + name="parent_lookup_basket", + type=int, + location=OpenApiParameter.PATH, + description="ID of the basket", + required=True + ), + OpenApiParameter( + name="id", + type=int, + location=OpenApiParameter.PATH, + description="ID of the basket item", + required=True + ) + ] +) class BasketItemViewSet( NestedViewSetMixin, ListCreateAPIView, mixins.DestroyModelMixin, GenericViewSet ): @@ -230,7 +258,24 @@ def create(self, request, *args, **kwargs): # noqa: ARG002 serializer.data, status=status.HTTP_201_CREATED, headers=headers ) - +@extend_schema( + parameters=[ + OpenApiParameter( + name="parent_lookup_basket", + type=int, + location=OpenApiParameter.PATH, + description="ID of the basket", + required=True + ), + OpenApiParameter( + name="id", + type=int, + location=OpenApiParameter.PATH, + description="ID of the basket discount", + required=True + ) + ] +) class BasketDiscountViewSet(ReadOnlyModelViewSet): """Applied basket discounts""" diff --git a/main/settings.py b/main/settings.py index f621d1b454..aacfd8ff75 100644 --- a/main/settings.py +++ b/main/settings.py @@ -952,7 +952,7 @@ "DEFAULT_VERSIONING": "rest_framework.versioning.NamespaceVersioning", "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", "ALLOWED_VERSIONS": [ - "v1" + "v0" ], } diff --git a/openapi/specs/v1.yaml b/openapi/specs/v0.yaml similarity index 98% rename from openapi/specs/v1.yaml rename to openapi/specs/v0.yaml index 7d32651131..c04c5b8bcd 100644 --- a/openapi/specs/v1.yaml +++ b/openapi/specs/v0.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: MITx Online API - version: 0.0.1 (v1) + version: 0.0.1 (v0) description: MIT public API paths: /.well-known/openid-configuration: @@ -27,6 +27,13 @@ paths: get: operationId: api_baskets_list description: API view set for Basket + parameters: + - in: path + name: username + schema: + type: string + description: Username of the basket owner + required: true tags: - api responses: @@ -43,10 +50,17 @@ paths: operationId: api_baskets_discounts_list description: Applied basket discounts parameters: + - in: path + name: id + schema: + type: integer + description: ID of the basket discount + required: true - in: path name: parent_lookup_basket schema: - type: string + type: integer + description: ID of the basket required: true tags: - api @@ -67,12 +81,14 @@ paths: - in: path name: id schema: - type: string + type: integer + description: ID of the basket discount required: true - in: path name: parent_lookup_basket schema: - type: string + type: integer + description: ID of the basket required: true tags: - api @@ -88,10 +104,17 @@ paths: operationId: api_baskets_items_list description: API view set for BasketItem parameters: + - in: path + name: id + schema: + type: integer + description: ID of the basket item + required: true - in: path name: parent_lookup_basket schema: - type: string + type: integer + description: ID of the basket required: true tags: - api @@ -108,10 +131,17 @@ paths: operationId: api_baskets_items_create description: API view set for BasketItem parameters: + - in: path + name: id + schema: + type: integer + description: ID of the basket item + required: true - in: path name: parent_lookup_basket schema: - type: string + type: integer + description: ID of the basket required: true tags: - api @@ -142,12 +172,14 @@ paths: - in: path name: id schema: - type: string + type: integer + description: ID of the basket item required: true - in: path name: parent_lookup_basket schema: - type: string + type: integer + description: ID of the basket required: true tags: - api @@ -163,6 +195,7 @@ paths: name: username schema: type: string + description: Username of the basket owner required: true tags: - api @@ -210,9 +243,25 @@ paths: required: true tags: - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeEmailRequestUpdateRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ChangeEmailRequestUpdateRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/ChangeEmailRequestUpdateRequest' + required: true responses: '200': - description: No response body + content: + application/json: + schema: + $ref: '#/components/schemas/ChangeEmailRequestUpdate' + description: '' patch: operationId: api_change_emails_partial_update description: Viewset for creating and updating email change requests @@ -2951,6 +3000,13 @@ paths: get: operationId: baskets_list description: API view set for Basket + parameters: + - in: path + name: username + schema: + type: string + description: Username of the basket owner + required: true tags: - baskets responses: @@ -2967,10 +3023,17 @@ paths: operationId: baskets_discounts_list description: Applied basket discounts parameters: + - in: path + name: id + schema: + type: integer + description: ID of the basket discount + required: true - in: path name: parent_lookup_basket schema: - type: string + type: integer + description: ID of the basket required: true tags: - baskets @@ -2991,12 +3054,14 @@ paths: - in: path name: id schema: - type: string + type: integer + description: ID of the basket discount required: true - in: path name: parent_lookup_basket schema: - type: string + type: integer + description: ID of the basket required: true tags: - baskets @@ -3012,10 +3077,17 @@ paths: operationId: baskets_items_list description: API view set for BasketItem parameters: + - in: path + name: id + schema: + type: integer + description: ID of the basket item + required: true - in: path name: parent_lookup_basket schema: - type: string + type: integer + description: ID of the basket required: true tags: - baskets @@ -3032,10 +3104,17 @@ paths: operationId: baskets_items_create description: API view set for BasketItem parameters: + - in: path + name: id + schema: + type: integer + description: ID of the basket item + required: true - in: path name: parent_lookup_basket schema: - type: string + type: integer + description: ID of the basket required: true tags: - baskets @@ -3066,12 +3145,14 @@ paths: - in: path name: id schema: - type: string + type: integer + description: ID of the basket item required: true - in: path name: parent_lookup_basket schema: - type: string + type: integer + description: ID of the basket required: true tags: - baskets @@ -3087,6 +3168,7 @@ paths: name: username schema: type: string + description: Username of the basket owner required: true tags: - baskets @@ -5735,7 +5817,9 @@ components: user: type: integer basket_items: - type: string + type: array + items: + $ref: '#/components/schemas/BasketItem' readOnly: true required: - basket_items @@ -5817,6 +5901,14 @@ components: type: boolean required: - confirmed + ChangeEmailRequestUpdateRequest: + type: object + description: Serializer for confirming a user email change + properties: + confirmed: + type: boolean + required: + - confirmed CompanySizeEnum: enum: - '1' diff --git a/openapi/urls.py b/openapi/urls.py index 5b90b78630..b2b9c062f5 100644 --- a/openapi/urls.py +++ b/openapi/urls.py @@ -11,7 +11,7 @@ urlpatterns = [ path( - "api/v0/schema/", SpectacularAPIView.as_view(api_version="v0"), name="v0_schema" + "api/v0/schema/", SpectacularAPIView.as_view(api_version="v1"), name="v0_schema" ), path( "api/v0/schema/swagger-ui/", diff --git a/users/views.py b/users/views.py index 02072db22f..b804bbd181 100644 --- a/users/views.py +++ b/users/views.py @@ -80,10 +80,15 @@ def get_queryset(self): ) def get_serializer_class(self): - if self.action == "create": # noqa: RET503 + """ + Return the appropriate serializer class based on the action + """ + if self.action == "create": return ChangeEmailRequestCreateSerializer - elif self.action == "partial_update": + elif self.action in ["update", "partial_update"]: return ChangeEmailRequestUpdateSerializer + # Default case + return ChangeEmailRequestCreateSerializer class CountriesStatesViewSet(viewsets.ViewSet): From 20e940b784a2b5e38c154dd63e49c069f5e287b1 Mon Sep 17 00:00:00 2001 From: CP Date: Fri, 21 Feb 2025 11:29:02 -0500 Subject: [PATCH 3/7] warning and error fixes --- courses/serializers/v1/courses.py | 55 ++++- ecommerce/serializers.py | 63 ++++-- ecommerce/views/v0/__init__.py | 93 ++++---- openapi/specs/v0.yaml | 340 +++++++++++++++++++++--------- users/serializers.py | 13 +- users/views.py | 12 +- 6 files changed, 425 insertions(+), 151 deletions(-) diff --git a/courses/serializers/v1/courses.py b/courses/serializers/v1/courses.py index 5bed970016..9d615d7866 100644 --- a/courses/serializers/v1/courses.py +++ b/courses/serializers/v1/courses.py @@ -1,6 +1,7 @@ from mitol.olposthog.features import is_enabled from rest_framework import serializers from rest_framework.exceptions import ValidationError +from drf_spectacular.utils import extend_schema from cms.serializers import CoursePageSerializer from courses import models @@ -15,6 +16,8 @@ from flexiblepricing.api import is_courseware_flexible_price_approved from main import features from openedx.constants import EDX_ENROLLMENT_AUDIT_MODE, EDX_ENROLLMENT_VERIFIED_MODE +from typing import Optional, List +from drf_spectacular.utils import extend_schema_field, inline_serializer class CourseSerializer(BaseCourseSerializer): @@ -110,18 +113,66 @@ class Meta: "courseruns", ] - class CourseRunWithCourseSerializer(CourseRunSerializer): """ CourseRun model serializer - also serializes the parent Course. """ course = CourseSerializer(read_only=True, context={"include_page_fields": True}) + courseware_url = serializers.SerializerMethodField() + is_upgradable = serializers.SerializerMethodField() + is_enrollable = serializers.SerializerMethodField() + is_archived = serializers.SerializerMethodField() + course_number = serializers.SerializerMethodField() + products = ProductRelatedField( + many=True, + read_only=True, + help_text="List of products associated with this course run" + ) + + @extend_schema_field(serializers.URLField) + def get_courseware_url(self, instance) -> Optional[str]: + """Get the full URL for this CourseRun in the courseware""" + return ( + edx_redirect_url(instance.courseware_url_path) + if instance.courseware_url_path + else None + ) + + @extend_schema_field(bool) + def get_is_upgradable(self, instance) -> bool: + """Check if the course run is upgradable""" + return instance.is_upgradable + + @extend_schema_field(bool) + def get_is_enrollable(self, instance) -> bool: + """Check if the course run is enrollable""" + return instance.is_enrollable + + @extend_schema_field(bool) + def get_is_archived(self, instance) -> bool: + """Check if the course run is archived""" + return instance.is_enrollable and instance.is_past + + @extend_schema_field(str) + def get_course_number(self, instance) -> str: + """Get the course number""" + return instance.course_number + + def get_products(self, instance) -> List[dict]: + """Get products associated with this course run""" + return super().get_products(instance) class Meta: model = models.CourseRun - fields = CourseRunSerializer.Meta.fields + [ # noqa: RUF005 + fields = CourseRunSerializer.Meta.fields + [ "course", + "courseware_url", + "is_upgradable", + "is_enrollable", + "is_archived", + "course_number", + "products" ] diff --git a/ecommerce/serializers.py b/ecommerce/serializers.py index 1b78cf449d..60b98b50c5 100644 --- a/ecommerce/serializers.py +++ b/ecommerce/serializers.py @@ -6,7 +6,7 @@ import pytz from rest_framework import serializers -from typing import List +from typing import Dict, List from drf_spectacular.utils import extend_schema_field from cms.serializers import CoursePageSerializer @@ -20,7 +20,7 @@ PAYMENT_TYPES, TRANSACTION_TYPE_REFUND, ) -from ecommerce.models import Basket, BasketItem, Order, Product +from ecommerce.models import Basket, BasketItem, Order, Product, BasketDiscount from flexiblepricing.api import determine_courseware_flexible_price_discount from main.settings import TIME_ZONE from users.serializers import ExtendedLegalAddressSerializer @@ -197,35 +197,74 @@ class Meta: class BasketWithProductSerializer(serializers.ModelSerializer): + """Serializer for Basket model with product details""" basket_items = serializers.SerializerMethodField() total_price = serializers.SerializerMethodField() discounted_price = serializers.SerializerMethodField() discounts = serializers.SerializerMethodField() - def get_basket_items(self, instance): + @extend_schema_field({ + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'basket': {'type': 'integer'}, + 'product': { + 'type': 'object', + 'properties': { + 'id': {'type': 'integer'}, + 'price': {'type': 'number'}, + 'description': {'type': 'string'}, + 'is_active': {'type': 'boolean'}, + 'purchasable_object': {'type': 'object'} + } + }, + 'id': {'type': 'integer'} + } + } + }) + def get_basket_items(self, instance) -> List[Dict[str, any]]: + """ + Get items in the basket with their associated product details + + Args: + instance: Basket model instance + + Returns: + List of serialized basket items with product details + """ return [ BasketItemWithProductSerializer(instance=basket, context=self.context).data for basket in instance.basket_items.all() ] - def get_total_price(self, instance): + @extend_schema_field(Decimal) + def get_total_price(self, instance) -> Decimal: + """Get total price of all items in basket before discounts""" return sum( basket_item.base_price for basket_item in instance.basket_items.all() ) - def get_discounted_price(self, instance): + @extend_schema_field(Decimal) + def get_discounted_price(self, instance) -> Decimal: + """Get total price after any discounts are applied""" discounts = instance.discounts.all() - if discounts.count() == 0: return self.get_total_price(instance) - return sum( basket_item.discounted_price for basket_item in instance.basket_items.all() ) - def get_discounts(self, instance): + @extend_schema_field(List[BasketDiscountSerializer]) + def get_discounts(self, instance) -> List[Dict[str, any]]: """ Exclude zero value discounts and return applicable discounts on the basket. + + Args: + instance: Basket instance + + Returns: + List of serialized basket discount records """ discounts = [] for discount_record in instance.discounts.all(): @@ -243,15 +282,15 @@ def get_discounts(self, instance): return discounts class Meta: + model = models.Basket fields = [ "id", - "user", + "user", "basket_items", "total_price", - "discounted_price", - "discounts", + "discounted_price", + "discounts" ] - model = models.Basket class LineSerializer(serializers.ModelSerializer): diff --git a/ecommerce/views/v0/__init__.py b/ecommerce/views/v0/__init__.py index b068eb96f4..d4ddf20089 100644 --- a/ecommerce/views/v0/__init__.py +++ b/ecommerce/views/v0/__init__.py @@ -453,16 +453,38 @@ class UserDiscountViewSet(ModelViewSet): pagination_class = RefinePagination +from rest_framework import serializers +from drf_spectacular.utils import extend_schema + +# Add serializers for request/response schemas +class RedeemDiscountRequestSerializer(serializers.Serializer): + """Serializer for discount redemption requests""" + discount = serializers.CharField(required=True) + +class RedeemDiscountResponseSerializer(serializers.Serializer): + """Serializer for discount redemption responses""" + message = serializers.CharField() + code = serializers.CharField() + +class AddToCartRequestSerializer(serializers.Serializer): + """Serializer for add to cart requests""" + product_id = serializers.IntegerField(required=True) + +class AddToCartResponseSerializer(serializers.Serializer): + """Serializer for add to cart responses""" + message = serializers.CharField() + class CheckoutApiViewSet(ViewSet): + """API viewset for checkout operations""" authentication_classes = (SessionAuthentication, TokenAuthentication) permission_classes = (IsAuthenticated,) - @action( - detail=False, - methods=["post"], - name="Redeem Discount", - url_name="redeem_discount", + @extend_schema( + request=RedeemDiscountRequestSerializer, + responses={200: RedeemDiscountResponseSerializer}, + description="Apply a discount code to the current basket" ) + @action(detail=False, methods=["post"], name="Redeem Discount", url_name="redeem_discount") def redeem_discount(self, request): """ API call to redeem a discount. Discounts are attached to the basket so @@ -538,6 +560,11 @@ def redeem_discount(self, request): } ) + @extend_schema( + request=AddToCartRequestSerializer, + responses={200: AddToCartResponseSerializer}, + description="Add a product to the shopping cart" + ) @action( detail=False, methods=["post"], @@ -564,28 +591,13 @@ def add_to_cart(self, request): } ) + @extend_schema( + responses={200: BasketWithProductSerializer}, + description="Get current cart contents" + ) @action( - detail=False, methods=["post"], name="Start Checkout", url_name="start_checkout" + detail=False, methods=["get"], name="Cart Info", url_name="cart" ) - def start_checkout(self, request): - """ - API call to start the checkout process. This assembles the basket items - into an Order with Lines for each item, applies the attached basket - discounts, and then calls the payment gateway to prepare for payment. - - Returns: - - JSON payload from the ol-django payment gateway app. The payment - gateway returns data necessary to construct a form that will - ultimately POST to the actual payment processor. - """ - try: - payload = api.generate_checkout_payload(request) - except ObjectDoesNotExist: - return Response("No basket", status=status.HTTP_406_NOT_ACCEPTABLE) - - return Response(payload) - - @action(detail=False, methods=["get"], name="Cart Info", url_name="cart") def cart(self, request): """ Returns the current cart, with the product info embedded. @@ -702,28 +714,37 @@ def post(self, request, *args, **kwargs): # noqa: ARG002 return self.post_checkout_redirect(order.state, order, request) +from rest_framework import serializers + +# Add a serializer for the cybersource payment response +class CybersourcePaymentResponseSerializer(serializers.Serializer): + """Serializer for Cybersource payment callback responses""" + req_reference_number = serializers.CharField(required=True) + decision = serializers.CharField(required=True) + message = serializers.CharField(required=True) + reason_code = serializers.CharField(required=True) + transaction_id = serializers.CharField(required=True) + +@extend_schema( + request=CybersourcePaymentResponseSerializer, + responses={200: None}, + description="Endpoint for Cybersource server-to-server payment callbacks" +) @method_decorator(csrf_exempt, name="dispatch") class BackofficeCallbackView(APIView): + """API view for processing Cybersource payment callbacks""" authentication_classes = [] # disables authentication permission_classes = [] # disables permission + serializer_class = CybersourcePaymentResponseSerializer - def post(self, request, *args, **kwargs): # noqa: ARG002 + def post(self, request, *args, **kwargs): """ This endpoint is called by Cybersource as a server-to-server call - in order to respond with the payment details. - - Returns: - - HTTP_200_OK if the Order is found. - - Raises: - - Http404 if the Order is not found. + to respond with the payment details. """ with transaction.atomic(): order = api.get_order_from_cybersource_payment_response(request) - # We only want to process responses related to orders which are PENDING - # otherwise we can conclude that we already received a response through - # the user's browser. if order is None: raise Http404 elif order.state == OrderStatus.PENDING: diff --git a/openapi/specs/v0.yaml b/openapi/specs/v0.yaml index c04c5b8bcd..edd665818a 100644 --- a/openapi/specs/v0.yaml +++ b/openapi/specs/v0.yaml @@ -294,90 +294,102 @@ paths: /api/checkout/add_to_cart/: post: operationId: api_checkout_add_to_cart_create - description: Add product to the cart + description: Add a product to the shopping cart tags: - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AddToCartRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AddToCartRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AddToCartRequestRequest' + required: true responses: '200': - description: No response body + content: + application/json: + schema: + $ref: '#/components/schemas/AddToCartResponse' + description: '' /api/checkout/cart/: get: operationId: api_checkout_cart_retrieve - description: Returns the current cart, with the product info embedded. + description: Get current cart contents tags: - api responses: '200': - description: No response body + content: + application/json: + schema: + $ref: '#/components/schemas/BasketWithProduct' + description: '' /api/checkout/redeem_discount/: post: operationId: api_checkout_redeem_discount_create - description: |- - API call to redeem a discount. Discounts are attached to the basket so - they can be attached at any time in the checkout process. Later on, - they'll be converted to attach to the resulting Order (as Baskets are - ephemeral). - - Discount application is subject to these rules: - - The discount can't be flagged for use with flexible pricing. - - If the discount is tied to a product, the product must already be in - the basket. - - POST Args: - - discount (str): Discount Code to apply - - Returns: - - Success message on success - - HTTP 406 if there's no basket yet - - HTTP 404 if the discount isn't found + description: Apply a discount code to the current basket tags: - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RedeemDiscountRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RedeemDiscountRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RedeemDiscountRequestRequest' + required: true responses: '200': - description: No response body + content: + application/json: + schema: + $ref: '#/components/schemas/RedeemDiscountResponse' + description: '' /api/checkout/result/: post: operationId: api_checkout_result_create - description: |- - This endpoint is called by Cybersource as a server-to-server call - in order to respond with the payment details. - - Returns: - - HTTP_200_OK if the Order is found. - - Raises: - - Http404 if the Order is not found. - tags: - - api - responses: - '200': - description: No response body - /api/checkout/start_checkout/: - post: - operationId: api_checkout_start_checkout_create - description: |- - API call to start the checkout process. This assembles the basket items - into an Order with Lines for each item, applies the attached basket - discounts, and then calls the payment gateway to prepare for payment. - - Returns: - - JSON payload from the ol-django payment gateway app. The payment - gateway returns data necessary to construct a form that will - ultimately POST to the actual payment processor. + description: Endpoint for Cybersource server-to-server payment callbacks tags: - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CybersourcePaymentResponseRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CybersourcePaymentResponseRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/CybersourcePaymentResponseRequest' + required: true responses: '200': description: No response body /api/countries/: get: - operationId: api_countries_retrieve + operationId: api_countries_list description: Get generator for countries/states list tags: - api responses: '200': - description: No response body + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Country' + description: '' /api/course_runs/: get: operationId: api_course_runs_list @@ -3182,64 +3194,66 @@ paths: /api/v0/checkout/add_to_cart/: post: operationId: checkout_add_to_cart_create - description: Add product to the cart + description: Add a product to the shopping cart tags: - checkout + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AddToCartRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/AddToCartRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/AddToCartRequestRequest' + required: true responses: '200': - description: No response body + content: + application/json: + schema: + $ref: '#/components/schemas/AddToCartResponse' + description: '' /api/v0/checkout/cart/: get: operationId: checkout_cart_retrieve - description: Returns the current cart, with the product info embedded. + description: Get current cart contents tags: - checkout responses: '200': - description: No response body + content: + application/json: + schema: + $ref: '#/components/schemas/BasketWithProduct' + description: '' /api/v0/checkout/redeem_discount/: post: operationId: checkout_redeem_discount_create - description: |- - API call to redeem a discount. Discounts are attached to the basket so - they can be attached at any time in the checkout process. Later on, - they'll be converted to attach to the resulting Order (as Baskets are - ephemeral). - - Discount application is subject to these rules: - - The discount can't be flagged for use with flexible pricing. - - If the discount is tied to a product, the product must already be in - the basket. - - POST Args: - - discount (str): Discount Code to apply - - Returns: - - Success message on success - - HTTP 406 if there's no basket yet - - HTTP 404 if the discount isn't found - tags: - - checkout - responses: - '200': - description: No response body - /api/v0/checkout/start_checkout/: - post: - operationId: checkout_start_checkout_create - description: |- - API call to start the checkout process. This assembles the basket items - into an Order with Lines for each item, applies the attached basket - discounts, and then calls the payment gateway to prepare for payment. - - Returns: - - JSON payload from the ol-django payment gateway app. The payment - gateway returns data necessary to construct a form that will - ultimately POST to the actual payment processor. + description: Apply a discount code to the current basket tags: - checkout + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RedeemDiscountRequestRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RedeemDiscountRequestRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RedeemDiscountRequestRequest' + required: true responses: '200': - description: No response body + content: + application/json: + schema: + $ref: '#/components/schemas/RedeemDiscountResponse' + description: '' /api/v0/discounts/: get: operationId: discounts_list @@ -5807,6 +5821,22 @@ paths: description: No response body components: schemas: + AddToCartRequestRequest: + type: object + description: Serializer for add to cart requests + properties: + product_id: + type: integer + required: + - product_id + AddToCartResponse: + type: object + description: Serializer for add to cart responses + properties: + message: + type: string + required: + - message Basket: type: object description: Basket model serializer @@ -5866,6 +5896,58 @@ components: required: - basket - product + BasketWithProduct: + type: object + description: Serializer for Basket model with product details + properties: + id: + type: integer + readOnly: true + user: + type: integer + basket_items: + type: array + items: + type: object + properties: + basket: + type: integer + product: + type: object + properties: + id: + type: integer + price: + type: number + description: + type: string + is_active: + type: boolean + purchasable_object: + type: object + id: + type: integer + readOnly: true + total_price: + type: number + format: double + description: Get total price of all items in basket before discounts + readOnly: true + discounted_price: + type: number + format: double + description: Get total price after any discounts are applied + readOnly: true + discounts: + type: string + readOnly: true + required: + - basket_items + - discounted_price + - discounts + - id + - total_price + - user BlankEnum: enum: - '' @@ -5935,6 +6017,25 @@ components: - Medium-sized (1000-9999 employees) - Large Enterprise (10,000+ employees) - Other (N/A or Don't know) + Country: + type: object + description: Serializer for pycountry countries, with states for US/CA + properties: + code: + type: string + description: Get the country alpha_2 code + readOnly: true + name: + type: string + description: Get the country name (common name preferred if available) + readOnly: true + states: + type: string + readOnly: true + required: + - code + - name + - states CountryIncomeThreshold: type: object properties: @@ -6246,6 +6347,7 @@ components: course run on their dashboard. courseware_url: type: string + format: uri readOnly: true courseware_id: type: string @@ -6263,13 +6365,16 @@ components: description: The date beyond which the learner can not enroll in paid course mode. is_upgradable: - type: string + type: boolean + description: Check if the course run is upgradable readOnly: true is_enrollable: - type: string + type: boolean + description: Check if the course run is enrollable readOnly: true is_archived: - type: string + type: boolean + description: Check if the course run is archived readOnly: true is_self_paced: type: boolean @@ -6285,12 +6390,14 @@ components: type: boolean course_number: type: string + description: Get the course number readOnly: true products: type: array items: type: string readOnly: true + description: List of products associated with this course run approved_flexible_price_exists: type: string readOnly: true @@ -6470,6 +6577,31 @@ components: minLength: 1 required: - email + CybersourcePaymentResponseRequest: + type: object + description: Serializer for Cybersource payment callback responses + properties: + req_reference_number: + type: string + minLength: 1 + decision: + type: string + minLength: 1 + message: + type: string + minLength: 1 + reason_code: + type: string + minLength: 1 + transaction_id: + type: string + minLength: 1 + required: + - decision + - message + - reason_code + - req_reference_number + - transaction_id Department: type: object description: Department model serializer @@ -7942,6 +8074,26 @@ components: - id - updated_on - username + RedeemDiscountRequestRequest: + type: object + description: Serializer for discount redemption requests + properties: + discount: + type: string + minLength: 1 + required: + - discount + RedeemDiscountResponse: + type: object + description: Serializer for discount redemption responses + properties: + message: + type: string + code: + type: string + required: + - code + - message RedemptionTypeEnum: enum: - one-time diff --git a/users/serializers.py b/users/serializers.py index df33adf3b7..ebc978cd32 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -10,6 +10,8 @@ from rest_framework import serializers from rest_framework.validators import UniqueValidator from social_django.models import UserSocialAuth +from typing import List +from drf_spectacular.utils import extend_schema_field from hubspot_sync.task_helpers import sync_hubspot_user @@ -483,21 +485,24 @@ class CountrySerializer(serializers.Serializer): name = serializers.SerializerMethodField() states = serializers.SerializerMethodField() - def get_code(self, instance): + @extend_schema_field(str) + def get_code(self, instance) -> str: """Get the country alpha_2 code""" return instance.alpha_2 - def get_name(self, instance): + @extend_schema_field(str) + def get_name(self, instance) -> str: """Get the country name (common name preferred if available)""" if hasattr(instance, "common_name"): return instance.common_name return instance.name - def get_states(self, instance): + @extend_schema_field(List[StateProvinceSerializer]) + def get_states(self, instance) -> List[dict]: """Get a list of states/provinces if USA or Canada""" if instance.alpha_2 in ("US", "CA"): return StateProvinceSerializer( - instance=sorted( # noqa: C414 + instance=sorted( list(pycountry.subdivisions.get(country_code=instance.alpha_2)), key=lambda state: state.name, ), diff --git a/users/views.py b/users/views.py index b804bbd181..82d0ef750b 100644 --- a/users/views.py +++ b/users/views.py @@ -9,6 +9,7 @@ from rest_framework.filters import SearchFilter from rest_framework.permissions import IsAdminUser, IsAuthenticated from rest_framework.response import Response +from rest_framework.generics import GenericAPIView from hubspot_sync.task_helpers import sync_hubspot_user from main.permissions import UserIsOwnerPermission @@ -91,15 +92,20 @@ def get_serializer_class(self): return ChangeEmailRequestCreateSerializer -class CountriesStatesViewSet(viewsets.ViewSet): +class CountriesStatesViewSet(viewsets.GenericViewSet, GenericAPIView): """Retrieve viewset of countries, with states/provinces for US and Canada""" permission_classes = [] + serializer_class = CountrySerializer + + def get_queryset(self): + """Get list of countries ordered by name""" + return sorted(list(pycountry.countries), key=lambda country: country.name) def list(self, request): # pylint:disable=unused-argument # noqa: ARG002 """Get generator for countries/states list""" - queryset = sorted(list(pycountry.countries), key=lambda country: country.name) # noqa: C414 - serializer = CountrySerializer(queryset, many=True) + queryset = self.get_queryset() + serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) From 609d4d8af78312d64bf31676ca501e20c58599f0 Mon Sep 17 00:00:00 2001 From: CP Date: Fri, 21 Feb 2025 12:44:05 -0500 Subject: [PATCH 4/7] some more warning fixes --- cms/serializers.py | 14 ++++++++++++++ courses/serializers/v1/courses.py | 3 +++ openapi/specs/v0.yaml | 18 ++++++++++++------ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/cms/serializers.py b/cms/serializers.py index 78291b67e1..46a10ab6f9 100644 --- a/cms/serializers.py +++ b/cms/serializers.py @@ -3,6 +3,7 @@ import bleach from django.templatetags.static import static from rest_framework import serializers +from drf_spectacular.utils import extend_schema_field from cms import models from cms.api import get_wagtail_img_src @@ -19,6 +20,7 @@ class BaseCoursePageSerializer(serializers.ModelSerializer): effort = serializers.SerializerMethodField() length = serializers.SerializerMethodField() + @extend_schema_field(str) def get_feature_image_src(self, instance): """Serializes the source of the feature_image""" feature_img_src = None @@ -27,12 +29,15 @@ def get_feature_image_src(self, instance): return feature_img_src or static(DEFAULT_COURSE_IMG_PATH) + @extend_schema_field(serializers.URLField) def get_page_url(self, instance): return instance.get_url() + @extend_schema_field(str) def get_description(self, instance): return bleach.clean(instance.description, tags=[], strip=True) + @extend_schema_field(str) def get_effort(self, instance): return ( bleach.clean(instance.effort, tags=[], strip=True) @@ -40,6 +45,7 @@ def get_effort(self, instance): else None ) + @extend_schema_field(str) def get_length(self, instance): return ( bleach.clean(instance.length, tags=[], strip=True) @@ -66,6 +72,7 @@ class CoursePageSerializer(BaseCoursePageSerializer): instructors = serializers.SerializerMethodField() current_price = serializers.SerializerMethodField() + @extend_schema_field(serializers.URLField) def get_financial_assistance_form_url(self, instance): """ Returns URL of the Financial Assistance Form. @@ -116,6 +123,7 @@ def get_financial_assistance_form_url(self, instance): else "" ) + @extend_schema_field(int) def get_current_price(self, instance): relevant_product = ( instance.product.active_products.filter().order_by("-price").first() @@ -124,6 +132,7 @@ def get_current_price(self, instance): ) return relevant_product.price if relevant_product else None + @extend_schema_field(list) def get_instructors(self, instance): members = [ member.linked_instructor_page @@ -160,6 +169,7 @@ class ProgramPageSerializer(serializers.ModelSerializer): price = serializers.SerializerMethodField() financial_assistance_form_url = serializers.SerializerMethodField() + @extend_schema_field(str) def get_feature_image_src(self, instance): """Serializes the source of the feature_image""" feature_img_src = None @@ -168,12 +178,15 @@ def get_feature_image_src(self, instance): return feature_img_src or static(DEFAULT_COURSE_IMG_PATH) + @extend_schema_field(serializers.URLField) def get_page_url(self, instance): return instance.get_url() + @extend_schema_field(str) def get_price(self, instance): return instance.price[0].value["text"] if len(instance.price) > 0 else None + @extend_schema_field(serializers.URLField) def get_financial_assistance_form_url(self, instance): """ Returns URL of the Financial Assistance Form. @@ -235,6 +248,7 @@ class InstructorPageSerializer(serializers.ModelSerializer): feature_image_src = serializers.SerializerMethodField() + @extend_schema_field(str) def get_feature_image_src(self, instance): """Serializes the source of the feature_image""" feature_img_src = None diff --git a/courses/serializers/v1/courses.py b/courses/serializers/v1/courses.py index 9d615d7866..98d3cbe68d 100644 --- a/courses/serializers/v1/courses.py +++ b/courses/serializers/v1/courses.py @@ -28,6 +28,7 @@ class CourseSerializer(BaseCourseSerializer): page = CoursePageSerializer(read_only=True) programs = serializers.SerializerMethodField() + @extend_schema_field(int) def get_next_run_id(self, instance): """Get next run id""" run = instance.first_unexpired_run @@ -85,6 +86,7 @@ def to_representation(self, instance): } return data + @extend_schema_field(bool) def get_approved_flexible_price_exists(self, instance): # Get the User object if it exists. user = self.context["request"].user if "request" in self.context else None @@ -159,6 +161,7 @@ def get_course_number(self, instance) -> str: """Get the course number""" return instance.course_number + @extend_schema_field(ProductRelatedField) def get_products(self, instance) -> List[dict]: """Get products associated with this course run""" return super().get_products(instance) diff --git a/openapi/specs/v0.yaml b/openapi/specs/v0.yaml index edd665818a..ca22792d30 100644 --- a/openapi/specs/v0.yaml +++ b/openapi/specs/v0.yaml @@ -6082,7 +6082,8 @@ components: pattern: ^[\w\-+:\.]+$ maxLength: 255 next_run_id: - type: string + type: integer + description: Get next run id readOnly: true departments: type: array @@ -6110,9 +6111,11 @@ components: properties: feature_image_src: type: string + description: Serializes the source of the feature_image readOnly: true page_url: type: string + format: uri readOnly: true description: type: string @@ -6128,12 +6131,14 @@ components: readOnly: true financial_assistance_form_url: type: string + format: uri readOnly: true current_price: - type: string + type: integer readOnly: true instructors: - type: string + type: array + items: {} readOnly: true required: - current_price @@ -6248,7 +6253,7 @@ components: type: string readOnly: true approved_flexible_price_exists: - type: string + type: boolean readOnly: true required: - approved_flexible_price_exists @@ -6399,7 +6404,7 @@ components: readOnly: true description: List of products associated with this course run approved_flexible_price_exists: - type: string + type: boolean readOnly: true course: allOf: @@ -6503,7 +6508,8 @@ components: pattern: ^[\w\-+:\.]+$ maxLength: 255 next_run_id: - type: string + type: integer + description: Get next run id readOnly: true departments: type: array From 0a7a0e8f58d73f35f693605097d98226f3f00411 Mon Sep 17 00:00:00 2001 From: CP Date: Mon, 24 Feb 2025 11:46:02 -0500 Subject: [PATCH 5/7] commit --- authentication/views.py | 29 +- courses/serializers/v1/base.py | 40 +- courses/serializers/v1/courses.py | 14 +- courses/serializers/v1/programs.py | 5 +- courses/serializers/v2/courses.py | 18 +- courses/serializers/v2/departments.py | 3 + courses/views/v1/__init__.py | 7 + ecommerce/serializers.py | 12 + ecommerce/views/v0/__init__.py | 37 +- openapi/specs/v0.yaml | 648 ++++++++++++++++---------- users/serializers.py | 8 +- 11 files changed, 543 insertions(+), 278 deletions(-) diff --git a/authentication/views.py b/authentication/views.py index 72dec89ca6..0d867824f3 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -9,6 +9,7 @@ from django.shortcuts import render, reverse from rest_framework import status from rest_framework.decorators import api_view, permission_classes, renderer_classes +from rest_framework.generics import GenericAPIView from rest_framework.permissions import IsAuthenticated from rest_framework.renderers import JSONRenderer from rest_framework.response import Response @@ -105,13 +106,37 @@ def post(self, request): return super().post(request) -class RegisterConfirmView(SocialAuthAPIView): +class RegisterConfirmView(SocialAuthAPIView, GenericAPIView): """Email registration confirmation view""" + + serializer_class = RegisterConfirmSerializer + permission_classes = [] + authentication_classes = [] def get_serializer_cls(self): - """Return the serializer cls""" + """Return the serializer class""" return RegisterConfirmSerializer + def post(self, request): + """ + Handle POST requests to confirm email registration + """ + if bool(request.session.get("hijack_history")): + return Response(status=status.HTTP_403_FORBIDDEN) + + serializer_cls = self.get_serializer_cls() + strategy = load_drf_strategy(request) + backend = load_backend(strategy, EmailAuth.name, None) + serializer = serializer_cls( + data=request.data, + context={"request": request, "strategy": strategy, "backend": backend}, + ) + + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + class RegisterDetailsView(SocialAuthAPIView): """Email registration details view""" diff --git a/courses/serializers/v1/base.py b/courses/serializers/v1/base.py index af83628623..733baaf606 100644 --- a/courses/serializers/v1/base.py +++ b/courses/serializers/v1/base.py @@ -1,4 +1,5 @@ from rest_framework import serializers +from drf_spectacular.utils import extend_schema_field, extend_schema from cms.serializers import CoursePageSerializer from courses import models @@ -32,15 +33,33 @@ class Meta: "type", ] - class BaseCourseRunSerializer(serializers.ModelSerializer): """Minimal CourseRun model serializer""" is_archived = serializers.SerializerMethodField() - + title = serializers.CharField() + start_date = serializers.DateTimeField() + end_date = serializers.DateTimeField() + enrollment_start = serializers.DateTimeField() + enrollment_end = serializers.DateTimeField() + expiration_date = serializers.DateTimeField() + courseware_url = serializers.URLField() + courseware_id = serializers.CharField() + certificate_available_date = serializers.DateTimeField() + upgrade_deadline = serializers.DateTimeField() + is_upgradable = serializers.BooleanField() + is_enrollable = serializers.BooleanField() + is_self_paced = serializers.BooleanField() + run_tag = serializers.CharField() + id = serializers.IntegerField() + live = serializers.BooleanField() + course_number = serializers.CharField() + + @extend_schema_field(bool) def get_is_archived(self, instance): return instance.is_enrollable and instance.is_past + class Meta: model = models.CourseRun fields = [ @@ -79,6 +98,14 @@ class Meta: fields = ["title", "readable_id", "id", "type"] +class CourseRunCertificateSerializer(serializers.ModelSerializer): + """CourseRunCertificate model serializer""" + + class Meta: + model = models.CourseRunCertificate + fields = ["uuid", "link"] + + class BaseCourseRunEnrollmentSerializer(serializers.ModelSerializer): certificate = serializers.SerializerMethodField(read_only=True) enrollment_mode = serializers.ChoiceField( @@ -87,6 +114,7 @@ class BaseCourseRunEnrollmentSerializer(serializers.ModelSerializer): approved_flexible_price_exists = serializers.SerializerMethodField() grades = serializers.SerializerMethodField(read_only=True) + @extend_schema_field(CourseRunCertificateSerializer) def get_certificate(self, enrollment): """ Resolve a certificate for this enrollment if it exists @@ -165,14 +193,6 @@ def to_representation(self, instance): return serializer.data -class CourseRunCertificateSerializer(serializers.ModelSerializer): - """CourseRunCertificate model serializer""" - - class Meta: - model = models.CourseRunCertificate - fields = ["uuid", "link"] - - class CourseRunGradeSerializer(serializers.ModelSerializer): """CourseRunGrade serializer""" diff --git a/courses/serializers/v1/courses.py b/courses/serializers/v1/courses.py index 98d3cbe68d..fe98e68b44 100644 --- a/courses/serializers/v1/courses.py +++ b/courses/serializers/v1/courses.py @@ -34,6 +34,16 @@ def get_next_run_id(self, instance): run = instance.first_unexpired_run return run.id if run is not None else None + @extend_schema_field( + inline_serializer( + name="ProgramSerializer", + fields={ + "id": serializers.IntegerField(), + "title": serializers.CharField(), + "readable_id": serializers.CharField(), + }, + ) + ) def get_programs(self, instance): if self.context.get("all_runs", False): from courses.serializers.v1.base import BaseProgramSerializer @@ -161,8 +171,8 @@ def get_course_number(self, instance) -> str: """Get the course number""" return instance.course_number - @extend_schema_field(ProductRelatedField) - def get_products(self, instance) -> List[dict]: + @extend_schema_field(list[dict]) + def get_products(self, instance) -> list[dict]: """Get products associated with this course run""" return super().get_products(instance) diff --git a/courses/serializers/v1/programs.py b/courses/serializers/v1/programs.py index bad565beb7..c964ce52e2 100644 --- a/courses/serializers/v1/programs.py +++ b/courses/serializers/v1/programs.py @@ -3,6 +3,7 @@ from mitol.common.utils import now_in_utc from rest_framework import serializers from rest_framework.exceptions import ValidationError +from drf_spectacular.utils import extend_schema_serializer from cms.serializers import ProgramPageSerializer from courses import models @@ -23,7 +24,9 @@ from openedx.constants import EDX_ENROLLMENT_VERIFIED_MODE from users.models import User - +@extend_schema_serializer( + component_name="V1ProgramSerializer" # Give it a unique name +) class ProgramSerializer(serializers.ModelSerializer): """Program model serializer""" diff --git a/courses/serializers/v2/courses.py b/courses/serializers/v2/courses.py index 42a6b2a2c4..f3b3c3824e 100644 --- a/courses/serializers/v2/courses.py +++ b/courses/serializers/v2/courses.py @@ -209,28 +209,14 @@ def get_approved_flexible_price_exists(self, instance): ) return flexible_price_exists # noqa: RET504 - class CourseWithCourseRunsSerializer(CourseSerializer): """Course model serializer - also serializes child course runs""" - courseruns = serializers.SerializerMethodField(read_only=True) - - def get_courseruns(self, instance): - context = { - "include_approved_financial_aid": self.context.get( - "include_approved_financial_aid", False - ) - } - - return CourseRunSerializer( - instance.courseruns.all(), many=True, read_only=True, context=context - ).data + courseruns = CourseRunSerializer(many=True, read_only=True) class Meta: model = models.Course - fields = CourseSerializer.Meta.fields + [ # noqa: RUF005 - "courseruns", - ] + fields = CourseSerializer.Meta.fields + ["courseruns"] class CourseRunWithCourseSerializer(CourseRunSerializer): diff --git a/courses/serializers/v2/departments.py b/courses/serializers/v2/departments.py index 9d2b4e86ce..e7ff640929 100644 --- a/courses/serializers/v2/departments.py +++ b/courses/serializers/v2/departments.py @@ -2,6 +2,7 @@ from rest_framework import serializers from courses.models import CourseRun, Department +from drf_spectacular.utils import extend_schema_field from courses.utils import get_enrollable_course_run_filter @@ -19,6 +20,7 @@ class DepartmentWithCoursesAndProgramsSerializer(DepartmentSerializer): course_ids = serializers.SerializerMethodField() program_ids = serializers.SerializerMethodField() + @extend_schema_field(serializers.ListField) def get_course_ids(self, instance): """ Returns a list of course IDs associated with courses which are live and @@ -42,6 +44,7 @@ def get_course_ids(self, instance): .values_list("id", flat=True) ) + @extend_schema_field(serializers.ListField) def get_program_ids(self, instance): """ Returns a list of program IDs associated with the department diff --git a/courses/views/v1/__init__.py b/courses/views/v1/__init__.py index a4ef164763..9d46e54bfe 100644 --- a/courses/views/v1/__init__.py +++ b/courses/views/v1/__init__.py @@ -493,6 +493,9 @@ class PartnerSchoolViewSet(viewsets.ReadOnlyModelViewSet): @api_view(["GET"]) @permission_classes([IsAuthenticated]) +@extend_schema( + responses={200: LearnerRecordSerializer}, +) def get_learner_record(request, pk): program = Program.objects.get(pk=pk) @@ -501,6 +504,10 @@ def get_learner_record(request, pk): @api_view(["POST"]) @permission_classes([IsAuthenticated]) +@extend_schema( + request=PartnerSchoolSerializer, + responses={200: LearnerRecordSerializer}, +) def get_learner_record_share(request, pk): """ Sets up a sharing link for the learner's record. Returns back the entire diff --git a/ecommerce/serializers.py b/ecommerce/serializers.py index 60b98b50c5..98ffd0a1e7 100644 --- a/ecommerce/serializers.py +++ b/ecommerce/serializers.py @@ -476,6 +476,18 @@ class Meta: class DiscountSerializer(serializers.ModelSerializer): + + id = serializers.IntegerField() + amount = serializers.DecimalField(max_digits=9, decimal_places=2) + automatic = serializers.BooleanField() + discount_type = serializers.ChoiceField(choices=DISCOUNT_TYPES) + redemption_type = serializers.ChoiceField(choices=PAYMENT_TYPES) + max_redemptions = serializers.IntegerField() + discount_code = serializers.CharField() + payment_type = serializers.ChoiceField(choices=PAYMENT_TYPES) + is_redeemed = serializers.BooleanField() + activation_date = serializers.DateTimeField() + expiration_date = serializers.DateTimeField() class Meta: model = models.Discount fields = [ diff --git a/ecommerce/views/v0/__init__.py b/ecommerce/views/v0/__init__.py index d4ddf20089..9684ee017a 100644 --- a/ecommerce/views/v0/__init__.py +++ b/ecommerce/views/v0/__init__.py @@ -343,6 +343,24 @@ def create_batch(self, request): raise ParseError(f"Batch creation failed: {otherSerializer.errors}") # noqa: EM102 +@extend_schema( + parameters=[ + OpenApiParameter( + name="parent_lookup_discount", + type=int, + location=OpenApiParameter.PATH, + description="ID of the parent discount", + required=True + ), + OpenApiParameter( + name="id", + type=int, + location=OpenApiParameter.PATH, + description="ID of the discount product", + required=True + ) + ] +) class NestedDiscountProductViewSet(NestedViewSetMixin, ModelViewSet): """API view set for Discounts""" @@ -390,7 +408,24 @@ class NestedDiscountRedemptionViewSet(NestedViewSetMixin, ModelViewSet): permission_classes = (IsAuthenticated, IsAdminUser) pagination_class = RefinePagination - +@extend_schema( + parameters=[ + OpenApiParameter( + name="parent_lookup_discount", + type=int, + location=OpenApiParameter.PATH, + description="ID of the parent discount", + required=True + ), + OpenApiParameter( + name="id", + type=int, + location=OpenApiParameter.PATH, + description="ID of the user discount", + required=True + ) + ] +) class NestedUserDiscountViewSet(NestedViewSetMixin, ModelViewSet): """ API view set for User Discounts. This one is for use within a Discount. diff --git a/openapi/specs/v0.yaml b/openapi/specs/v0.yaml index ca22792d30..0441eb79bf 100644 --- a/openapi/specs/v0.yaml +++ b/openapi/specs/v0.yaml @@ -636,6 +636,12 @@ paths: operationId: api_discounts_assignees_list description: API view set for User Discounts. This one is for use within a Discount. parameters: + - in: path + name: id + schema: + type: integer + description: ID of the user discount + required: true - name: l required: false in: query @@ -651,7 +657,8 @@ paths: - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -666,10 +673,17 @@ paths: operationId: api_discounts_assignees_create description: API view set for User Discounts. This one is for use within a Discount. parameters: + - in: path + name: id + schema: + type: integer + description: ID of the user discount + required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -689,12 +703,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this user discount. + description: ID of the user discount required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -713,12 +728,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this user discount. + description: ID of the user discount required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -737,12 +753,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this user discount. + description: ID of the user discount required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -761,12 +778,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this user discount. + description: ID of the user discount required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -778,6 +796,12 @@ paths: operationId: api_discounts_products_list description: API view set for Discounts parameters: + - in: path + name: id + schema: + type: integer + description: ID of the discount product + required: true - name: l required: false in: query @@ -793,7 +817,8 @@ paths: - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -808,10 +833,17 @@ paths: operationId: api_discounts_products_create description: API view set for Discounts parameters: + - in: path + name: id + schema: + type: integer + description: ID of the discount product + required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -843,12 +875,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this discount product. + description: ID of the discount product required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -867,12 +900,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this discount product. + description: ID of the discount product required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -903,12 +937,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this discount product. + description: ID of the discount product required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -938,12 +973,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this discount product. + description: ID of the discount product required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - api @@ -2744,7 +2780,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PaginatedProgramList' + $ref: '#/components/schemas/PaginatedV1ProgramList' description: '' /api/programs/{id}/: get: @@ -2764,7 +2800,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Program' + $ref: '#/components/schemas/V1Program' description: '' /api/records/program/{id}/: get: @@ -2835,12 +2871,28 @@ paths: /api/register/confirm/: post: operationId: api_register_confirm_create - description: Processes a request + description: Handle POST requests to confirm email registration tags: - api + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterConfirmRequest' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/RegisterConfirmRequest' + multipart/form-data: + schema: + $ref: '#/components/schemas/RegisterConfirmRequest' + required: true responses: '200': - description: No response body + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterConfirm' + description: '' /api/register/details/: post: operationId: api_register_details_create @@ -3355,6 +3407,12 @@ paths: operationId: discounts_assignees_list description: API view set for User Discounts. This one is for use within a Discount. parameters: + - in: path + name: id + schema: + type: integer + description: ID of the user discount + required: true - name: l required: false in: query @@ -3370,7 +3428,8 @@ paths: - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -3385,10 +3444,17 @@ paths: operationId: discounts_assignees_create description: API view set for User Discounts. This one is for use within a Discount. parameters: + - in: path + name: id + schema: + type: integer + description: ID of the user discount + required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -3408,12 +3474,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this user discount. + description: ID of the user discount required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -3432,12 +3499,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this user discount. + description: ID of the user discount required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -3456,12 +3524,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this user discount. + description: ID of the user discount required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -3480,12 +3549,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this user discount. + description: ID of the user discount required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -3497,6 +3567,12 @@ paths: operationId: discounts_products_list description: API view set for Discounts parameters: + - in: path + name: id + schema: + type: integer + description: ID of the discount product + required: true - name: l required: false in: query @@ -3512,7 +3588,8 @@ paths: - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -3527,10 +3604,17 @@ paths: operationId: discounts_products_create description: API view set for Discounts parameters: + - in: path + name: id + schema: + type: integer + description: ID of the discount product + required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -3562,12 +3646,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this discount product. + description: ID of the discount product required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -3586,12 +3671,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this discount product. + description: ID of the discount product required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -3622,12 +3708,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this discount product. + description: ID of the discount product required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -3657,12 +3744,13 @@ paths: name: id schema: type: integer - description: A unique integer value identifying this discount product. + description: ID of the discount product required: true - in: path name: parent_lookup_discount schema: - type: string + type: integer + description: ID of the parent discount required: true tags: - discounts @@ -5502,7 +5590,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PaginatedProgramList' + $ref: '#/components/schemas/PaginatedV1ProgramList' description: '' /api/v1/programs/{id}/: get: @@ -5522,7 +5610,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Program' + $ref: '#/components/schemas/V1Program' description: '' /api/v2/courses/: get: @@ -6030,7 +6118,11 @@ components: description: Get the country name (common name preferred if available) readOnly: true states: - type: string + type: array + items: + type: object + additionalProperties: {} + description: Get a list of states/provinces if USA or Canada readOnly: true required: - code @@ -6095,7 +6187,8 @@ components: - $ref: '#/components/schemas/CoursePage' readOnly: true programs: - type: string + allOf: + - $ref: '#/components/schemas/Program' readOnly: true required: - departments @@ -6172,81 +6265,49 @@ components: properties: title: type: string - description: The title of the course. This value is synced automatically - with edX studio. - maxLength: 255 start_date: type: string format: date-time - nullable: true - description: The day the course begins. This value is synced automatically - with edX studio. end_date: type: string format: date-time - nullable: true - description: The last day the course is active. This value is synced automatically - with edX studio. enrollment_start: type: string format: date-time - nullable: true - description: The first day students can enroll. This value is synced automatically - with edX studio. enrollment_end: type: string format: date-time - nullable: true - description: The last day students can enroll. This value is synced automatically - with edX studio. expiration_date: type: string format: date-time - nullable: true - description: The date beyond which the learner should not see link to this - course run on their dashboard. courseware_url: type: string - readOnly: true + format: uri courseware_id: type: string - maxLength: 255 certificate_available_date: type: string format: date-time - nullable: true - description: The day certificates should be available to users. This value - is synced automatically with edX studio. upgrade_deadline: type: string format: date-time - nullable: true - description: The date beyond which the learner can not enroll in paid course - mode. is_upgradable: - type: string - readOnly: true + type: boolean is_enrollable: - type: string - readOnly: true + type: boolean is_archived: - type: string + type: boolean readOnly: true is_self_paced: type: boolean run_tag: type: string - description: 'A string that identifies the set of runs that this run belongs - to (example: ''R2'')' - maxLength: 100 id: type: integer - readOnly: true live: type: boolean course_number: type: string - readOnly: true products: type: array items: @@ -6257,16 +6318,39 @@ components: readOnly: true required: - approved_flexible_price_exists + - certificate_available_date - course_number - courseware_id - courseware_url + - end_date + - enrollment_end + - enrollment_start + - expiration_date - id - is_archived - is_enrollable + - is_self_paced - is_upgradable + - live - products - run_tag + - start_date - title + - upgrade_deadline + CourseRunCertificate: + type: object + description: CourseRunCertificate model serializer + properties: + uuid: + type: string + format: uuid + readOnly: true + link: + type: string + readOnly: true + required: + - link + - uuid CourseRunEnrollment: type: object description: CourseRunEnrollment model serializer @@ -6281,7 +6365,8 @@ components: edx_emails_subscription: type: boolean certificate: - type: string + allOf: + - $ref: '#/components/schemas/CourseRunCertificate' readOnly: true enrollment_mode: allOf: @@ -6317,58 +6402,33 @@ components: properties: title: type: string - description: The title of the course. This value is synced automatically - with edX studio. - maxLength: 255 start_date: type: string format: date-time - nullable: true - description: The day the course begins. This value is synced automatically - with edX studio. end_date: type: string format: date-time - nullable: true - description: The last day the course is active. This value is synced automatically - with edX studio. enrollment_start: type: string format: date-time - nullable: true - description: The first day students can enroll. This value is synced automatically - with edX studio. enrollment_end: type: string format: date-time - nullable: true - description: The last day students can enroll. This value is synced automatically - with edX studio. expiration_date: type: string format: date-time - nullable: true - description: The date beyond which the learner should not see link to this - course run on their dashboard. courseware_url: type: string format: uri readOnly: true courseware_id: type: string - maxLength: 255 certificate_available_date: type: string format: date-time - nullable: true - description: The day certificates should be available to users. This value - is synced automatically with edX studio. upgrade_deadline: type: string format: date-time - nullable: true - description: The date beyond which the learner can not enroll in paid course - mode. is_upgradable: type: boolean description: Check if the course run is upgradable @@ -6385,12 +6445,8 @@ components: type: boolean run_tag: type: string - description: 'A string that identifies the set of runs that this run belongs - to (example: ''R2'')' - maxLength: 100 id: type: integer - readOnly: true live: type: boolean course_number: @@ -6412,17 +6468,26 @@ components: readOnly: true required: - approved_flexible_price_exists + - certificate_available_date - course - course_number - courseware_id - courseware_url + - end_date + - enrollment_end + - enrollment_start + - expiration_date - id - is_archived - is_enrollable + - is_self_paced - is_upgradable + - live - products - run_tag + - start_date - title + - upgrade_deadline CourseRunWithCourseRequest: type: object description: CourseRun model serializer - also serializes the parent Course. @@ -6430,69 +6495,53 @@ components: title: type: string minLength: 1 - description: The title of the course. This value is synced automatically - with edX studio. - maxLength: 255 start_date: type: string format: date-time - nullable: true - description: The day the course begins. This value is synced automatically - with edX studio. end_date: type: string format: date-time - nullable: true - description: The last day the course is active. This value is synced automatically - with edX studio. enrollment_start: type: string format: date-time - nullable: true - description: The first day students can enroll. This value is synced automatically - with edX studio. enrollment_end: type: string format: date-time - nullable: true - description: The last day students can enroll. This value is synced automatically - with edX studio. expiration_date: type: string format: date-time - nullable: true - description: The date beyond which the learner should not see link to this - course run on their dashboard. courseware_id: type: string minLength: 1 - maxLength: 255 certificate_available_date: type: string format: date-time - nullable: true - description: The day certificates should be available to users. This value - is synced automatically with edX studio. upgrade_deadline: type: string format: date-time - nullable: true - description: The date beyond which the learner can not enroll in paid course - mode. is_self_paced: type: boolean run_tag: type: string minLength: 1 - description: 'A string that identifies the set of runs that this run belongs - to (example: ''R2'')' - maxLength: 100 + id: + type: integer live: type: boolean required: + - certificate_available_date - courseware_id + - end_date + - enrollment_end + - enrollment_start + - expiration_date + - id + - is_self_paced + - live - run_tag + - start_date - title + - upgrade_deadline CourseWithCourseRuns: type: object description: Course model serializer - also serializes child course runs @@ -6521,7 +6570,8 @@ components: - $ref: '#/components/schemas/CoursePage' readOnly: true programs: - type: string + allOf: + - $ref: '#/components/schemas/Program' readOnly: true courseruns: type: array @@ -6659,10 +6709,12 @@ components: maxLength: 128 pattern: ^[-a-zA-Z0-9_]+$ course_ids: - type: string + type: array + items: {} readOnly: true program_ids: - type: string + type: array + items: {} readOnly: true required: - course_ids @@ -6675,51 +6727,41 @@ components: properties: id: type: integer - readOnly: true amount: type: string format: decimal - pattern: ^-?\d{0,15}(?:\.\d{0,5})?$ + pattern: ^-?\d{0,7}(?:\.\d{0,2})?$ automatic: type: boolean discount_type: $ref: '#/components/schemas/DiscountTypeEnum' redemption_type: - $ref: '#/components/schemas/RedemptionTypeEnum' + $ref: '#/components/schemas/RedemptionTypeF58Enum' max_redemptions: type: integer - maximum: 2147483647 - minimum: 0 - nullable: true discount_code: type: string - maxLength: 100 payment_type: - nullable: true - oneOf: - - $ref: '#/components/schemas/PaymentTypeEnum' - - $ref: '#/components/schemas/NullEnum' + $ref: '#/components/schemas/PaymentTypeEnum' is_redeemed: - type: string - readOnly: true + type: boolean activation_date: type: string format: date-time - nullable: true - description: If set, this discount code will not be redeemable before this - date. expiration_date: type: string format: date-time - nullable: true - description: If set, this discount code will not be redeemable after this - date. required: + - activation_date - amount + - automatic - discount_code - discount_type + - expiration_date - id - is_redeemed + - max_redemptions + - payment_type - redemption_type DiscountProduct: type: object @@ -6776,46 +6818,44 @@ components: DiscountRequest: type: object properties: + id: + type: integer amount: type: string format: decimal - pattern: ^-?\d{0,15}(?:\.\d{0,5})?$ + pattern: ^-?\d{0,7}(?:\.\d{0,2})?$ automatic: type: boolean discount_type: $ref: '#/components/schemas/DiscountTypeEnum' redemption_type: - $ref: '#/components/schemas/RedemptionTypeEnum' + $ref: '#/components/schemas/RedemptionTypeF58Enum' max_redemptions: type: integer - maximum: 2147483647 - minimum: 0 - nullable: true discount_code: type: string minLength: 1 - maxLength: 100 payment_type: - nullable: true - oneOf: - - $ref: '#/components/schemas/PaymentTypeEnum' - - $ref: '#/components/schemas/NullEnum' + $ref: '#/components/schemas/PaymentTypeEnum' + is_redeemed: + type: boolean activation_date: type: string format: date-time - nullable: true - description: If set, this discount code will not be redeemable before this - date. expiration_date: type: string format: date-time - nullable: true - description: If set, this discount code will not be redeemable after this - date. required: + - activation_date - amount + - automatic - discount_code - discount_type + - expiration_date + - id + - is_redeemed + - max_redemptions + - payment_type - redemption_type DiscountTypeEnum: enum: @@ -7052,6 +7092,17 @@ components: required: - discount - income_threshold_usd + FlowEnum: + enum: + - login + - register + type: string + description: |- + * `login` - Login + * `register` - Register + x-enum-descriptions: + - Login + - Register GenderEnum: enum: - m @@ -7205,7 +7256,7 @@ components: discount_type: $ref: '#/components/schemas/DiscountTypeEnum' redemption_type: - $ref: '#/components/schemas/RedemptionTypeEnum' + $ref: '#/components/schemas/NestedRedemptionTypeEnum' payment_type: nullable: true oneOf: @@ -7241,6 +7292,20 @@ components: - id - redemption_type - updated_on + NestedRedemptionTypeEnum: + enum: + - one-time + - one-time-per-user + - unlimited + type: string + description: |- + * `one-time` - one-time + * `one-time-per-user` - one-time-per-user + * `unlimited` - unlimited + x-enum-descriptions: + - one-time + - one-time-per-user + - unlimited NestedRequest: type: object properties: @@ -7253,7 +7318,7 @@ components: discount_type: $ref: '#/components/schemas/DiscountTypeEnum' redemption_type: - $ref: '#/components/schemas/RedemptionTypeEnum' + $ref: '#/components/schemas/NestedRedemptionTypeEnum' payment_type: nullable: true oneOf: @@ -7682,6 +7747,29 @@ components: type: array items: $ref: '#/components/schemas/UserDiscountMeta' + PaginatedV1ProgramList: + type: object + required: + - count + - results + properties: + count: + type: integer + example: 123 + next: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=4 + previous: + type: string + nullable: true + format: uri + example: http://api.example.org/accounts/?page=2 + results: + type: array + items: + $ref: '#/components/schemas/V1Program' PartnerSchool: type: object properties: @@ -7789,42 +7877,33 @@ components: PatchedDiscountRequest: type: object properties: + id: + type: integer amount: type: string format: decimal - pattern: ^-?\d{0,15}(?:\.\d{0,5})?$ + pattern: ^-?\d{0,7}(?:\.\d{0,2})?$ automatic: type: boolean discount_type: $ref: '#/components/schemas/DiscountTypeEnum' redemption_type: - $ref: '#/components/schemas/RedemptionTypeEnum' + $ref: '#/components/schemas/RedemptionTypeF58Enum' max_redemptions: type: integer - maximum: 2147483647 - minimum: 0 - nullable: true discount_code: type: string minLength: 1 - maxLength: 100 payment_type: - nullable: true - oneOf: - - $ref: '#/components/schemas/PaymentTypeEnum' - - $ref: '#/components/schemas/NullEnum' + $ref: '#/components/schemas/PaymentTypeEnum' + is_redeemed: + type: boolean activation_date: type: string format: date-time - nullable: true - description: If set, this discount code will not be redeemable before this - date. expiration_date: type: string format: date-time - nullable: true - description: If set, this discount code will not be redeemable after this - date. PatchedFlexiblePriceAdminRequest: type: object description: Serializer for Financial Assistance Requests @@ -7929,6 +8008,7 @@ components: maxLength: 255 email: type: string + description: Returns the email or None in the case of AnonymousUser password: type: string writeOnly: true @@ -8010,49 +8090,16 @@ components: - price Program: type: object - description: Program model serializer properties: - title: - type: string - maxLength: 255 - readable_id: - type: string - pattern: ^[\w\-+:\.]+$ - maxLength: 255 id: type: integer - readOnly: true - courses: - type: string - readOnly: true - requirements: - type: string - readOnly: true - req_tree: - type: string - readOnly: true - page: + title: type: string - readOnly: true - program_type: + readable_id: type: string - nullable: true - maxLength: 255 - departments: - type: array - items: - $ref: '#/components/schemas/Department' - readOnly: true - live: - type: boolean required: - - courses - - departments - id - - page - readable_id - - req_tree - - requirements - title PublicUser: type: object @@ -8100,20 +8147,83 @@ components: required: - code - message - RedemptionTypeEnum: + RedemptionTypeF58Enum: enum: - - one-time - - one-time-per-user - - unlimited + - marketing + - sales + - financial-assistance + - customer-support + - staff + - legacy type: string description: |- - * `one-time` - one-time - * `one-time-per-user` - one-time-per-user - * `unlimited` - unlimited + * `marketing` - marketing + * `sales` - sales + * `financial-assistance` - financial-assistance + * `customer-support` - customer-support + * `staff` - staff + * `legacy` - legacy x-enum-descriptions: - - one-time - - one-time-per-user - - unlimited + - marketing + - sales + - financial-assistance + - customer-support + - staff + - legacy + RegisterConfirm: + type: object + description: Serializer for email confirmation + properties: + partial_token: + type: string + flow: + $ref: '#/components/schemas/FlowEnum' + provider: + type: string + readOnly: true + state: + type: string + readOnly: true + errors: + type: array + items: {} + readOnly: true + field_errors: + type: object + additionalProperties: {} + readOnly: true + redirect_url: + type: string + readOnly: true + extra_data: + type: string + readOnly: true + required: + - errors + - extra_data + - field_errors + - flow + - partial_token + - provider + - redirect_url + - state + RegisterConfirmRequest: + type: object + description: Serializer for email confirmation + properties: + partial_token: + type: string + minLength: 1 + flow: + $ref: '#/components/schemas/FlowEnum' + verification_code: + type: string + writeOnly: true + minLength: 1 + required: + - flow + - partial_token + - verification_code SetPassword: type: object properties: @@ -8236,6 +8346,7 @@ components: maxLength: 255 email: type: string + description: Returns the email or None in the case of AnonymousUser legal_address: allOf: - $ref: '#/components/schemas/LegalAddress' @@ -8273,7 +8384,9 @@ components: format: date-time readOnly: true grants: - type: string + type: array + items: + type: string readOnly: true is_active: type: boolean @@ -8485,6 +8598,7 @@ components: maxLength: 255 email: type: string + description: Returns the email or None in the case of AnonymousUser password: type: string writeOnly: true @@ -8502,6 +8616,52 @@ components: default: true required: - legal_address + V1Program: + type: object + description: Program model serializer + properties: + title: + type: string + maxLength: 255 + readable_id: + type: string + pattern: ^[\w\-+:\.]+$ + maxLength: 255 + id: + type: integer + readOnly: true + courses: + type: string + readOnly: true + requirements: + type: string + readOnly: true + req_tree: + type: string + readOnly: true + page: + type: string + readOnly: true + program_type: + type: string + nullable: true + maxLength: 255 + departments: + type: array + items: + $ref: '#/components/schemas/Department' + readOnly: true + live: + type: boolean + required: + - courses + - departments + - id + - page + - readable_id + - req_tree + - requirements + - title YearsExperienceEnum: enum: - '2' diff --git a/users/serializers.py b/users/serializers.py index ebc978cd32..c8521988ad 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -146,6 +146,7 @@ class ExtendedLegalAddressSerializer(LegalAddressSerializer): email = serializers.SerializerMethodField() + @extend_schema_field(str) def get_email(self, instance): """Get email from the linked user object""" return instance.user.email @@ -220,14 +221,17 @@ def validate_username(self, value): return trimmed_value + @extend_schema_field(str) def get_email(self, instance): """Returns the email or None in the case of AnonymousUser""" return getattr(instance, "email", None) + @extend_schema_field(str) def get_username(self, instance): """Returns the username or None in the case of AnonymousUser""" return getattr(instance, "username", None) + @extend_schema_field(List[str]) def get_grants(self, instance): return instance.get_all_permissions() @@ -497,8 +501,8 @@ def get_name(self, instance) -> str: return instance.common_name return instance.name - @extend_schema_field(List[StateProvinceSerializer]) - def get_states(self, instance) -> List[dict]: + @extend_schema_field(list[dict]) + def get_states(self, instance) -> list[dict]: """Get a list of states/provinces if USA or Canada""" if instance.alpha_2 in ("US", "CA"): return StateProvinceSerializer( From bca6ee577e1037ab01dd36b2c040e20eaa80dbd7 Mon Sep 17 00:00:00 2001 From: CP Date: Mon, 24 Feb 2025 14:55:59 -0500 Subject: [PATCH 6/7] fix tests --- courses/serializers/v1/courses.py | 12 ++++-------- ecommerce/views/v0/__init__.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/courses/serializers/v1/courses.py b/courses/serializers/v1/courses.py index fe98e68b44..0056e34317 100644 --- a/courses/serializers/v1/courses.py +++ b/courses/serializers/v1/courses.py @@ -141,15 +141,11 @@ class CourseRunWithCourseSerializer(CourseRunSerializer): read_only=True, help_text="List of products associated with this course run" ) - - @extend_schema_field(serializers.URLField) + + @extend_schema_field(str) def get_courseware_url(self, instance) -> Optional[str]: - """Get the full URL for this CourseRun in the courseware""" - return ( - edx_redirect_url(instance.courseware_url_path) - if instance.courseware_url_path - else None - ) + """Get the courseware URL""" + return instance.courseware_url @extend_schema_field(bool) def get_is_upgradable(self, instance) -> bool: diff --git a/ecommerce/views/v0/__init__.py b/ecommerce/views/v0/__init__.py index 9684ee017a..ae3a316997 100644 --- a/ecommerce/views/v0/__init__.py +++ b/ecommerce/views/v0/__init__.py @@ -514,6 +514,24 @@ class CheckoutApiViewSet(ViewSet): authentication_classes = (SessionAuthentication, TokenAuthentication) permission_classes = (IsAuthenticated,) + @action(detail=False, methods=["post"], name="Start Checkout", url_name="start_checkout") + def start_checkout(self, request): + """ + API call to start the checkout process. This assembles the basket items + into an Order with Lines for each item, applies the attached basket + discounts, and then calls the payment gateway to prepare for payment. + Returns: + - JSON payload from the ol-django payment gateway app. The payment + gateway returns data necessary to construct a form that will + ultimately POST to the actual payment processor. + """ + try: + payload = api.generate_checkout_payload(request) + except ObjectDoesNotExist: + return Response("No basket", status=status.HTTP_406_NOT_ACCEPTABLE) + + return Response(payload) + @extend_schema( request=RedeemDiscountRequestSerializer, responses={200: RedeemDiscountResponseSerializer}, From 783d7e9525f94d2e034fb8174d0e02d152744afe Mon Sep 17 00:00:00 2001 From: CP Date: Tue, 25 Feb 2025 09:39:20 -0500 Subject: [PATCH 7/7] Test fix --- courses/views/v2/__init__.py | 9 ++++----- ecommerce/serializers.py | 13 +------------ 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/courses/views/v2/__init__.py b/courses/views/v2/__init__.py index 3b19d33946..ae089acc64 100644 --- a/courses/views/v2/__init__.py +++ b/courses/views/v2/__init__.py @@ -96,11 +96,10 @@ def get_queryset(self): def get_serializer_context(self): added_context = {} - if self.request and self.request.query_params: - if self.request.query_params.get("readable_id", None): - added_context["all_runs"] = True - if self.request.query_params.get("include_approved_financial_aid", None): - added_context["include_approved_financial_aid"] = True + if self.request.query_params.get("readable_id", None): + added_context["all_runs"] = True + if self.request.query_params.get("include_approved_financial_aid", None): + added_context["include_approved_financial_aid"] = True return {**super().get_serializer_context(), **added_context} diff --git a/ecommerce/serializers.py b/ecommerce/serializers.py index 98ffd0a1e7..95dab93ba6 100644 --- a/ecommerce/serializers.py +++ b/ecommerce/serializers.py @@ -476,18 +476,7 @@ class Meta: class DiscountSerializer(serializers.ModelSerializer): - - id = serializers.IntegerField() - amount = serializers.DecimalField(max_digits=9, decimal_places=2) - automatic = serializers.BooleanField() - discount_type = serializers.ChoiceField(choices=DISCOUNT_TYPES) - redemption_type = serializers.ChoiceField(choices=PAYMENT_TYPES) - max_redemptions = serializers.IntegerField() - discount_code = serializers.CharField() - payment_type = serializers.ChoiceField(choices=PAYMENT_TYPES) - is_redeemed = serializers.BooleanField() - activation_date = serializers.DateTimeField() - expiration_date = serializers.DateTimeField() + class Meta: model = models.Discount fields = [