Skip to content

Commit

Permalink
Merge pull request #875 from ae-utbm/taiste
Browse files Browse the repository at this point in the history
Send mail to inactive users, fix user accounts and webpack sas
  • Loading branch information
imperosol authored Oct 12, 2024
2 parents cbcdc61 + 768e286 commit 19e21c8
Show file tree
Hide file tree
Showing 24 changed files with 1,015 additions and 496 deletions.
22 changes: 17 additions & 5 deletions core/baker_recipes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from datetime import timedelta

from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.utils.timezone import now
from django.utils.timezone import localdate, now
from model_bakery import seq
from model_bakery.recipe import Recipe, related

Expand All @@ -11,13 +12,13 @@

active_subscription = Recipe(
Subscription,
subscription_start=now() - timedelta(days=30),
subscription_end=now() + timedelta(days=30),
subscription_start=localdate() - timedelta(days=30),
subscription_end=localdate() + timedelta(days=30),
)
ended_subscription = Recipe(
Subscription,
subscription_start=now() - timedelta(days=60),
subscription_end=now() - timedelta(days=30),
subscription_start=localdate() - timedelta(days=60),
subscription_end=localdate() - timedelta(days=30),
)

subscriber_user = Recipe(
Expand All @@ -36,6 +37,17 @@
)
"""A user with an ended subscription."""

__inactivity = localdate() - settings.SITH_ACCOUNT_INACTIVITY_DELTA
very_old_subscriber_user = old_subscriber_user.extend(
subscriptions=related(
ended_subscription.extend(
subscription_start=__inactivity - relativedelta(months=6, days=1),
subscription_end=__inactivity - relativedelta(days=1),
)
)
)
"""A user which subscription ended enough time ago to be considered as inactive."""

ae_board_membership = Recipe(
Membership,
start_date=now() - timedelta(days=30),
Expand Down
15 changes: 15 additions & 0 deletions core/migrations/0039_alter_user_managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Generated by Django 4.2.16 on 2024-10-06 14:52

from django.db import migrations

import core.models


class Migration(migrations.Migration):
dependencies = [("core", "0038_alter_preferences_receive_weekmail")]

operations = [
migrations.AlterModelManagers(
name="user", managers=[("objects", core.models.CustomUserManager())]
)
]
97 changes: 58 additions & 39 deletions core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,12 @@
import logging
import os
import unicodedata
from datetime import date, timedelta
from datetime import timedelta
from pathlib import Path
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Any, Optional, Self

from django.conf import settings
from django.contrib.auth.models import (
AbstractBaseUser,
UserManager,
)
from django.contrib.auth.models import AbstractBaseUser, UserManager
from django.contrib.auth.models import (
AnonymousUser as AuthAnonymousUser,
)
Expand All @@ -51,15 +48,18 @@
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.mail import send_mail
from django.db import models, transaction
from django.db.models import Exists, OuterRef, Q
from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.html import escape
from django.utils.timezone import localdate, now
from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField
from pydantic.v1 import NonNegativeInt

if TYPE_CHECKING:
from pydantic import NonNegativeInt

from club.models import Club


Expand Down Expand Up @@ -91,15 +91,15 @@ class Group(AuthGroup):
class Meta:
ordering = ["name"]

def get_absolute_url(self):
def get_absolute_url(self) -> str:
return reverse("core:group_list")

def save(self, *args, **kwargs):
def save(self, *args, **kwargs) -> None:
super().save(*args, **kwargs)
cache.set(f"sith_group_{self.id}", self)
cache.set(f"sith_group_{self.name.replace(' ', '_')}", self)

def delete(self, *args, **kwargs):
def delete(self, *args, **kwargs) -> None:
super().delete(*args, **kwargs)
cache.delete(f"sith_group_{self.id}")
cache.delete(f"sith_group_{self.name.replace(' ', '_')}")
Expand Down Expand Up @@ -164,17 +164,17 @@ class Meta:
proxy = True


def validate_promo(value):
def validate_promo(value: int) -> None:
start_year = settings.SITH_SCHOOL_START_YEAR
delta = (date.today() + timedelta(days=180)).year - start_year
delta = (localdate() + timedelta(days=180)).year - start_year
if value < 0 or delta < value:
raise ValidationError(
_("%(value)s is not a valid promo (between 0 and %(end)s)"),
params={"value": value, "end": delta},
)


def get_group(*, pk: int = None, name: str = None) -> Optional[Group]:
def get_group(*, pk: int = None, name: str = None) -> Group | None:
"""Search for a group by its primary key or its name.
Either one of the two must be set.
Expand Down Expand Up @@ -216,6 +216,31 @@ def get_group(*, pk: int = None, name: str = None) -> Optional[Group]:
return group


class UserQuerySet(models.QuerySet):
def filter_inactive(self) -> Self:
from counter.models import Refilling, Selling
from subscription.models import Subscription

threshold = now() - settings.SITH_ACCOUNT_INACTIVITY_DELTA
subscriptions = Subscription.objects.filter(
member_id=OuterRef("pk"), subscription_end__gt=localdate(threshold)
)
refills = Refilling.objects.filter(
customer__user_id=OuterRef("pk"), date__gt=threshold
)
purchases = Selling.objects.filter(
customer__user_id=OuterRef("pk"), date__gt=threshold
)
return self.exclude(
Q(Exists(subscriptions)) | Q(Exists(refills)) | Q(Exists(purchases))
)


class CustomUserManager(UserManager.from_queryset(UserQuerySet)):
# see https://docs.djangoproject.com/fr/stable/topics/migrations/#model-managers
pass


class User(AbstractBaseUser):
"""Defines the base user class, useable in every app.
Expand Down Expand Up @@ -373,36 +398,41 @@ class User(AbstractBaseUser):
)
godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True)

