Merge pull request #929 from ae-utbm/user-model

Migrate User parent class from AbstractBaseUser to AbstractUser
This commit is contained in:
Bartuccio Antoine 2024-12-19 14:27:16 +01:00 committed by GitHub
commit ddeb12f08c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 413 additions and 521 deletions

View File

@ -216,7 +216,7 @@ class TestOperation(TestCase):
self.journal.operations.filter(target_label="Le fantome du jour").exists() self.journal.operations.filter(target_label="Le fantome du jour").exists()
) )
def test__operation_simple_accounting(self): def test_operation_simple_accounting(self):
sat = SimplifiedAccountingType.objects.all().first() sat = SimplifiedAccountingType.objects.all().first()
response = self.client.post( response = self.client.post(
reverse("accounting:op_new", args=[self.journal.id]), reverse("accounting:op_new", args=[self.journal.id]),
@ -237,15 +237,14 @@ class TestOperation(TestCase):
"done": False, "done": False,
}, },
) )
self.assertFalse(response.status_code == 403) assert response.status_code != 403
self.assertTrue(self.journal.operations.filter(amount=23).exists()) assert self.journal.operations.filter(amount=23).exists()
response_get = self.client.get( response_get = self.client.get(
reverse("accounting:journal_details", args=[self.journal.id]) reverse("accounting:journal_details", args=[self.journal.id])
) )
self.assertTrue( assert "<td>Le fantome de l&#39;aurore</td>" in str(response_get.content)
"<td>Le fantome de l&#39;aurore</td>" in str(response_get.content)
) assert (
self.assertTrue(
self.journal.operations.filter(amount=23) self.journal.operations.filter(amount=23)
.values("accounting_type") .values("accounting_type")
.first()["accounting_type"] .first()["accounting_type"]

View File

@ -215,17 +215,14 @@ class JournalTabsMixin(TabedViewMixin):
return _("Journal") return _("Journal")
def get_list_of_tabs(self): def get_list_of_tabs(self):
tab_list = [] return [
tab_list.append(
{ {
"url": reverse( "url": reverse(
"accounting:journal_details", kwargs={"j_id": self.object.id} "accounting:journal_details", kwargs={"j_id": self.object.id}
), ),
"slug": "journal", "slug": "journal",
"name": _("Journal"), "name": _("Journal"),
} },
)
tab_list.append(
{ {
"url": reverse( "url": reverse(
"accounting:journal_nature_statement", "accounting:journal_nature_statement",
@ -233,9 +230,7 @@ class JournalTabsMixin(TabedViewMixin):
), ),
"slug": "nature_statement", "slug": "nature_statement",
"name": _("Statement by nature"), "name": _("Statement by nature"),
} },
)
tab_list.append(
{ {
"url": reverse( "url": reverse(
"accounting:journal_person_statement", "accounting:journal_person_statement",
@ -243,9 +238,7 @@ class JournalTabsMixin(TabedViewMixin):
), ),
"slug": "person_statement", "slug": "person_statement",
"name": _("Statement by person"), "name": _("Statement by person"),
} },
)
tab_list.append(
{ {
"url": reverse( "url": reverse(
"accounting:journal_accounting_statement", "accounting:journal_accounting_statement",
@ -253,9 +246,8 @@ class JournalTabsMixin(TabedViewMixin):
), ),
"slug": "accounting_statement", "slug": "accounting_statement",
"name": _("Accounting statement"), "name": _("Accounting statement"),
} },
) ]
return tab_list
class JournalCreateView(CanCreateMixin, CreateView): class JournalCreateView(CanCreateMixin, CreateView):

View File

@ -3,19 +3,6 @@ from __future__ import unicode_literals
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models
from club.models import Club
from core.operations import PsqlRunOnly
def generate_club_pages(apps, schema_editor):
def recursive_generate_club_page(club):
club.make_page()
for child in Club.objects.filter(parent=club).all():
recursive_generate_club_page(child)
for club in Club.objects.filter(parent=None).all():
recursive_generate_club_page(club)
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("core", "0024_auto_20170906_1317"), ("club", "0010_club_logo")] dependencies = [("core", "0024_auto_20170906_1317"), ("club", "0010_club_logo")]
@ -48,11 +35,4 @@ class Migration(migrations.Migration):
null=True, null=True,
), ),
), ),
PsqlRunOnly(
"SET CONSTRAINTS ALL IMMEDIATE", reverse_sql=migrations.RunSQL.noop
),
migrations.RunPython(generate_club_pages),
PsqlRunOnly(
migrations.RunSQL.noop, reverse_sql="SET CONSTRAINTS ALL IMMEDIATE"
),
] ]

