Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Management command to delete batch of users #915

Open
wants to merge 100 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
e4974a7
✨(backend) add order states and flow for the new sales tunnel
kernicPanel May 16, 2024
df846f7
♻️(backend) assign orga in order create endpoint
kernicPanel May 22, 2024
0466bc1
♻️(backend) add ProductTargetCourseRelation on order creation
kernicPanel May 22, 2024
cfe54ae
♻️(backend) create main invoice in order create endpoint
kernicPanel May 22, 2024
d5e67e8
♻️(backend) aadd credit card to order factory
kernicPanel May 23, 2024
46a51ba
🔥(backend) remove order abort endpoint
kernicPanel May 24, 2024
814da00
🔥(backend) remove order submit endpoint
kernicPanel May 24, 2024
51f8554
🔥(backend) remove order validate endpoint
kernicPanel May 24, 2024
19d92de
🔥(backend) remove payment from submit transition
kernicPanel May 24, 2024
f6ed47c
🔥(backend) remove validated state usage
kernicPanel May 27, 2024
caca0fe
🔥(backend) remove order.submit
kernicPanel May 28, 2024
e1f6fcd
🔥(backend) remove unused flow transitions
kernicPanel May 28, 2024
3218094
🔥(backend) migrate order states
kernicPanel May 28, 2024
0d12592
🔥(backend) remove pending flow transition
kernicPanel May 28, 2024
fe234b9
🔥(backend) remove validated state
kernicPanel May 28, 2024
915fd07
🔥(backend) remove submitted state
kernicPanel May 28, 2024
8d11ae2
✅(backend) fix flaky test
kernicPanel May 29, 2024
77d463d
➕(backend) add pytest-subtest
kernicPanel May 29, 2024
f381cb1
✅(backend) fix submit signature order test
kernicPanel May 29, 2024
4233162
✅(backend) use subtest in test loops
kernicPanel May 29, 2024
3888104
🎨(backend) cleanup order state flow
kernicPanel May 29, 2024
0a66bc8
🩹(backend) fix pending transition conditions
kernicPanel May 29, 2024
71218db
🩹(backend) fix _post_transition_success state check
kernicPanel May 29, 2024
58d0de1
💡(backend) add todos
kernicPanel May 29, 2024
5d1dc01
👔(backend) update contract queryset
kernicPanel May 29, 2024
f967295
🔨(backend) add pylint ignore todos
kernicPanel May 30, 2024
5cebab4
✅(backend) fix another flaky test
kernicPanel May 30, 2024
0e2aa73
💬(backend) fix order cancel error message
kernicPanel May 30, 2024
5fc815a
👔(backend) update filter nested order course
kernicPanel May 30, 2024
43a0640
💬(backend) fix order submit_for_signature error message
kernicPanel May 30, 2024
2f8aacc
💡(backend) add todo for complete flow update
kernicPanel May 31, 2024
c99fd98
🩹(backend) check billing address before order assign
kernicPanel May 31, 2024
d1dd06b
✅(backend) fix order.submit_for_signature test
kernicPanel Jun 3, 2024
566d131
💡(backend) remove TODO
kernicPanel Jun 3, 2024
6395fbb
♻️(backend) rework flow.update
kernicPanel Jun 3, 2024
cbff241
♻️(backend) simplify order._set_installment_state
kernicPanel Jun 3, 2024
909747c
♻️(backend) rename flow.assign
kernicPanel Jun 3, 2024
dd98ba8
🔥(backend) remove to_sign_and_to_save_payment order state
kernicPanel Jun 3, 2024
827f714
♻️(backend) simplify flow.update
kernicPanel Jun 4, 2024
41207b3
♻️(backend) extract assign transition
kernicPanel Jun 4, 2024
828d04e
🔨(ci) fix pylint ignore todos
kernicPanel Jun 11, 2024
87a09b3
♻️(backend) deprecate `has_consent_to_terms` for Order model
jonathanreveille Jun 10, 2024
140afa2
🎨(backend) update context for contract for terms and conditions
jonathanreveille Jun 10, 2024
ae75487
✅(backend) fix flow order tests
kernicPanel Jun 13, 2024
bed20ac
✨(backend) sign all contracts but canceled orders
kernicPanel Jun 7, 2024
633dd0f
🧑‍💻(backend) add new order factory
kernicPanel Jun 5, 2024
414a550
✨(backend) generate payment schedule before signing
kernicPanel Jun 11, 2024
7401fbb
✨(backend) create order contract on init_flow
kernicPanel Jun 13, 2024
4616c8e
🐛(backend) update order state after student signature
kernicPanel Jun 13, 2024
c8addd4
✨(backend) get signature reference exclude canceled orders
kernicPanel Jun 10, 2024
771165e
✨(backend) use product contract definition for has_unsigned_contract
kernicPanel Jun 14, 2024
1a244eb
✨(backend) order add payment method api endpoint
kernicPanel Jun 14, 2024
442e362
🩹(backend) force card storage on payment
kernicPanel Jun 14, 2024
57b61e2
✨(backend) use all enrollable order states for enroll mode
kernicPanel Jun 18, 2024
8b80c9b
🩹(backend) always use installments for orders
kernicPanel Jun 18, 2024
c484724
✅(backend) fix tests
kernicPanel Jun 21, 2024
ea9856c
✅(backend) fix tests
kernicPanel Jun 26, 2024
fb643cd
🎨(backend) installment required in payment methods
kernicPanel Jun 20, 2024
01e71b2
🐛(backend) always use stockholm for installment amount
kernicPanel Jun 20, 2024
21f013f
🐛(frontend) use new order states
kernicPanel Jun 24, 2024
ed62a8e
✨(backend) add payment schedule to order admin api
kernicPanel Jun 24, 2024
268156a
✨(frontend) add payment schedule to order view
kernicPanel Jun 27, 2024
ab7b4f4
🐛(back) manage lyra card tokenization without order for a user
lunika Jun 27, 2024
54e214d
🐛(back) fix payment debug view
lunika Jun 27, 2024
3e6278c
✨(backend) catch up on late payment schedule event
jonathanreveille Jun 14, 2024
ef87932
🗃️(backend) store Order images through DocumentImage model
kernicPanel Jul 2, 2024
eb5f6ef
🐛(backend) add signing order state
kernicPanel Jun 28, 2024
60b7a02
🐛(backend) realistic dummy signature behavior
kernicPanel Jul 2, 2024
d810cec
🐛(backend) update state on signature reset
kernicPanel Jul 2, 2024
adaf546
🐛(backend) fix handle_notification of dummy signature backend
jbpenrath Jul 4, 2024
c5386dc
🐛(frontend/admin) add support of signing order state
jbpenrath Jul 4, 2024
310ed55
👔(backend) update condition to transition order to pending_payment
jbpenrath Jul 11, 2024
a206cb8
✨(backend) add property has_submitted_contract to Order model
jbpenrath Jul 11, 2024
bf4350f
👔(backend) prevent signing Order to go back to_sign state if submitted
jbpenrath Jul 11, 2024
b31a4f4
✨(backend) sort credit card per is_main then creation date
jbpenrath Aug 1, 2024
071aecc
🔧(tray) add cronjob for process_payment_schedule management command
lunika Aug 1, 2024
a197755
🔧(backend) update PAYMENT_SCHEDULE_LIMITS
jbpenrath Aug 1, 2024
b53a69c
✨(backend) manage payment_schedule with certificate product
jbpenrath Aug 1, 2024
5564424
✨(backend) nestedOrderCourseViewSet filters order with binding states
jbpenrath Aug 1, 2024
10f1211
🔧(tray) add configMap env into db_migrate job
jbpenrath Aug 7, 2024
4f1cd0b
🚚(tray) fix cronjob app service extension
jbpenrath Aug 8, 2024
be99ef5
🔥(admin) remove has_consent_to_terms
jbpenrath Aug 8, 2024
6e231a8
✨(backend) allow to generate payment schedule for any kind of product
jbpenrath Aug 8, 2024
0ac9938
👔(backend) update find_today_installments to retrieve past due payment
jbpenrath Aug 8, 2024
3b279f5
✨(backend) prevent duplicate addresses for a user or an organization
jonathanreveille Aug 12, 2024
dbd6b3a
🐛(backend) fix payment schedule date calculation
kernicPanel Aug 14, 2024
e647ecd
✨(backend) installment paid email mjml template
jonathanreveille Jul 18, 2024
89c0ec6
✨(backend) all installment paid email mjml template
jonathanreveille Aug 6, 2024
9cf645a
🧑‍💻(backend) debug view for installment payment email
jonathanreveille Jul 18, 2024
2a782e4
🧑‍💻(backend) debug view all installments paid email
jonathanreveille Aug 6, 2024
ed7808a
✨(backend) send an email when new installment is paid
jonathanreveille Jul 1, 2024
4f6a94c
✨(backend) bind payment_schedule into OrderLightSerializer
jbpenrath Aug 22, 2024
eb1bfc4
✨(backend) installment refused debit email mjml template
jonathanreveille Aug 7, 2024
afd60dd
🧑‍💻(backend) debug view refused debit installment email
jonathanreveille Aug 7, 2024
a73228d
✨(backend) send an email when installment debit refused
jonathanreveille Aug 8, 2024
5d74890
♻️(back) use settings.AUTH_USER_MODEL and change deletion to cascade
lunika Aug 23, 2024
002340f
✨(back) create a management command to bulk delete users
lunika Aug 23, 2024
dc24ec8
fixup! ✨(back) create a management command to bulk delete users
lunika Aug 23, 2024
87ba646
fixup! ♻️(back) use settings.AUTH_USER_MODEL and change deletion to c…
lunika Aug 23, 2024
26fdf9a
fixup! ✨(back) create a management command to bulk delete users
lunika Aug 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ generate-version-file: &generate-version-file
"$CIRCLE_PROJECT_REPONAME" \
"$CIRCLE_BUILD_URL" > src/backend/joanie/version.json