objects = UserManager()
objects = CustomUserManager()

USERNAME_FIELD = "username"

def promo_has_logo(self):
def __str__(self):
return self.get_display_name()

def save(self, *args, **kwargs):
with transaction.atomic():
if self.id:
old = User.objects.filter(id=self.id).first()
if old and old.username != self.username:
self._change_username(self.username)
super().save(*args, **kwargs)

def get_absolute_url(self) -> str:
return reverse("core:user_profile", kwargs={"user_id": self.pk})

def promo_has_logo(self) -> bool:
return Path(
settings.BASE_DIR / f"core/static/core/img/promo_{self.promo}.png"
).exists()

def has_module_perms(self, package_name):
def has_module_perms(self, package_name: str) -> bool:
return self.is_active

def has_perm(self, perm, obj=None):
def has_perm(self, perm: str, obj: Any = None) -> bool:
return self.is_active and self.is_superuser

def get_absolute_url(self):
return reverse("core:user_profile", kwargs={"user_id": self.pk})

def __str__(self):
return self.get_display_name()

def to_dict(self):
return self.__dict__

@cached_property
def was_subscribed(self):
def was_subscribed(self) -> bool:
return self.subscriptions.exists()

@cached_property
def is_subscribed(self):
def is_subscribed(self) -> bool:
s = self.subscriptions.filter(
subscription_start__lte=timezone.now(), subscription_end__gte=timezone.now()
)
Expand Down Expand Up @@ -542,17 +572,6 @@ def age(self) -> int:
)
return age

def save(self, *args, **kwargs):
create = False
with transaction.atomic():
if self.id:
old = User.objects.filter(id=self.id).first()
if old and old.username != self.username:
self._change_username(self.username)
else:
create = True
super().save(*args, **kwargs)

def make_home(self):
if self.home is None:
home_root = SithFile.objects.filter(parent=None, name="users").first()
Expand Down
49 changes: 0 additions & 49 deletions core/static/core/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,52 +74,3 @@ function displayNotif() {
function getCSRFToken() {
return $("[name=csrfmiddlewaretoken]").val();
}

// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
const initialUrlParams = new URLSearchParams(window.location.search);

/**
* @readonly
* @enum {number}
*/
const History = {
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
NONE: 0,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
PUSH: 1,
// biome-ignore lint/style/useNamingConvention: this feels more like an enum
REPLACE: 2,
};

/**
* @param {string} key
* @param {string | string[] | null} value
* @param {History} action
* @param {URL | null} url
*/
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
function updateQueryString(key, value, action = History.REPLACE, url = null) {
let ret = url;
if (!ret) {
ret = new URL(window.location.href);
}
if (value === undefined || value === null || value === "") {
// If the value is null, undefined or empty => delete it
ret.searchParams.delete(key);
} else if (Array.isArray(value)) {
ret.searchParams.delete(key);
for (const v of value) {
ret.searchParams.append(key, v);
}
} else {
ret.searchParams.set(key, value);
}

if (action === History.PUSH) {
window.history.pushState(null, "", ret.toString());
} else if (action === History.REPLACE) {
window.history.replaceState(null, "", ret.toString());
}

return ret;
}
9 changes: 3 additions & 6 deletions core/static/webpack/user/family-graph-index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { History, initialUrlParams, updateQueryString } from "#core:utils/history";
import cytoscape from "cytoscape";
import cxtmenu from "cytoscape-cxtmenu";
import klay from "cytoscape-klay";
Expand Down Expand Up @@ -184,7 +185,6 @@ window.loadFamilyGraph = (config) => {
const defaultDepth = 2;

function getInitialDepth(prop) {
// biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js
const value = Number.parseInt(initialUrlParams.get(prop));
if (Number.isNaN(value) || value < config.depthMin || value > config.depthMax) {
return defaultDepth;
Expand All @@ -196,7 +196,6 @@ window.loadFamilyGraph = (config) => {
loading: false,
godfathersDepth: getInitialDepth("godfathersDepth"),
godchildrenDepth: getInitialDepth("godchildrenDepth"),
// biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js
reverse: initialUrlParams.get("reverse")?.toLowerCase?.() === "true",
graph: undefined,
graphData: {},
Expand All @@ -210,14 +209,12 @@ window.loadFamilyGraph = (config) => {
if (value < config.depthMin || value > config.depthMax) {
return;
}
// biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js
updateQueryString(param, value, History.REPLACE);
updateQueryString(param, value, History.Replace);
await delayedFetch();
});
}
this.$watch("reverse", async (value) => {
// biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js
updateQueryString("reverse", value, History.REPLACE);
updateQueryString("reverse", value, History.Replace);
await this.reverseGraph();
});
this.$watch("graphData", async () => {
Expand Down
10 changes: 6 additions & 4 deletions core/static/webpack/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ export const paginated = async <T>(
options?: PaginatedRequest,
) => {
const maxPerPage = 199;
options.query.page_size = maxPerPage;
options.query.page = 1;
const queryParams = options ?? {};
queryParams.query = queryParams.query ?? {};
queryParams.query.page_size = maxPerPage;
queryParams.query.page = 1;

const firstPage = (await endpoint(options)).data;
const firstPage = (await endpoint(queryParams)).data;
const results = firstPage.results;

const nbElements = firstPage.count;
Expand All @@ -39,7 +41,7 @@ export const paginated = async <T>(
if (nbPages > 1) {
const promises: Promise<T[]>[] = [];
for (let i = 2; i <= nbPages; i++) {
const nextPage = structuredClone(options);
const nextPage = structuredClone(queryParams);
nextPage.query.page = i;
promises.push(endpoint(nextPage).then((res) => res.data.results));
}
Expand Down
Loading

0 comments on commit 19e21c8

Please sign in to comment.