View File

@ -31,14 +31,14 @@ from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import RegexValidator, validate_email from django.core.validators import RegexValidator, validate_email
from django.db import models, transaction from django.db import models, transaction
from django.db.models import Q from django.db.models import Exists, OuterRef, Q
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import localdate from django.utils.timezone import localdate
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from core.models import Group, MetaGroup, Notification, Page, RealGroup, SithFile, User from core.models import Group, MetaGroup, Notification, Page, SithFile, User
# Create your models here. # Create your models here.
@ -438,19 +438,18 @@ class Mailing(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.is_moderated: if not self.is_moderated:
for user in ( unread_notif_subquery = Notification.objects.filter(
RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) user=OuterRef("pk"), type="MAILING_MODERATION", viewed=False
.first() )
.users.all() for user in User.objects.filter(
~Exists(unread_notif_subquery),
groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID],
): ):
if not user.notifications.filter( Notification(
type="MAILING_MODERATION", viewed=False user=user,
).exists(): url=reverse("com:mailing_admin"),
Notification( type="MAILING_MODERATION",
user=user, ).save(*args, **kwargs)
url=reverse("com:mailing_admin"),
type="MAILING_MODERATION",
).save(*args, **kwargs)
super().save(*args, **kwargs) super().save(*args, **kwargs)
def clean(self): def clean(self):

View File

@ -34,7 +34,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from club.models import Club from club.models import Club
from core.models import Notification, Preferences, RealGroup, User from core.models import Notification, Preferences, User
class Sith(models.Model): class Sith(models.Model):
@ -108,17 +108,15 @@ class News(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super().save(*args, **kwargs) super().save(*args, **kwargs)
for u in ( for user in User.objects.filter(
RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID]
.first()
.users.all()
): ):
Notification( Notification.objects.create(
user=u, user=user,
url=reverse("com:news_admin_list"), url=reverse("com:news_admin_list"),
type="NEWS_MODERATION", type="NEWS_MODERATION",
param="1", param="1",
).save() )
def get_absolute_url(self): def get_absolute_url(self):
return reverse("com:news_detail", kwargs={"news_id": self.id}) return reverse("com:news_detail", kwargs={"news_id": self.id})
@ -336,16 +334,14 @@ class Poster(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.is_moderated: if not self.is_moderated:
for u in ( for user in User.objects.filter(
RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID]
.first()
.users.all()
): ):
Notification( Notification.objects.create(
user=u, user=user,
url=reverse("com:poster_moderate_list"), url=reverse("com:poster_moderate_list"),
type="POSTER_MODERATION", type="POSTER_MODERATION",
).save() )
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):

View File