version: 2
version: 2.1
jobs:
# Git jobs
# Check that the git history is clean and complies with our expectations
Expand Down Expand Up @@ -158,9 +158,21 @@ jobs:
- run:
name: Lint code with ruff
command: ~/.local/bin/ruff check joanie
- run:
name: Lint code with pylint
command: ~/.local/bin/pylint joanie
- when:
condition:
not:
matches: { pattern: "^dev_/.+$", value: << pipeline.git.branch >> }
steps:
- run:
name: Lint code with pylint
command: ~/.local/bin/pylint joanie
- when:
condition:
matches: { pattern: "^dev_/.+$", value: << pipeline.git.branch >> }
steps:
- run:
name: Lint code with pylint, ignoring TODOs
command: ~/.local/bin/pylint joanie --disable=fixme

test-back:
docker:
Expand All @@ -175,12 +187,14 @@ jobs:
DB_USER: fun
DB_PASSWORD: pass
DB_PORT: 5432
DJANGO_CACHE_REDIS_LOCATION: redis://localhost:6379/0
# services
- image: cimg/postgres:12.10
environment:
POSTGRES_DB: test_joanie
POSTGRES_USER: fun
POSTGRES_PASSWORD: pass
- image: cimg/redis:6.2
working_directory: ~/joanie/src/backend
environment:
DJANGO_CONFIGURATION: Test
Expand Down
26 changes: 25 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,35 @@ and this project adheres to

