Skip to content

Commit

Permalink
Feature/upgrade emails for plans (#80)
Browse files Browse the repository at this point in the history
* create automatically groups for User Plans in  impresso.apps.ImpressoConfig (apps.py)
* Create UserChangePlanRequest model and related migrations
* Update admin.py to get the new model UserChangePlanRequest
* add email templates per group and user role
* add test, typings and doc to email methods, also including send_email_password_reset 
* upgrade patch version django

* Add specific task to add an user to a group in tasks.py

* Create post_save signal to ship celery task in externa module signals.py

* fix user request plan admin layout

* add missing templates for accepted and rejected emails

* add documentation on UserRequest model

* delegate group addition to celery task to prevent LOCK mysql error
  • Loading branch information
danieleguido authored Dec 23, 2024
1 parent 630d68e commit 6a39fb0
Show file tree
Hide file tree
Showing 29 changed files with 1,291 additions and 38 deletions.
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pip = "*"
celery = "*"
requests = "*"
redis = "*"
django = "==5.1.3"
django = "==5.1.4"
pymysql = "*"
django-registration = "*"
gunicorn = "*"
Expand Down
38 changes: 19 additions & 19 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion impresso/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

__all__ = ('celery_app',)
__all__ = ("celery_app",)
73 changes: 70 additions & 3 deletions impresso/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from django import forms
from django.contrib import admin
from django.contrib import messages
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
Expand All @@ -10,9 +9,11 @@
from .models import Collection, CollectableItem, Tag, TaggableItem
from .models import Attachment, UploadedImage
from .models import UserBitmap, DatasetBitmapPosition, UserRequest

from .models import UserChangePlanRequest
from impresso.tasks import after_user_activation

from django.utils.html import format_html


@admin.register(UserRequest)
class UserRequestAdmin(admin.ModelAdmin):
Expand All @@ -23,11 +24,77 @@ class UserRequestAdmin(admin.ModelAdmin):
"status",
"date_created",
)
search_fields = ["subscriber__username", "subscription__name"]
search_fields = ["user__username", "subscription__name"]
list_filter = ["status"]
autocomplete_fields = ["user", "reviewer", "subscription"]


@admin.register(UserChangePlanRequest)
class UserChangePlanRequestAdmin(admin.ModelAdmin):
search_fields = ["user__username", "user__last_name"]
list_filter = ["status"]
search_help_text = "Search by requester user id (numeric) or username"
list_display = ("user", "plan", "status", "date_created", "changelog_parsed")
autocomplete_fields = ["user", "plan"]
actions = ["approve_requests", "reject_requests"]

def changelog_parsed(self, obj):
try:
html = "<ul style='padding:0'>"
for entry in obj.changelog:
date = timezone.datetime.fromisoformat(entry["date"]).strftime(
"%Y-%m-%d %H:%M:%S"
)

html += (
f"<li><b>{entry['plan']}</b><br/>{date} ({entry['status']})</li>"
)
html += "</ul>"
return format_html(html)
except AttributeError as e:
return f"Changelog error: {e}"
except (TypeError, ValueError):
return "Invalid JSON"

changelog_parsed.short_description = "Changes"

@admin.action(description="APPROVE selected requests")
def approve_requests(self, request, queryset):
updated = queryset.count()
for req in queryset:
req.status = UserChangePlanRequest.STATUS_APPROVED
# post_save method in impresso.signals already include the code to add the user the Plan Group.
req.save()
self.message_user(
request,
ngettext(
"%d request was successfully approved.",
"%d requests were successfully approved.",
updated,
)
% updated,
messages.SUCCESS,
)

@admin.action(description="REJECT selected requests")
def reject_requests(self, request, queryset):
updated = queryset.count()
for req in queryset:
req.status = UserChangePlanRequest.STATUS_REJECTED
# post_save() method in impresso.signals already includes the code to remove the Plan Group.
req.save()
self.message_user(
request,
ngettext(
"%d request was successfully rejected.",
"%d requests were successfully rejected.",
updated,
)
% updated,
messages.SUCCESS,
)


@admin.register(UserBitmap)
class UserBitmapAdmin(admin.ModelAdmin):
list_display = (
Expand Down
23 changes: 23 additions & 0 deletions impresso/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.apps import AppConfig


class ImpressoConfig(AppConfig):
name = "impresso"
verbose_name = "Impresso"

def ready(self):
# we import the signal handler inside the ready() method to avoid import issues
from django.db.models.signals import post_migrate, post_save, m2m_changed
from impresso.models import UserBitmap
from django.contrib.auth.models import User
from .signals import (
create_default_groups,
post_save_user_change_plan_request,
)

post_migrate.connect(create_default_groups, sender="impresso")

post_save.connect(
post_save_user_change_plan_request,
sender="impresso.UserChangePlanRequest",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Generated by Django 5.1.4 on 2024-12-19 11:12

import django.contrib.auth.models
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('impresso', '0050_alter_job_type'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='UserRequestingChangePlan',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('auth.user',),
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='UserChangePlanRequest',
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)),
('changelog', models.JSONField(blank=True, default=list, null=True)),
('notes', models.TextField(blank=True, null=True)),
('plan', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='auth.group')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='changePlanRequest', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'User Change Plan Request',
'verbose_name_plural': 'User Change Plan Requests',
'unique_together': {('user', 'plan')},
},
),
]
16 changes: 16 additions & 0 deletions impresso/migrations/0052_delete_userrequestingchangeplan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Django 5.1.4 on 2024-12-19 12:29

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('impresso', '0051_userrequestingchangeplan_userchangeplanrequest'),
]

operations = [
migrations.DeleteModel(
name='UserRequestingChangePlan',
),
]
21 changes: 21 additions & 0 deletions impresso/migrations/0053_alter_userchangeplanrequest_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.1.4 on 2024-12-20 16:02

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('impresso', '0052_delete_userrequestingchangeplan'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.AlterField(
model_name='userchangeplanrequest',
name='user',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='changePlanRequest', to=settings.AUTH_USER_MODEL),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.1.4 on 2024-12-20 16:04

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('impresso', '0053_alter_userchangeplanrequest_user'),
]

operations = [
migrations.AlterUniqueTogether(
name='userchangeplanrequest',
unique_together=set(),
),
]
1 change: 1 addition & 0 deletions impresso/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
from .datasetBitmapPosition import DatasetBitmapPosition
from .userBitmap import UserBitmap
from .userRequest import UserRequest
from .userChangePlanRequest import UserChangePlanRequest
Loading

0 comments on commit 6a39fb0

Please sign in to comment.