@ -23,7 +23,7 @@ from django.utils.translation import gettext as _
from club.models import Club, Membership from club.models import Club, Membership
from com.models import News, Poster, Sith, Weekmail, WeekmailArticle from com.models import News, Poster, Sith, Weekmail, WeekmailArticle
from core.models import AnonymousUser, RealGroup, User from core.models import AnonymousUser, Group, User
@pytest.fixture() @pytest.fixture()
@ -49,9 +49,7 @@ class TestCom(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.skia = User.objects.get(username="skia") cls.skia = User.objects.get(username="skia")
cls.com_group = RealGroup.objects.filter( cls.com_group = Group.objects.get(id=settings.SITH_GROUP_COM_ADMIN_ID)
id=settings.SITH_GROUP_COM_ADMIN_ID
).first()
cls.skia.groups.set([cls.com_group]) cls.skia.groups.set([cls.com_group])
def setUp(self): def setUp(self):

View File

@ -28,7 +28,7 @@ from smtplib import SMTPRecipientsRefused
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.db.models import Max from django.db.models import Exists, Max, OuterRef
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
@ -42,7 +42,7 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView
from club.models import Club, Mailing from club.models import Club, Mailing
from com.models import News, NewsDate, Poster, Screen, Sith, Weekmail, WeekmailArticle from com.models import News, NewsDate, Poster, Screen, Sith, Weekmail, WeekmailArticle
from core.models import Notification, RealGroup, User from core.models import Notification, User
from core.views import ( from core.views import (
CanCreateMixin, CanCreateMixin,
CanEditMixin, CanEditMixin,
@ -278,21 +278,18 @@ class NewsEditView(CanEditMixin, UpdateView):
else: else:
self.object.is_moderated = False self.object.is_moderated = False
self.object.save() self.object.save()
for u in ( unread_notif_subquery = Notification.objects.filter(
RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) user=OuterRef("pk"), type="NEWS_MODERATION", viewed=False
.first() )
.users.all() for user in User.objects.filter(
~Exists(unread_notif_subquery),
groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID],
): ):
if not u.notifications.filter( Notification.objects.create(
type="NEWS_MODERATION", viewed=False user=user,
).exists(): url=self.object.get_absolute_url(),
Notification( type="NEWS_MODERATION",
user=u, )
url=reverse(
"com:news_detail", kwargs={"news_id": self.object.id}
),
type="NEWS_MODERATION",
).save()
return super().form_valid(form) return super().form_valid(form)
@ -323,19 +320,18 @@ class NewsCreateView(CanCreateMixin, CreateView):
self.object.is_moderated = True self.object.is_moderated = True
self.object.save() self.object.save()
else: else:
for u in ( unread_notif_subquery = Notification.objects.filter(
RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) user=OuterRef("pk"), type="NEWS_MODERATION", viewed=False
.first() )
.users.all() for user in User.objects.filter(
~Exists(unread_notif_subquery),
groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID],
): ):
if not u.notifications.filter( Notification.objects.create(
type="NEWS_MODERATION", viewed=False user=user,
).exists(): url=reverse("com:news_admin_list"),
Notification( type="NEWS_MODERATION",
user=u, )
url=reverse("com:news_admin_list"),
type="NEWS_MODERATION",
).save()
return super().form_valid(form) return super().form_valid(form)

View File

@ -261,19 +261,19 @@ class Command(BaseCommand):
User.groups.through.objects.bulk_create( User.groups.through.objects.bulk_create(
[ [
User.groups.through( User.groups.through(
realgroup_id=settings.SITH_GROUP_COUNTER_ADMIN_ID, user=counter group_id=settings.SITH_GROUP_COUNTER_ADMIN_ID, user=counter
), ),
User.groups.through( User.groups.through(
realgroup_id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID, user=comptable group_id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID, user=comptable
), ),
User.groups.through( User.groups.through(
realgroup_id=settings.SITH_GROUP_COM_ADMIN_ID, user=comunity group_id=settings.SITH_GROUP_COM_ADMIN_ID, user=comunity
), ),
User.groups.through( User.groups.through(
realgroup_id=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID, user=tutu group_id=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID, user=tutu
), ),
User.groups.through( User.groups.through(
realgroup_id=settings.SITH_GROUP_SAS_ADMIN_ID, user=skia group_id=settings.SITH_GROUP_SAS_ADMIN_ID, user=skia
), ),
] ]
) )

View File