## [Unreleased]

### Added

- Send an email to the user when an installment debit has been
refused
- Send an email to the user when an installment is successfully
paid
- Support of payment_schedule for certificate products

### Changed

- Bind payment_schedule into `OrderLightSerializer`
- Generate payment schedule for any kind of product
- Sort credit card list by is_main then descending creation date
- Rework order statuses
- Update the task `debit_pending_installment` to catch up on late
payments of installments that are in the past
- Deprecated field `has_consent_to_terms` for `Order` model

### Fixed

- Prevent duplicate Address objects for a user or an organization
- Allow to cancel an enrollment order linked to an archived course run

### Removed

- Remove the `has_consent_to_terms` field from the `Order` edit view
in the back office application


## [2.6.1] - 2024-07-25

### Fixed
Expand Down Expand Up @@ -44,7 +69,6 @@ and this project adheres to

- Do not update OpenEdX enrollment if this one is already
up-to-date on the remote lms
-

## [2.4.0] - 2024-06-21

Expand Down
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,21 @@ lint-pylint: ## lint back-end python sources with pylint only on changed files f
bin/pylint --diff-only=origin/main
.PHONY: lint-pylint

lint-pylint-todo: ## lint back-end python sources with pylint only on changed files from main without fixme warnings
@echo 'lint:pylint started…'
bin/pylint --diff-only=origin/main --disable=fixme
.PHONY: lint-pylint-todo

lint-pylint-all: ## lint back-end python sources with pylint
@echo 'lint:pylint-all started…'
bin/pylint joanie
.PHONY: lint-pylint-all

lint-pylint-all-todo: ## lint back-end python sources with pylint without fixme warnings
@echo 'lint:pylint-all started…'
bin/pylint joanie --disable=fixme
.PHONY: lint-pylint-all-todo

