-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/temporary subscription #145
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
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
af7583b
check metadata field of SpecialMembershipDataset Model
danieleguido 2f44465
Add temporary approval to membership requests
danieleguido d71176a
Update settings.py
danieleguido 98ff7c0
temporary request (wip)
danieleguido bee5c84
membership revoked template (WIP)
danieleguido fa82f95
Create 0058_remove_userrequest_subscription_and_more.py
danieleguido 2734c2c
WIP
danieleguido 9424393
Add temp membership check & revocation beat
danieleguido 0847dc8
revoke after days metadata accepts float values
danieleguido c637e67
Update AGENTS.md
danieleguido 195cdcb
add temporary_expires_at field in userSpecialMembershipRequest
danieleguido 06f027f
add custom command to create SMR
danieleguido 2181263
add status label to email templates and add this to specific command
danieleguido d8680cc
align templates HTML and TXT
danieleguido 7aac236
fix tests
danieleguido 376bf05
Update test_userSpecialMembershipRequest.py
danieleguido c8e638d
Update test_userSpecialMembershipRequest.py
danieleguido 26e3596
Make temporary revocations beat-driven
danieleguido 04c59f6
Potential fix for pull request finding
danieleguido 4e3261c
Update user_special_membership_request_approved_temporary_to_user.html
danieleguido c3807fc
Merge branch 'feature/temporary-subscription' of https://github.com/i…
danieleguido b489d01
Update userSpecialMembershipRequest.py
danieleguido 584815e
Use settings default for temporary revoke days
danieleguido bb4e84b
Update settings.py
danieleguido f65eb83
specialMembershipDataset: add common method on model directly
danieleguido b3356b4
Update .gitignore
danieleguido 1c9fa87
use newly created methods
danieleguido 926bb48
remove celery beat
danieleguido 2588ecc
exclude 0 when changing revoke_after_days property in the admin
danieleguido 22bf338
Create 0059_alter_collection_serialized_search_query_and_more.py
danieleguido ca97983
refine admin
danieleguido File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -120,4 +120,5 @@ ___* | |
|
|
||
| # local docker settings and config | ||
| .docker/* | ||
| !.docker/.gitkeep | ||
| !.docker/.gitkeep | ||
| *.db | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| from typing import Any | ||
| from django.core.management.base import BaseCommand | ||
| from django.utils import timezone | ||
| from impresso.models import UserSpecialMembershipRequest | ||
| from impresso.tasks.userSpecialMembershipRequest_tasks import ( | ||
| revoke_expired_temporary_memberships, | ||
| ) | ||
|
|
||
| class Command(BaseCommand): | ||
| help = ( | ||
| "Check temporary special memberships and enqueue asynchronous revocation for " | ||
| "expired ones." | ||
| ) | ||
|
|
||
| def add_arguments(self, parser) -> None: | ||
| parser.add_argument( | ||
| "--dry-run", | ||
| action="store_true", | ||
| help="Run the command without making any actual changes to the database.", | ||
| ) | ||
|
|
||
| def handle(self, *args: Any, **options: Any) -> None: | ||
| dry_run = options.get("dry_run", False) | ||
|
|
||
| if dry_run: | ||
| self.stdout.write(self.style.NOTICE("Running in DRY RUN mode.")) | ||
|
|
||
| requests = UserSpecialMembershipRequest.objects.filter( | ||
| status=UserSpecialMembershipRequest.STATUS_APPROVED_TEMPORARY | ||
| ) | ||
|
|
||
| count = requests.count() | ||
| self.stdout.write(self.style.SUCCESS(f"Found {count} special memberships with temporary approval.")) | ||
|
|
||
| for req in requests: | ||
| expires_at = req.temporary_expires_at | ||
| expired_str = "" | ||
| if expires_at and expires_at < timezone.now(): | ||
| expired_str = " (EXPIRED)" | ||
| elif expires_at: | ||
| expired_str = f" (Expires: {expires_at.strftime('%Y-%m-%d %H:%M:%S %Z')})" | ||
|
|
||
| dataset_title = req.subscription.title if req.subscription else "None" | ||
| self.stdout.write( | ||
| f"- Request ID: {req.pk}, User: {req.user.username}, " | ||
| f"Dataset: {dataset_title}{expired_str}" | ||
| ) | ||
|
|
||
| if dry_run: | ||
| self.stdout.write( | ||
| self.style.NOTICE( | ||
| "Dry run completed: task dispatch skipped." | ||
| ) | ||
| ) | ||
| return | ||
|
|
||
| async_result = revoke_expired_temporary_memberships.delay() | ||
| self.stdout.write( | ||
| self.style.SUCCESS( | ||
| f"Enqueued revoke_expired_temporary_memberships task (id={async_result.id})." | ||
| ) | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
impresso/management/commands/createspecialmembershiprequest.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| from datetime import timedelta | ||
| from typing import Any | ||
|
|
||
| from django.contrib.auth.models import User | ||
| from django.core.management.base import BaseCommand, CommandError | ||
| from django.db import IntegrityError | ||
| from django.utils import timezone | ||
|
|
||
| from django.conf import settings | ||
| from impresso.models import SpecialMembershipDataset, UserSpecialMembershipRequest | ||
|
|
||
|
|
||
| class Command(BaseCommand): | ||
| help = ( | ||
| "Create a pending special membership request on behalf of a user " | ||
| "using user email and dataset id." | ||
| ) | ||
|
|
||
| def add_arguments(self, parser) -> None: | ||
| parser.add_argument( | ||
| "user_email", | ||
| type=str, | ||
| help="Email of the user for whom the request will be created", | ||
| ) | ||
| parser.add_argument( | ||
| "dataset_id", | ||
| type=int, | ||
| help="SpecialMembershipDataset id", | ||
| ) | ||
| parser.add_argument( | ||
| "--status", | ||
| type=str, | ||
| default=UserSpecialMembershipRequest.STATUS_PENDING, | ||
| choices=[ | ||
| UserSpecialMembershipRequest.STATUS_PENDING, | ||
| UserSpecialMembershipRequest.STATUS_APPROVED, | ||
| UserSpecialMembershipRequest.STATUS_APPROVED_TEMPORARY, | ||
| UserSpecialMembershipRequest.STATUS_REJECTED, | ||
| UserSpecialMembershipRequest.STATUS_REVOKED, | ||
| ], | ||
| help=( | ||
| "Initial status of the request " | ||
| f"(default: {UserSpecialMembershipRequest.STATUS_PENDING})" | ||
| ), | ||
| ) | ||
| parser.add_argument( | ||
| "--notes", | ||
| type=str, | ||
| default=None, | ||
| help="Optional notes to attach to the request", | ||
| ) | ||
| parser.add_argument( | ||
| "--revoke-after", | ||
| type=float, | ||
| default=None, | ||
| dest="revoke_after", | ||
| help="Number of days after which the temporary membership expires (sets temporary_expires_at and overrides the default value from subscription metadata)", | ||
| ) | ||
|
|
||
| def handle( | ||
| self, user_email: str, dataset_id: int, *args: Any, **options: Any | ||
| ) -> None: | ||
| try: | ||
| user = User.objects.get(email=user_email) | ||
| except User.DoesNotExist as exc: | ||
| raise CommandError( | ||
| f"User with email '{user_email}' does not exist." | ||
| ) from exc | ||
|
|
||
| try: | ||
| dataset = SpecialMembershipDataset.objects.select_related("reviewer").get( | ||
| pk=dataset_id | ||
| ) | ||
| except SpecialMembershipDataset.DoesNotExist as exc: | ||
| raise CommandError( | ||
| f"SpecialMembershipDataset with id={dataset_id} does not exist." | ||
| ) from exc | ||
|
|
||
| revoke_after: float | None = options["revoke_after"] | ||
| # Check that revoke_after options is valid int or float greater than 0 | ||
| if revoke_after is not None: | ||
| if revoke_after <= 0: | ||
| raise CommandError( | ||
| f"Invalid value for --revoke-after: {revoke_after}. It must be a positive number." | ||
| ) | ||
|
|
||
| if ( | ||
| revoke_after is None | ||
| and options["status"] | ||
| == UserSpecialMembershipRequest.STATUS_APPROVED_TEMPORARY | ||
| ): | ||
| revoke_after = dataset.resolve_revoke_after_days( | ||
| default_days=settings.IMPRESSO_SPECIAL_MEMBERSHIP_TEMPORARY_APPROVAL_DEFAULT_DAYS | ||
| ) | ||
|
|
||
| # normally this would be set from request.creation_date + revoke_after, | ||
| # but since we're creating the request now we can set it from now + revoke_after. | ||
| temporary_expires_at = ( | ||
| timezone.now() + timedelta(days=revoke_after) | ||
| if revoke_after is not None | ||
| else None | ||
| ) | ||
|
|
||
| try: | ||
| request = UserSpecialMembershipRequest.objects.create( | ||
| user=user, | ||
| reviewer=dataset.reviewer, | ||
| subscription=dataset, | ||
| status=options["status"], | ||
| notes=options["notes"], | ||
| temporary_expires_at=temporary_expires_at, | ||
| ) | ||
| except IntegrityError as exc: | ||
| raise CommandError( | ||
| "A request for this user and dataset already exists or could not be created." | ||
| ) from exc | ||
|
|
||
| self.stdout.write( | ||
| self.style.SUCCESS( | ||
| "Created special membership request " | ||
| f"id={request.pk} for user='{user.email}' and dataset_id={dataset.pk}." | ||
| ) | ||
| ) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.