@ -11,7 +11,7 @@ from django.utils.timezone import localdate, make_aware, now
from faker import Faker from faker import Faker
from club.models import Club, Membership from club.models import Club, Membership
from core.models import RealGroup, User from core.models import Group, User
from counter.models import ( from counter.models import (
Counter, Counter,
Customer, Customer,
@ -225,9 +225,7 @@ class Command(BaseCommand):
ae = Club.objects.get(unix_name="ae") ae = Club.objects.get(unix_name="ae")
other_clubs = random.sample(list(Club.objects.all()), k=3) other_clubs = random.sample(list(Club.objects.all()), k=3)
groups = list( groups = list(
RealGroup.objects.filter( Group.objects.filter(name__in=["Subscribers", "Old subscribers", "Public"])
name__in=["Subscribers", "Old subscribers", "Public"]
)
) )
counters = list( counters = list(
Counter.objects.filter(name__in=["Foyer", "MDE", "La Gommette", "Eboutic"]) Counter.objects.filter(name__in=["Foyer", "MDE", "La Gommette", "Eboutic"])

View File

@ -0,0 +1,82 @@
# Generated by Django 4.2.16 on 2024-11-20 16:22
import django.contrib.auth.validators
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
("core", "0039_alter_user_managers"),
]
operations = [
migrations.AlterModelOptions(
name="user",
options={"verbose_name": "user", "verbose_name_plural": "users"},
),
migrations.AddField(
model_name="user",
name="user_permissions",
field=models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.permission",
verbose_name="user permissions",
),
),
migrations.AlterField(
model_name="user",
name="date_joined",
field=models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
migrations.AlterField(
model_name="user",
name="is_superuser",
field=models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
migrations.AlterField(
model_name="user",
name="username",
field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
verbose_name="username",
),
),
migrations.AlterField(
model_name="user",
name="groups",
field=models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.group",
verbose_name="groups",
),
),
migrations.AlterField(
model_name="user",
name="groups",
field=models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="users",
to="core.group",
verbose_name="groups",
),
),
]

View File

@ -30,19 +30,13 @@ import string
import unicodedata import unicodedata
from datetime import timedelta from datetime import timedelta
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional, Self from typing import TYPE_CHECKING, Optional, Self
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser, UserManager from django.contrib.auth.models import AbstractUser, UserManager
from django.contrib.auth.models import ( from django.contrib.auth.models import AnonymousUser as AuthAnonymousUser
AnonymousUser as AuthAnonymousUser, from django.contrib.auth.models import Group as AuthGroup
) from django.contrib.auth.models import GroupManager as AuthGroupManager
from django.contrib.auth.models import (
Group as AuthGroup,
)
from django.contrib.auth.models import (
GroupManager as AuthGroupManager,
)
from django.contrib.staticfiles.storage import staticfiles_storage from django.contrib.staticfiles.storage import staticfiles_storage
from django.core import validators from django.core import validators
from django.core.cache import cache from django.core.cache import cache
@ -242,7 +236,7 @@ class CustomUserManager(UserManager.from_queryset(UserQuerySet)):
pass pass
class User(AbstractBaseUser): class User(AbstractUser):
"""Defines the base user class, useable in every app. """Defines the base user class, useable in every app.
This is almost the same as the auth module AbstractUser since it inherits from it, This is almost the same as the auth module AbstractUser since it inherits from it,
@ -253,51 +247,22 @@ class User(AbstractBaseUser):
Required fields: email, first_name, last_name, date_of_birth Required fields: email, first_name, last_name, date_of_birth
""" """
username = models.CharField(
_("username"),
max_length=254,
unique=True,
help_text=_(
"Required. 254 characters or fewer. Letters, digits and ./+/-/_ only."
),
validators=[
validators.RegexValidator(
r"^[\w.+-]+$",
_(
"Enter a valid username. This value may contain only "
"letters, numbers "
"and ./+/-/_ characters."
),
)
],
error_messages={"unique": _("A user with that username already exists.")},
)
first_name = models.CharField(_("first name"), max_length=64) first_name = models.CharField(_("first name"), max_length=64)
last_name = models.CharField(_("last name"), max_length=64) last_name = models.CharField(_("last name"), max_length=64)
email = models.EmailField(_("email address"), unique=True) email = models.EmailField(_("email address"), unique=True)
date_of_birth = models.DateField(_("date of birth"), blank=True, null=True) date_of_birth = models.DateField(_("date of birth"), blank=True, null=True)
nick_name = models.CharField(_("nick name"), max_length=64, null=True, blank=True) nick_name = models.CharField(_("nick name"), max_length=64, null=True, blank=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
is_active = models.BooleanField(
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
)
date_joined = models.DateField(_("date joined"), auto_now_add=True)
last_update = models.DateTimeField(_("last update"), auto_now=True) last_update = models.DateTimeField(_("last update"), auto_now=True)
is_superuser = models.BooleanField( groups = models.ManyToManyField(
_("superuser"), Group,
default=False, verbose_name=_("groups"),
help_text=_("Designates whether this user is a superuser. "), help_text=_(
"The groups this user belongs to. A user will get all permissions "
"granted to each of their groups."
),
related_name="users",
blank=True,
) )
groups = models.ManyToManyField(RealGroup, related_name="users", blank=True)
home = models.OneToOneField( home = models.OneToOneField(
"SithFile", "SithFile",
related_name="home_of", related_name="home_of",
@ -401,8 +366,6 @@ class User(AbstractBaseUser):
objects = CustomUserManager() objects = CustomUserManager()
USERNAME_FIELD = "username"
def __str__(self): def __str__(self):
return self.get_display_name() return self.get_display_name()
@ -422,22 +385,23 @@ class User(AbstractBaseUser):
settings.BASE_DIR / f"core/static/core/img/promo_{self.promo}.png" settings.BASE_DIR / f"core/static/core/img/promo_{self.promo}.png"
).exists() ).exists()
def has_module_perms(self, package_name: str) -> bool:
return self.is_active
def has_perm(self, perm: str, obj: Any = None) -> bool:
return self.is_active and self.is_superuser
@cached_property @cached_property
def was_subscribed(self) -> bool: def was_subscribed(self) -> bool:
if "is_subscribed" in self.__dict__ and self.is_subscribed:
# if the user is currently subscribed, he is an old subscriber too
# if the property has already been cached, avoid another request
return True
return self.subscriptions.exists() return self.subscriptions.exists()
@cached_property @cached_property
def is_subscribed(self) -> bool: def is_subscribed(self) -> bool:
s = self.subscriptions.filter( if "was_subscribed" in self.__dict__ and not self.was_subscribed:
# if the user never subscribed, he cannot be a subscriber now.
# if the property has already been cached, avoid another request
return False
return self.subscriptions.filter(
subscription_start__lte=timezone.now(), subscription_end__gte=timezone.now() subscription_start__lte=timezone.now(), subscription_end__gte=timezone.now()
) ).exists()
return s.exists()
@cached_property @cached_property
def account_balance(self): def account_balance(self):
@ -599,11 +563,6 @@ class User(AbstractBaseUser):
"date_of_birth": self.date_of_birth, "date_of_birth": self.date_of_birth,
} }
def get_full_name(self):
"""Returns the first_name plus the last_name, with a space in between."""
full_name = "%s %s" % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self): def get_short_name(self):
"""Returns the short name for the user.""" """Returns the short name for the user."""
if self.nick_name: if self.nick_name:
@ -982,13 +941,11 @@ class SithFile(models.Model):
if copy_rights: if copy_rights:
self.copy_rights() self.copy_rights()
if self.is_in_sas: if self.is_in_sas:
for u in ( for user in User.objects.filter(
RealGroup.objects.filter(id=settings.SITH_GROUP_SAS_ADMIN_ID) groups__id__in=[settings.SITH_GROUP_SAS_ADMIN_ID]
.first()
.users.all()
): ):
Notification( Notification(
user=u, user=user,
url=reverse("sas:moderation"), url=reverse("sas:moderation"),
type="SAS_MODERATION", type="SAS_MODERATION",
param="1", param="1",

View File

@ -118,7 +118,9 @@ class TestUserRegistration:
response = client.post(reverse("core:register"), valid_payload) response = client.post(reverse("core:register"), valid_payload)
assert response.status_code == 200 assert response.status_code == 200
error_html = "<li>Un objet User avec ce champ Adresse email existe déjà.</li>" error_html = (
"<li>Un objet Utilisateur avec ce champ Adresse email existe déjà.</li>"
)
assertInHTML(error_html, str(response.content.decode())) assertInHTML(error_html, str(response.content.decode()))
def test_register_fail_with_not_existing_email( def test_register_fail_with_not_existing_email(

View File

@ -14,7 +14,7 @@ from PIL import Image
from pytest_django.asserts import assertNumQueries from pytest_django.asserts import assertNumQueries
from core.baker_recipes import board_user, old_subscriber_user, subscriber_user from core.baker_recipes import board_user, old_subscriber_user, subscriber_user
from core.models import Group, RealGroup, SithFile, User from core.models import Group, SithFile, User
from sas.models import Picture from sas.models import Picture
from sith import settings from sith import settings
@ -26,12 +26,10 @@ class TestImageAccess:
[ [
lambda: baker.make(User, is_superuser=True), lambda: baker.make(User, is_superuser=True),
lambda: baker.make( lambda: baker.make(
User, User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)],
), ),
lambda: baker.make( lambda: baker.make(
User, User, groups=[Group.objects.get(pk=settings.SITH_GROUP_COM_ADMIN_ID)]
groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_COM_ADMIN_ID)],
), ),
], ],
) )