test: ## run project tests
@$(MAKE) test-back-parallel
@$(MAKE) admin-test
Expand Down
5 changes: 4 additions & 1 deletion env.d/development/common.dist
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ DJANGO_EMAIL_PORT=1025
# Richie
JOANIE_CATALOG_BASE_URL=http://richie:8070
JOANIE_CATALOG_NAME=richie
JOANIE_CONTRACT_CONTEXT_PROCESSORS =
JOANIE_CONTRACT_CONTEXT_PROCESSORS =

# Backoffice
JOANIE_BACKOFFICE_BASE_URL="http://localhost:8072"
Expand All @@ -75,3 +75,6 @@ DEVELOPER_EMAIL="[email protected]"

# Security for remote endpoints API
JOANIE_AUTHORIZED_API_TOKENS = "secretTokenForRemoteAPIConsumer"

# Add here the dashboard link of orders for email sent when an installment is paid
JOANIE_DASHBOARD_ORDER_LINK = "http://localhost:8070/dashboard/courses/orders/:orderId/"
5 changes: 3 additions & 2 deletions src/backend/joanie/badges/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

from functools import lru_cache

from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _

from parler import models as parler_models

from joanie.core.models import BaseModel, User
from joanie.core.models import BaseModel


@lru_cache
Expand Down Expand Up @@ -105,7 +106,7 @@ class IssuedBadge(BaseModel):
)

user = models.ForeignKey(
to=User,
to=settings.AUTH_USER_MODEL,
verbose_name=_("User"),
related_name="issued_badges",
on_delete=models.CASCADE,
Expand Down
1 change: 0 additions & 1 deletion src/backend/joanie/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,6 @@ class OrderAdmin(DjangoObjectActions, admin.ModelAdmin):
readonly_fields = (
"state",
"total",
"has_consent_to_terms",
"invoice",
"certificate",
)
Expand Down
138 changes: 75 additions & 63 deletions src/backend/joanie/core/api/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
Client API endpoints
"""

# pylint: disable=too-many-ancestors, too-many-lines
# pylint: disable=too-many-ancestors, too-many-lines, too-many-branches
# ruff: noqa: PLR0911,PLR0912
import io
import uuid
from http import HTTPStatus
Expand All @@ -28,6 +29,7 @@
from joanie.core import enums, filters, models, permissions, serializers
from joanie.core.api.base import NestedGenericViewSet
from joanie.core.exceptions import NoContractToSignError
from joanie.core.models import Address
from joanie.core.tasks import generate_zip_archive_task
from joanie.core.utils import contract as contract_utility
from joanie.core.utils import contract_definition, issuers
Expand Down Expand Up @@ -200,9 +202,13 @@ def payment_schedule(self, *args, **kwargs):
Return the payment schedule for a course product relation.
"""
course_product_relation = self.get_object()
course_run_dates = (
course_product_relation.product.get_equivalent_course_run_dates()
)

if course_product_relation.product.type == enums.PRODUCT_TYPE_CERTIFICATE:
instance = course_product_relation.course
else:
instance = course_product_relation.product
course_run_dates = instance.get_equivalent_course_run_dates()

payment_schedule = generate_payment_schedule(
course_product_relation.product.price,
timezone.now(),
Expand Down Expand Up @@ -397,6 +403,19 @@ def create(self, request, *args, **kwargs):
)
course = enrollment.course_run.course

if not serializer.initial_data.get("organization_id"):
organization = self._get_organization_with_least_active_orders(
product, course, enrollment
)
if organization:
serializer.initial_data["organization_id"] = organization.id

if product.price != 0 and not request.data.get("billing_address"):
return Response(
{"billing_address": "This field is required."},
status=HTTPStatus.BAD_REQUEST,
)

# - Validate data then create an order
try:
self.perform_create(serializer)
Expand All @@ -409,58 +428,21 @@ def create(self, request, *args, **kwargs):
status=HTTPStatus.BAD_REQUEST,
)

# Else return the fresh new order
return Response(serializer.data, status=HTTPStatus.CREATED)

