diff --git a/impresso/admin.py b/impresso/admin.py index e729813..ad36156 100644 --- a/impresso/admin.py +++ b/impresso/admin.py @@ -9,11 +9,25 @@ from .models import SearchQuery, ContentItem from .models import Collection, CollectableItem, Tag, TaggableItem from .models import Attachment, UploadedImage -from .models import UserBitmap, DatasetBitmapPosition +from .models import UserBitmap, DatasetBitmapPosition, UserRequest from impresso.tasks import after_user_activation +@admin.register(UserRequest) +class UserRequestAdmin(admin.ModelAdmin): + list_display = ( + "user", + "reviewer", + "subscription", + "status", + "date_created", + ) + search_fields = ["subscriber__username", "subscription__name"] + list_filter = ["status"] + autocomplete_fields = ["user", "reviewer", "subscription"] + + @admin.register(UserBitmap) class UserBitmapAdmin(admin.ModelAdmin): list_display = ( @@ -58,9 +72,10 @@ def user_plan_display(self, obj): @admin.register(DatasetBitmapPosition) class DatasetBitmapPositionAdmin(admin.ModelAdmin): - list_display = ("name", "bitmap_position") - search_fields = ["name"] + list_display = ("name", "bitmap_position", "reviewer") + search_fields = ["name", "reviewer__username", "reviewer__email"] readonly_fields = ("bitmap_position",) + autocomplete_fields = ["reviewer"] @admin.register(Issue) @@ -224,6 +239,7 @@ class UserAdmin(BaseUserAdmin): "max_parallel_jobs", ) actions = ["make_active", "make_suspended"] + search_fields = ["username", "profile__uid", "email"] @admin.action(description="ACTIVATE selected users") def make_active(self, request, queryset): diff --git a/impresso/migrations/0043_userrequest.py b/impresso/migrations/0043_userrequest.py new file mode 100644 index 0000000..ae86e46 --- /dev/null +++ b/impresso/migrations/0043_userrequest.py @@ -0,0 +1,34 @@ +# Generated by Django 5.0.8 on 2024-10-14 14:33 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('impresso', '0042_alter_userbitmap_subscriptions'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='UserRequest', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('date_last_modified', models.DateTimeField(auto_now=True)), + ('status', models.CharField(choices=[('pending', 'Pending'), ('approved', 'Approved'), ('rejected', 'Rejected')], default='pending', max_length=10)), + ('change_logs', models.JSONField(blank=True, default=list, null=True)), + ('reviewer', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='review', to=settings.AUTH_USER_MODEL)), + ('subscriber', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='request', to=settings.AUTH_USER_MODEL)), + ('subscription', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='impresso.datasetbitmapposition')), + ], + options={ + 'verbose_name': 'User Subscription Request', + 'verbose_name_plural': 'User Subscription Requests', + 'unique_together': {('subscriber', 'subscription')}, + }, + ), + ] diff --git a/impresso/migrations/0044_rename_change_logs_userrequest_changelog_and_more.py b/impresso/migrations/0044_rename_change_logs_userrequest_changelog_and_more.py new file mode 100644 index 0000000..179e2e8 --- /dev/null +++ b/impresso/migrations/0044_rename_change_logs_userrequest_changelog_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.8 on 2024-10-14 14:40 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('impresso', '0043_userrequest'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.RenameField( + model_name='userrequest', + old_name='change_logs', + new_name='changelog', + ), + migrations.AlterField( + model_name='userrequest', + name='reviewer', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='review', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/impresso/migrations/0045_datasetbitmapposition_reviewer.py b/impresso/migrations/0045_datasetbitmapposition_reviewer.py new file mode 100644 index 0000000..f8a6424 --- /dev/null +++ b/impresso/migrations/0045_datasetbitmapposition_reviewer.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.8 on 2024-10-15 14:07 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('impresso', '0044_rename_change_logs_userrequest_changelog_and_more'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='datasetbitmapposition', + name='reviewer', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reviewed_datasets', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/impresso/migrations/0046_rename_subscriber_userrequest_user_and_more.py b/impresso/migrations/0046_rename_subscriber_userrequest_user_and_more.py new file mode 100644 index 0000000..599830b --- /dev/null +++ b/impresso/migrations/0046_rename_subscriber_userrequest_user_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.8 on 2024-10-16 09:24 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('impresso', '0045_datasetbitmapposition_reviewer'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.RenameField( + model_name='userrequest', + old_name='subscriber', + new_name='user', + ), + migrations.AlterUniqueTogether( + name='userrequest', + unique_together={('user', 'subscription')}, + ), + ] diff --git a/impresso/migrations/0047_userrequest_notes.py b/impresso/migrations/0047_userrequest_notes.py new file mode 100644 index 0000000..9c4e6e9 --- /dev/null +++ b/impresso/migrations/0047_userrequest_notes.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.8 on 2024-10-16 09:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('impresso', '0046_rename_subscriber_userrequest_user_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='userrequest', + name='notes', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/impresso/models/__init__.py b/impresso/models/__init__.py index 408ac14..a680cae 100644 --- a/impresso/models/__init__.py +++ b/impresso/models/__init__.py @@ -17,3 +17,4 @@ from .datasetBitmapPosition import DatasetBitmapPosition from .userBitmap import UserBitmap +from .userRequest import UserRequest diff --git a/impresso/models/datasetBitmapPosition.py b/impresso/models/datasetBitmapPosition.py index 7fbebba..5474973 100644 --- a/impresso/models/datasetBitmapPosition.py +++ b/impresso/models/datasetBitmapPosition.py @@ -10,6 +10,13 @@ class DatasetBitmapPosition(models.Model): blank=True, ) metadata = models.JSONField(default=dict, blank=True) + reviewer = models.ForeignKey( + "auth.User", + on_delete=models.SET_NULL, + related_name="reviewed_datasets", + null=True, + blank=True, + ) def __str__(self): return self.name diff --git a/impresso/models/userRequest.py b/impresso/models/userRequest.py new file mode 100644 index 0000000..5139fa9 --- /dev/null +++ b/impresso/models/userRequest.py @@ -0,0 +1,54 @@ +from django.db import models +from django.contrib.auth.models import User +from .datasetBitmapPosition import DatasetBitmapPosition + + +class UserRequest(models.Model): + STATUS_PENDING = "pending" + STATUS_APPROVED = "approved" + STATUS_REJECTED = "rejected" + + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="request") + reviewer = models.ForeignKey( + User, on_delete=models.SET_NULL, related_name="review", null=True, blank=True + ) + subscription = models.ForeignKey( + DatasetBitmapPosition, on_delete=models.SET_NULL, null=True + ) + date_created = models.DateTimeField(auto_now_add=True) + date_last_modified = models.DateTimeField(auto_now=True) + status = models.CharField( + max_length=10, + default=STATUS_PENDING, + choices=( + (STATUS_PENDING, "Pending"), + (STATUS_APPROVED, "Approved"), + (STATUS_REJECTED, "Rejected"), + ), + ) + changelog = models.JSONField(null=True, blank=True, default=list) + notes = models.TextField(null=True, blank=True) + + def __str__(self): + return f"{self.user.username} Request for {self.subscription.name if self.subscription else '[deleted subscription]'}" + + class Meta: + unique_together = ("user", "subscription") + verbose_name = "User Subscription Request" + verbose_name_plural = "User Subscription Requests" + + def save(self, *args, **kwargs): + if self.pk: + # Prepare the new changelog entry + changelog_entry = { + "status": self.status, + "subscription": self.subscription.name if self.subscription else None, + "date": self.date_last_modified.isoformat(), + "reviewer": self.reviewer.username if self.reviewer else None, + "notes": self.notes if self.notes else "", + } + + # Append the new entry to the changelog list + self.changelog.append(changelog_entry) + + super().save(*args, **kwargs) diff --git a/impresso/tasks.py b/impresso/tasks.py index 94c3c72..e57dfac 100644 --- a/impresso/tasks.py +++ b/impresso/tasks.py @@ -26,6 +26,7 @@ from .utils.tasks.account import send_emails_after_user_registration from .utils.tasks.account import send_emails_after_user_activation from .utils.tasks.account import send_email_password_reset +from .utils.tasks.userBitmap import update_user_bitmap logger = get_task_logger(__name__) @@ -1027,6 +1028,15 @@ def update_collection( items_ids=items_ids_to_remove, method=METHOD_DEL_FROM_INDEX, ) - if items_ids_to_add or items_ids_to_remove: - # update count items in collection (db) - count_items_in_collection.delay(collection_id=collection_id) + # update count items in collection (db) + count_items_in_collection.delay(collection_id=collection_id) + + +@app.task(bind=True) +def update_user_bitmap_task(self, user_id): + """ + Update the user bitmap for the given user. + """ + logger.info(f"User bitmap update request for user {user_id}") + update_user_bitmap(user_id=user_id) + return diff --git a/impresso/utils/tasks/userBitmap.py b/impresso/utils/tasks/userBitmap.py new file mode 100644 index 0000000..2eda935 --- /dev/null +++ b/impresso/utils/tasks/userBitmap.py @@ -0,0 +1,9 @@ +from ...models import UserBitmap + + +def update_user_bitmap(user_id): + user_bitmap = UserBitmap.objects.get(user_id=user_id) + bitmap = user_bitmap.get_up_to_date_bitmap() + bitmap_bytes = bitmap.to_bytes((user_bitmap.bit_length() + 7) // 8, byteorder="big") + user_bitmap.bitmap = bitmap_bytes + user_bitmap.save()