View File

@ -21,6 +21,7 @@ from wsgiref.util import FileWrapper
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models import Exists, OuterRef
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.http import Http404, HttpRequest, HttpResponse from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
@ -31,7 +32,7 @@ from django.views.generic import DetailView, ListView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import DeleteView, FormMixin, UpdateView from django.views.generic.edit import DeleteView, FormMixin, UpdateView
from core.models import Notification, RealGroup, SithFile, User from core.models import Notification, SithFile, User
from core.views import ( from core.views import (
AllowFragment, AllowFragment,
CanEditMixin, CanEditMixin,
@ -159,19 +160,18 @@ class AddFilesForm(forms.Form):
% {"file_name": f, "msg": repr(e)}, % {"file_name": f, "msg": repr(e)},
) )
if notif: if notif:
for u in ( unread_notif_subquery = Notification.objects.filter(
RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) user=OuterRef("pk"), type="FILE_MODERATION", viewed=False
.first() )
.users.all() for user in User.objects.filter(
~Exists(unread_notif_subquery),
groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID],
): ):
if not u.notifications.filter( Notification.objects.create(
type="FILE_MODERATION", viewed=False user=user,
).exists(): url=reverse("core:file_moderation"),
Notification( type="FILE_MODERATION",
user=u, )
url=reverse("core:file_moderation"),
type="FILE_MODERATION",
).save()
class FileListView(ListView): class FileListView(ListView):