@action(detail=True, methods=["PATCH"])
def submit(self, request, pk=None): # pylint: disable=no-self-use, invalid-name, unused-argument
"""
Submit a draft order if the conditions are filled
"""
billing_address = (
models.Address(**request.data.get("billing_address"))
if request.data.get("billing_address")
else None
serializer.instance.init_flow(
billing_address=request.data.get("billing_address")
)
credit_card_id = request.data.get("credit_card_id")
order = self.get_object()

if order.organization is None:
order.organization = self._get_organization_with_least_active_orders(
order.product, order.course, order.enrollment
)
order.save()

return Response(
{"payment_info": order.submit(billing_address, credit_card_id)},
status=HTTPStatus.CREATED,
)

@action(detail=True, methods=["POST"])
def abort(self, request, pk=None): # pylint: disable=no-self-use, invalid-name, unused-argument
"""Change the state of the order to pending"""
payment_id = request.data.get("payment_id")

order = self.get_object()

if order.state == enums.ORDER_STATE_VALIDATED:
return Response(
"Cannot abort a validated order.",
status=HTTPStatus.UNPROCESSABLE_ENTITY,
)

order.flow.pending(payment_id)

return Response(status=HTTPStatus.NO_CONTENT)
# Else return the fresh new order
return Response(serializer.data, status=HTTPStatus.CREATED)

@action(detail=True, methods=["POST"])
def cancel(self, request, pk=None): # pylint: disable=no-self-use, invalid-name, unused-argument
"""Change the state of the order to cancelled"""
order = self.get_object()

if order.state == enums.ORDER_STATE_VALIDATED:
if order.state == enums.ORDER_STATE_COMPLETED:
return Response(
"Cannot cancel a validated order.",
"Cannot cancel a completed order.",
status=HTTPStatus.UNPROCESSABLE_ENTITY,
)

Expand Down Expand Up @@ -508,15 +490,6 @@ def invoice(self, request, pk=None): # pylint: disable=no-self-use, invalid-nam

return response

@action(detail=True, methods=["PUT"])
def validate(self, request, pk=None): # pylint: disable=no-self-use, invalid-name, unused-argument
"""
Validate the order
"""
order = self.get_object()
order.flow.validate()
return Response(status=HTTPStatus.OK)

@extend_schema(request=None)
@action(detail=True, methods=["POST"])
def submit_for_signature(self, request, pk=None): # pylint: disable=no-self-use, unused-argument, invalid-name
Expand Down Expand Up @@ -612,6 +585,45 @@ def submit_installment_payment(self, request, pk=None): # pylint: disable=unuse

return Response(payment_infos, status=HTTPStatus.OK)

@extend_schema(
request={"credit_card_id": OpenApiTypes.UUID},
responses={
(200, "application/json"): OpenApiTypes.OBJECT,
400: serializers.ErrorResponseSerializer,
404: serializers.ErrorResponseSerializer,
},
)
@action(detail=True, methods=["POST"], url_path="payment-method")
def payment_method(self, request, *args, **kwargs):
"""
Set the payment method for an order.
"""
order = self.get_object()

credit_card_id = request.data.get("credit_card_id")
if not credit_card_id:
return Response(
{"credit_card_id": "This field is required."},
status=HTTPStatus.BAD_REQUEST,
)

try:
credit_card = CreditCard.objects.get_card_for_owner(
pk=credit_card_id,
username=order.owner.username,
)
except CreditCard.DoesNotExist:
return Response(
{"detail": "Credit card does not exist."},
status=HTTPStatus.NOT_FOUND,
)

order.credit_card = credit_card
order.save()
order.flow.update()

return Response(status=HTTPStatus.CREATED)


class AddressViewSet(
mixins.ListModelMixin,
Expand Down Expand Up @@ -1175,8 +1187,8 @@ class GenericContractViewSet(
serializer_class = serializers.ContractSerializer
filterset_class = filters.ContractViewSetFilter
ordering = ["-student_signed_on", "-created_on"]
queryset = models.Contract.objects.filter(
order__state=enums.ORDER_STATE_VALIDATED
queryset = models.Contract.objects.exclude(
order__state=enums.ORDER_STATE_CANCELED
).select_related(
"definition",
"order__organization",
Expand Down Expand Up @@ -1236,10 +1248,8 @@ def download(self, request, pk=None): # pylint: disable=unused-argument, invali
"""
contract = self.get_object()

if contract.order.state != enums.ORDER_STATE_VALIDATED:
raise ValidationError(
"Cannot get contract when an order is not yet validated."
)
if contract.order.state == enums.ORDER_STATE_CANCELED:
raise ValidationError("Cannot get contract when an order is cancelled.")

if not contract.is_fully_signed:
raise ValidationError(
Expand Down Expand Up @@ -1526,7 +1536,9 @@ class NestedOrderCourseViewSet(NestedGenericViewSet, mixins.ListModelMixin):
filterset_class = filters.NestedOrderCourseViewSetFilter
ordering = ["-created_on"]
queryset = (
models.Order.objects.filter(state=enums.ORDER_STATE_VALIDATED)
models.Order.objects.filter(
state__in=enums.ORDER_STATES_BINDING,
)
.select_related(
"contract",
"certificate",
Expand Down
Loading