View File

@ -167,9 +167,7 @@ class RegisteringForm(UserCreationForm):
class Meta: class Meta:
model = User model = User
fields = ("first_name", "last_name", "email") fields = ("first_name", "last_name", "email")
field_classes = { field_classes = {"email": AntiSpamEmailField}
"email": AntiSpamEmailField,
}
class UserProfileForm(forms.ModelForm): class UserProfileForm(forms.ModelForm):

View File

@ -23,9 +23,7 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView
from core.models import RealGroup, User from core.models import RealGroup, User
from core.views import CanCreateMixin, CanEditMixin, DetailFormView from core.views import CanCreateMixin, CanEditMixin, DetailFormView
from core.views.widgets.select import ( from core.views.widgets.select import AutoCompleteSelectMultipleUser
AutoCompleteSelectMultipleUser,
)
# Forms # Forms

View File

@ -1,38 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
from django.utils.translation import gettext_lazy as _
from core.models import User
from counter.models import Counter, Customer, Product, Selling
def balance_ecocups(apps, schema_editor):
for customer in Customer.objects.all():
customer.recorded_products = 0
for selling in customer.buyings.filter(
product__id__in=[settings.SITH_ECOCUP_CONS, settings.SITH_ECOCUP_DECO]
).all():
if selling.product.is_record_product:
customer.recorded_products += selling.quantity
elif selling.product.is_unrecord_product:
customer.recorded_products -= selling.quantity
if customer.recorded_products < -settings.SITH_ECOCUP_LIMIT:
qt = -(customer.recorded_products + settings.SITH_ECOCUP_LIMIT)
cons = Product.objects.get(id=settings.SITH_ECOCUP_CONS)
Selling(
label=_("Ecocup regularization"),
product=cons,
unit_price=cons.selling_price,
club=cons.club,
counter=Counter.objects.filter(name="Foyer").first(),
quantity=qt,
seller=User.objects.get(id=0),
customer=customer,
).save(allow_negative=True)
customer.recorded_products += qt
customer.save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -44,5 +12,4 @@ class Migration(migrations.Migration):
name="recorded_products", name="recorded_products",
field=models.IntegerField(verbose_name="recorded items", default=0), field=models.IntegerField(verbose_name="recorded items", default=0),
), ),
migrations.RunPython(balance_ecocups),
] ]

View File

@ -13,7 +13,7 @@ from PIL import Image
from pytest_django.asserts import assertNumQueries from pytest_django.asserts import assertNumQueries
from core.baker_recipes import board_user, subscriber_user from core.baker_recipes import board_user, subscriber_user
from core.models import RealGroup, User from core.models import Group, User
from counter.models import Product, ProductType from counter.models import Product, ProductType
@ -51,16 +51,14 @@ def test_resize_product_icon(model):
( (
lambda: baker.make( lambda: baker.make(
User, User,
groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)], groups=[Group.objects.get(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)],
), ),
200, 200,
), ),
( (
lambda: baker.make( lambda: baker.make(
User, User,
groups=[ groups=[Group.objects.get(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)],
RealGroup.objects.get(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
],
), ),
200, 200,
), ),

View File

@ -6,7 +6,7 @@ from model_bakery import baker, seq
from ninja_extra.testing import TestClient from ninja_extra.testing import TestClient
from core.baker_recipes import board_user, subscriber_user from core.baker_recipes import board_user, subscriber_user
from core.models import RealGroup, User from core.models import Group, User
from counter.api import ProductTypeController from counter.api import ProductTypeController
from counter.models import ProductType from counter.models import ProductType
@ -70,16 +70,14 @@ def test_move_above_product_type(product_types: list[ProductType]):
( (
lambda: baker.make( lambda: baker.make(
User, User,
groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)], groups=[Group.objects.get(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)],
), ),
200, 200,
), ),
( (
lambda: baker.make( lambda: baker.make(
User, User,
groups=[ groups=[Group.objects.get(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)],
RealGroup.objects.get(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
],
), ),
200, 200,
), ),

File diff suppressed because it is too large Load Diff

View File

@ -25,26 +25,11 @@ from __future__ import unicode_literals
from django.db import migrations from django.db import migrations
from core.models import User
def remove_multiples_comments_from_same_user(apps, schema_editor):
for user in User.objects.exclude(uv_comments=None).prefetch_related("uv_comments"):
for uv in user.uv_comments.values("uv").distinct():
last = (
user.uv_comments.filter(uv__id=uv["uv"])
.order_by("-publish_date")
.first()
)
user.uv_comments.filter(uv__id=uv["uv"]).exclude(pk=last.pk).delete()
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [("pedagogy", "0001_initial")] dependencies = [("pedagogy", "0001_initial")]
operations = [ # This migration contained just a RunPython operation
migrations.RunPython( # Which has since been removed.
remove_multiples_comments_from_same_user, # The migration itself is kept in order not to break the migration tree
reverse_code=migrations.RunPython.noop, operations = []
)
]

View File

@ -8,7 +8,7 @@ from model_bakery import baker
from model_bakery.recipe import Recipe from model_bakery.recipe import Recipe
from core.baker_recipes import subscriber_user from core.baker_recipes import subscriber_user
from core.models import RealGroup, User from core.models import Group, User
from pedagogy.models import UV from pedagogy.models import UV
@ -80,9 +80,7 @@ class TestUVSearch(TestCase):
subscriber_user.make(), subscriber_user.make(),
baker.make( baker.make(
User, User,
groups=[ groups=[Group.objects.get(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)],
RealGroup.objects.get(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)
],
), ),
): ):
# users that have right # users that have right

View File

@ -24,6 +24,7 @@
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models import Exists, OuterRef
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.views.generic import ( from django.views.generic import (
@ -34,7 +35,7 @@ from django.views.generic import (
UpdateView, UpdateView,
) )
from core.models import Notification, RealGroup from core.models import Notification, User
from core.views import ( from core.views import (
CanCreateMixin, CanCreateMixin,
CanEditPropMixin, CanEditPropMixin,
@ -156,21 +157,19 @@ class UVCommentReportCreateView(CanCreateMixin, CreateView):
def form_valid(self, form): def form_valid(self, form):
resp = super().form_valid(form) resp = super().form_valid(form)
# Send a message to moderation admins # Send a message to moderation admins
for user in ( unread_notif_subquery = Notification.objects.filter(
RealGroup.objects.filter(id=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID) user=OuterRef("pk"), type="PEDAGOGY_MODERATION", viewed=False
.first() )
.users.all() for user in User.objects.filter(
~Exists(unread_notif_subquery),
groups__id__in=[settings.SITH_GROUP_PEDAGOGY_ADMIN_ID],
): ):
if not user.notifications.filter( Notification.objects.create(
type="PEDAGOGY_MODERATION", viewed=False user=user,
).exists(): url=reverse("pedagogy:moderation"),
Notification( type="PEDAGOGY_MODERATION",
user=user, )
url=reverse("pedagogy:moderation"),
type="PEDAGOGY_MODERATION",
).save()
return resp return resp

View File

@ -19,7 +19,7 @@ from django.urls import reverse
from django.utils.timezone import localtime, now from django.utils.timezone import localtime, now
from club.models import Club from club.models import Club
from core.models import RealGroup, User from core.models import Group, User
from counter.models import Counter, Customer, Product, Refilling, Selling from counter.models import Counter, Customer, Product, Refilling, Selling
from subscription.models import Subscription from subscription.models import Subscription
@ -50,9 +50,9 @@ class TestMergeUser(TestCase):
self.to_keep.address = "Jerusalem" self.to_keep.address = "Jerusalem"
self.to_delete.parent_address = "Rome" self.to_delete.parent_address = "Rome"
self.to_delete.address = "Rome" self.to_delete.address = "Rome"
subscribers = RealGroup.objects.get(name="Subscribers") subscribers = Group.objects.get(name="Subscribers")
mde_admin = RealGroup.objects.get(name="MDE admin") mde_admin = Group.objects.get(name="MDE admin")
sas_admin = RealGroup.objects.get(name="SAS admin") sas_admin = Group.objects.get(name="SAS admin")
self.to_keep.groups.add(subscribers.id) self.to_keep.groups.add(subscribers.id)
self.to_delete.groups.add(mde_admin.id) self.to_delete.groups.add(mde_admin.id)
self.to_keep.groups.add(sas_admin.id) self.to_keep.groups.add(sas_admin.id)

View File

@ -7,7 +7,7 @@ from model_bakery import baker
from model_bakery.recipe import Recipe from model_bakery.recipe import Recipe
from core.baker_recipes import old_subscriber_user, subscriber_user from core.baker_recipes import old_subscriber_user, subscriber_user
from core.models import RealGroup, SithFile, User from core.models import Group, SithFile, User
from sas.baker_recipes import picture_recipe from sas.baker_recipes import picture_recipe
from sas.models import Album, PeoplePictureRelation, Picture, PictureModerationRequest from sas.models import Album, PeoplePictureRelation, Picture, PictureModerationRequest
@ -155,7 +155,7 @@ class TestPictureRelation(TestSas):
def test_delete_relation_with_authorized_users(self): def test_delete_relation_with_authorized_users(self):
"""Test that deletion works as intended when called by an authorized user.""" """Test that deletion works as intended when called by an authorized user."""
relation: PeoplePictureRelation = self.user_a.pictures.first() relation: PeoplePictureRelation = self.user_a.pictures.first()
sas_admin_group = RealGroup.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID) sas_admin_group = Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)
sas_admin = baker.make(User, groups=[sas_admin_group]) sas_admin = baker.make(User, groups=[sas_admin_group])
root = baker.make(User, is_superuser=True) root = baker.make(User, is_superuser=True)
for user in sas_admin, root, self.user_a: for user in sas_admin, root, self.user_a:
@ -189,7 +189,7 @@ class TestPictureModeration(TestSas):
def setUpTestData(cls): def setUpTestData(cls):
super().setUpTestData() super().setUpTestData()
cls.sas_admin = baker.make( cls.sas_admin = baker.make(
User, groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
) )
cls.picture = Picture.objects.filter(parent=cls.album_a)[0] cls.picture = Picture.objects.filter(parent=cls.album_a)[0]
cls.picture.is_moderated = False cls.picture.is_moderated = False

View File

@ -23,7 +23,7 @@ from model_bakery import baker
from pytest_django.asserts import assertInHTML, assertRedirects from pytest_django.asserts import assertInHTML, assertRedirects
from core.baker_recipes import old_subscriber_user, subscriber_user from core.baker_recipes import old_subscriber_user, subscriber_user
from core.models import RealGroup, User from core.models import Group, User
from sas.baker_recipes import picture_recipe from sas.baker_recipes import picture_recipe
from sas.models import Album, Picture from sas.models import Album, Picture
@ -38,7 +38,7 @@ from sas.models import Album, Picture
old_subscriber_user.make, old_subscriber_user.make,
lambda: baker.make(User, is_superuser=True), lambda: baker.make(User, is_superuser=True),
lambda: baker.make( lambda: baker.make(
User, groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
), ),
lambda: baker.make(User), lambda: baker.make(User),
], ],
@ -80,7 +80,7 @@ class TestSasModeration(TestCase):
cls.to_moderate.is_moderated = False cls.to_moderate.is_moderated = False
cls.to_moderate.save() cls.to_moderate.save()
cls.moderator = baker.make( cls.moderator = baker.make(
User, groups=[RealGroup.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
) )
cls.simple_user = subscriber_user.make() cls.simple_user = subscriber_user.make()