Mise à jour d'avril (#643)

This commit is contained in:
Julien Constant
2023-05-10 11:56:33 +02:00
committed by GitHub
parent 910a6f8b34
commit 288764b551
201 changed files with 1746 additions and 1144 deletions

View File

@ -25,6 +25,7 @@
import sys
from django.apps import AppConfig
from django.core.cache import cache
from django.core.signals import request_started
@ -33,26 +34,17 @@ class SithConfig(AppConfig):
verbose_name = "Core app of the Sith"
def ready(self):
from core.models import User
from club.models import Club
from forum.models import Forum
import core.signals
def clear_cached_groups(**kwargs):
User._group_ids = {}
User._group_name = {}
cache.clear()
def clear_cached_memberships(**kwargs):
User._club_memberships = {}
Club._memberships = {}
Forum._club_memberships = {}
print("Connecting signals!", file=sys.stderr)
request_started.connect(
clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups"
)
request_started.connect(
clear_cached_memberships,
weak=False,
dispatch_uid="clear_cached_memberships",
)
# TODO: there may be a need to add more cache clearing

View File

@ -39,6 +39,5 @@ class Command(compilemessages.Command):
"""
def handle(self, *args, **options):
os.chdir("sith")
super(Command, self).handle(*args, **options)

View File

@ -60,7 +60,7 @@ class Command(BaseCommand):
def compilescss(self, file):
print("compiling %s" % file)
with (open(file.replace(".scss", ".css"), "w")) as newfile:
with open(file.replace(".scss", ".css"), "w") as newfile:
newfile.write(self.compile(file))
def removescss(self, file):
@ -68,7 +68,6 @@ class Command(BaseCommand):
os.remove(file)
def handle(self, *args, **options):
if os.path.isdir(settings.STATIC_ROOT):
print("---- Compiling scss files ---")
self.exec_on_folder(settings.STATIC_ROOT, self.compilescss)

View File

@ -155,12 +155,10 @@ class Command(BaseCommand):
Counter(name="Eboutic", club=main_club, type="EBOUTIC").save()
Counter(name="AE", club=main_club, type="OFFICE").save()
home_root.view_groups.set(
[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()]
)
club_root.view_groups.set(
[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()]
)
ae_members = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP)
home_root.view_groups.set([ae_members])
club_root.view_groups.set([ae_members])
home_root.save()
club_root.save()
@ -220,9 +218,7 @@ Welcome to the wiki page!
)
skia.set_password("plop")
skia.save()
skia.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
skia.view_groups = [ae_members.id]
skia.save()
skia_profile_path = (
root_path
@ -261,9 +257,7 @@ Welcome to the wiki page!
)
public.set_password("plop")
public.save()
public.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
public.view_groups = [ae_members.id]
public.save()
# Adding user Subscriber
subscriber = User(
@ -277,9 +271,7 @@ Welcome to the wiki page!
)
subscriber.set_password("plop")
subscriber.save()
subscriber.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
subscriber.view_groups = [ae_members.id]
subscriber.save()
# Adding user old Subscriber
old_subscriber = User(
@ -293,9 +285,7 @@ Welcome to the wiki page!
)
old_subscriber.set_password("plop")
old_subscriber.save()
old_subscriber.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
old_subscriber.view_groups = [ae_members.id]
old_subscriber.save()
# Adding user Counter admin
counter = User(
@ -309,9 +299,7 @@ Welcome to the wiki page!
)
counter.set_password("plop")
counter.save()
counter.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
counter.view_groups = [ae_members.id]
counter.groups.set(
[
Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID)
@ -332,9 +320,7 @@ Welcome to the wiki page!
)
comptable.set_password("plop")
comptable.save()
comptable.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
comptable.view_groups = [ae_members.id]
comptable.groups.set(
[
Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
@ -355,9 +341,7 @@ Welcome to the wiki page!
)
u.set_password("plop")
u.save()
u.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
u.view_groups = [ae_members.id]
u.save()
# Adding user Richard Batsbak
richard = User(
@ -394,9 +378,7 @@ Welcome to the wiki page!
richard_profile.save()
richard.profile_pict = richard_profile
richard.save()
richard.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
richard.view_groups = [ae_members.id]
richard.save()
# Adding syntax help page
p = Page(name="Aide_sur_la_syntaxe")
@ -428,7 +410,7 @@ Welcome to the wiki page!
default_subscription = "un-semestre"
# Root
s = Subscription(
member=User.objects.filter(pk=root.pk).first(),
member=root,
subscription_type=default_subscription,
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
)
@ -528,7 +510,7 @@ Welcome to the wiki page!
Club(
name="Woenzel'UT", unix_name="woenzel", address="Woenzel", parent=guyut
).save()
Membership(user=skia, club=main_club, role=3, description="").save()
Membership(user=skia, club=main_club, role=3).save()
troll = Club(
name="Troll Penché",
unix_name="troll",
@ -855,9 +837,7 @@ Welcome to the wiki page!
)
sli.set_password("plop")
sli.save()
sli.view_groups = [
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
]
sli.view_groups = [ae_members.id]
sli.save()
sli_profile_path = (
root_path
@ -934,7 +914,6 @@ Welcome to the wiki page!
Membership(
user=comunity,
club=bar_club,
start_date=timezone.now(),
role=settings.SITH_CLUB_ROLES_ID["Board member"],
).save()
# Adding user tutu

View File

@ -22,9 +22,6 @@ from django.core.management import call_command
class Command(BaseCommand):
help = "Set up a new instance of the Sith AE"
def add_arguments(self, parser):
parser.add_argument("--prod", action="store_true")
def handle(self, *args, **options):
root_path = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
@ -40,7 +37,4 @@ class Command(BaseCommand):
except Exception as e:
repr(e)
call_command("migrate")
if options["prod"]:
call_command("populate", "--prod")
else:
call_command("populate")
call_command("populate")

View File

@ -12,7 +12,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("auth", "0006_require_contenttypes_0002")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0001_initial")]
operations = [

View File

@ -6,7 +6,6 @@ import django.core.validators
class Migration(migrations.Migration):
dependencies = [("core", "0002_auto_20160831_0144")]
operations = [

View File

@ -6,7 +6,6 @@ from django.conf import settings
class Migration(migrations.Migration):
dependencies = [("core", "0003_auto_20160902_1914")]
operations = [

View File

@ -7,7 +7,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("core", "0004_user_godfathers")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0005_auto_20161105_1035")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0006_auto_20161108_1703")]
operations = [

View File

@ -7,7 +7,6 @@ import core.models
class Migration(migrations.Migration):
dependencies = [("core", "0008_sithfile_asked_for_removal")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0009_auto_20161120_1155")]
operations = [

View File

@ -7,7 +7,6 @@ import core.models
class Migration(migrations.Migration):
dependencies = [("core", "0010_sithfile_is_in_sas")]
operations = [

View File

@ -8,7 +8,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("core", "0011_auto_20161124_0848")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0012_notification")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0013_auto_20161209_2338")]
operations = [

View File

@ -7,7 +7,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("core", "0014_auto_20161210_0009")]
operations = [

View File

@ -7,7 +7,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("core", "0015_sithfile_moderator")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0016_auto_20161212_1922")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0017_auto_20161220_1626")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0018_auto_20161224_0211")]
operations = [

View File

@ -6,7 +6,6 @@ import django.core.validators
class Migration(migrations.Migration):
dependencies = [("core", "0019_preferences_receive_weekmail")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0020_auto_20170324_0917")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0021_auto_20170822_1529")]
operations = [

View File

@ -7,7 +7,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("core", "0022_auto_20170822_2232")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0023_auto_20170902_1226")]
operations = [

View File

@ -6,7 +6,6 @@ import django.core.validators
class Migration(migrations.Migration):
dependencies = [("core", "0024_auto_20170906_1317")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0025_auto_20170919_1521")]
operations = [

View File

@ -8,7 +8,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("core", "0026_auto_20170926_1512")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0027_gift")]
operations = [

View File

@ -7,7 +7,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [("core", "0028_auto_20171216_2044")]
operations = [

View File

@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0029_auto_20180426_2013")]
operations = [

View File

@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0030_auto_20190704_1500")]
operations = [

View File

@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0031_auto_20190906_1615")]
operations = [

View File

@ -5,7 +5,6 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [("core", "0032_auto_20190909_0043")]
operations = [

View File

@ -6,7 +6,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("core", "0033_auto_20191006_0049"),
]

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0034_operationlog"),
]

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0035_auto_20200216_1743")]
operations = [

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("core", "0036_auto_20211001_0248")]
operations = [

View File

@ -23,12 +23,12 @@
#
#
import importlib
from typing import Union, Optional, List
from django.db import models
from django.core.cache import cache
from django.core.mail import send_mail
from django.contrib.auth.models import (
AbstractBaseUser,
PermissionsMixin,
UserManager,
Group as AuthGroup,
GroupManager as AuthGroupManager,
@ -40,7 +40,7 @@ from django.core import validators
from django.core.exceptions import ValidationError, PermissionDenied
from django.urls import reverse
from django.conf import settings
from django.db import transaction
from django.db import models, transaction
from django.contrib.staticfiles.storage import staticfiles_storage
from django.utils.html import escape
from django.utils.functional import cached_property
@ -50,7 +50,7 @@ from core import utils
from phonenumber_field.modelfields import PhoneNumberField
from datetime import datetime, timedelta, date
from datetime import timedelta, date
import unicodedata
@ -90,14 +90,24 @@ class Group(AuthGroup):
"""
return reverse("core:group_list")
def save(self, *args, **kwargs):
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):
super().delete(*args, **kwargs)
cache.delete(f"sith_group_{self.id}")
cache.delete(f"sith_group_{self.name.replace(' ', '_')}")
class MetaGroup(Group):
"""
MetaGroups are dynamically created groups.
Generaly used with clubs where creating a club creates two groups:
Generally used with clubs where creating a club creates two groups:
* club-SITH_BOARD_SUFFIX
* club-SITH_MEMBER_SUFFIX
* club-SITH_BOARD_SUFFIX
* club-SITH_MEMBER_SUFFIX
"""
#: Assign a manager in a way that MetaGroup.objects only return groups with is_meta=False
@ -110,6 +120,32 @@ class MetaGroup(Group):
super(MetaGroup, self).__init__(*args, **kwargs)
self.is_meta = True
@cached_property
def associated_club(self):
"""
Return the group associated with this meta group
The result of this function is cached
:return: The associated club if it exists, else None
:rtype: club.models.Club | None
"""
from club.models import Club
if self.name.endswith(settings.SITH_BOARD_SUFFIX):
# replace this with str.removesuffix as soon as Python
# is upgraded to 3.10
club_name = self.name[: -len(settings.SITH_BOARD_SUFFIX)]
elif self.name.endswith(settings.SITH_MEMBER_SUFFIX):
club_name = self.name[: -len(settings.SITH_MEMBER_SUFFIX)]
else:
return None
club = cache.get(f"sith_club_{club_name}")
if club is None:
club = Club.objects.filter(unix_name=club_name).first()
cache.set(f"sith_club_{club_name}", club)
return club
class RealGroup(Group):
"""
@ -134,6 +170,43 @@ def validate_promo(value):
)
def get_group(*, pk: int = None, name: str = None) -> Optional[Group]:
"""
Search for a group by its primary key or its name.
Either one of the two must be set.
The result is cached for the default duration (should be 5 minutes).
:param pk: The primary key of the group
:param name: The name of the group
:return: The group if it exists, else None
:raises ValueError: If no group matches the criteria
"""
if pk is None and name is None:
raise ValueError("Either pk or name must be set")
if name is not None:
name = name.replace(" ", "_") # avoid errors with memcached backend
pk_or_name: Union[str, int] = pk if pk is not None else name
group = cache.get(f"sith_group_{pk_or_name}")
if group == "not_found":
# Using None as a cache value is a little bit tricky,
# so we use a special string to represent None
return None
elif group is not None:
return group
# if this point is reached, the group is not in cache
if pk is not None:
group = Group.objects.filter(pk=pk).first()
else:
group = Group.objects.filter(name=name).first()
if group is not None:
cache.set(f"sith_group_{group.id}", group)
cache.set(f"sith_group_{group.name.replace(' ', '_')}", group)
else:
cache.set(f"sith_group_{pk_or_name}", "not_found")
return group
class User(AbstractBaseUser):
"""
Defines the base user class, useable in every app
@ -295,7 +368,6 @@ class User(AbstractBaseUser):
objects = UserManager()
USERNAME_FIELD = "username"
# REQUIRED_FIELDS = ['email']
def promo_has_logo(self):
return utils.file_exist("./core/static/core/img/promo_%02d.png" % self.promo)
@ -336,94 +408,72 @@ class User(AbstractBaseUser):
else:
return 0
_club_memberships = {}
_group_names = {}
_group_ids = {}
def is_in_group(self, *, pk: int = None, name: str = None) -> bool:
"""
Check if this user is in the given group.
Either a group id or a group name must be provided.
If both are passed, only the id will be considered.
def is_in_group(self, group_name):
"""If the user is in the group passed in argument (as string or by id)"""
group_id = 0
g = None
if isinstance(group_name, int): # Handle the case where group_name is an ID
if group_name in User._group_ids.keys():
g = User._group_ids[group_name]
else:
g = Group.objects.filter(id=group_name).first()
User._group_ids[group_name] = g
else:
if group_name in User._group_names.keys():
g = User._group_names[group_name]
else:
g = Group.objects.filter(name=group_name).first()
User._group_names[group_name] = g
if g:
group_name = g.name
group_id = g.id
The group will be fetched using the given parameter.
If no group is found, return False.
If a group is found, check if this user is in the latter.
:return: True if the user is the group, else False
"""
if pk is not None:
group: Optional[Group] = get_group(pk=pk)
elif name is not None:
group: Optional[Group] = get_group(name=name)
else:
raise ValueError("You must either provide the id or the name of the group")
if group is None:
return False
if group_id == settings.SITH_GROUP_PUBLIC_ID:
if group.id == settings.SITH_GROUP_PUBLIC_ID:
return True
if group_id == settings.SITH_GROUP_SUBSCRIBERS_ID:
if group.id == settings.SITH_GROUP_SUBSCRIBERS_ID:
return self.is_subscribed
if group_id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID:
if group.id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID:
return self.was_subscribed
if (
group_name == settings.SITH_MAIN_MEMBERS_GROUP
): # We check the subscription if asked
return self.is_subscribed
if group_name[-len(settings.SITH_BOARD_SUFFIX) :] == settings.SITH_BOARD_SUFFIX:
name = group_name[: -len(settings.SITH_BOARD_SUFFIX)]
if name in User._club_memberships.keys():
mem = User._club_memberships[name]
else:
from club.models import Club
c = Club.objects.filter(unix_name=name).first()
mem = c.get_membership_for(self)
User._club_memberships[name] = mem
if mem:
return mem.role > settings.SITH_MAXIMUM_FREE_ROLE
return False
if (
group_name[-len(settings.SITH_MEMBER_SUFFIX) :]
== settings.SITH_MEMBER_SUFFIX
):
name = group_name[: -len(settings.SITH_MEMBER_SUFFIX)]
if name in User._club_memberships.keys():
mem = User._club_memberships[name]
else:
from club.models import Club
c = Club.objects.filter(unix_name=name).first()
mem = c.get_membership_for(self)
User._club_memberships[name] = mem
if mem:
if group.id == settings.SITH_GROUP_ROOT_ID:
return self.is_root
if group.is_meta:
# check if this group is associated with a club
group.__class__ = MetaGroup
club = group.associated_club
if club is None:
return False
membership = club.get_membership_for(self)
if membership is None:
return False
if group.name.endswith(settings.SITH_MEMBER_SUFFIX):
return True
return False
if group_id == settings.SITH_GROUP_ROOT_ID and self.is_superuser:
return membership.role > settings.SITH_MAXIMUM_FREE_ROLE
return group in self.cached_groups
@property
def cached_groups(self) -> List[Group]:
"""
Get the list of groups this user is in.
The result is cached for the default duration (should be 5 minutes)
:return: A list of all the groups this user is in
"""
groups = cache.get(f"user_{self.id}_groups")
if groups is None:
groups = list(self.groups.all())
cache.set(f"user_{self.id}_groups", groups)
return groups
@cached_property
def is_root(self) -> bool:
if self.is_superuser:
return True
return group_name in self.cached_groups_names
@cached_property
def cached_groups_names(self):
return [g.name for g in self.groups.all()]
@cached_property
def is_root(self):
return (
self.is_superuser
or self.groups.filter(id=settings.SITH_GROUP_ROOT_ID).exists()
)
root_id = settings.SITH_GROUP_ROOT_ID
return any(g.id == root_id for g in self.cached_groups)
@cached_property
def is_board_member(self):
from club.models import Club
return (
Club.objects.filter(unix_name=settings.SITH_MAIN_CLUB["unix_name"])
.first()
.has_rights_in_club(self)
)
main_club = settings.SITH_MAIN_CLUB["unix_name"]
return self.is_in_group(name=main_club + settings.SITH_BOARD_SUFFIX)
@cached_property
def can_read_subscription_history(self):
@ -434,8 +484,8 @@ class User(AbstractBaseUser):
for club in Club.objects.filter(
id__in=settings.SITH_CAN_READ_SUBSCRIPTION_HISTORY
).all():
if club.has_rights_in_club(self):
):
if club in self.clubs_with_rights:
return True
return False
@ -443,10 +493,8 @@ class User(AbstractBaseUser):
def can_create_subscription(self):
from club.models import Club
for club in Club.objects.filter(
id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS
).all():
if club.has_rights_in_club(self):
for club in Club.objects.filter(id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS):
if club in self.clubs_with_rights:
return True
return False
@ -464,11 +512,11 @@ class User(AbstractBaseUser):
@cached_property
def is_banned_alcohol(self):
return self.is_in_group(settings.SITH_GROUP_BANNED_ALCOHOL_ID)
return self.is_in_group(pk=settings.SITH_GROUP_BANNED_ALCOHOL_ID)
@cached_property
def is_banned_counter(self):
return self.is_in_group(settings.SITH_GROUP_BANNED_COUNTER_ID)
return self.is_in_group(pk=settings.SITH_GROUP_BANNED_COUNTER_ID)
@cached_property
def age(self) -> int:
@ -598,9 +646,9 @@ class User(AbstractBaseUser):
"""
if hasattr(obj, "is_owned_by") and obj.is_owned_by(self):
return True
if hasattr(obj, "owner_group") and self.is_in_group(obj.owner_group.name):
if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id):
return True
if self.is_superuser or self.is_in_group(settings.SITH_GROUP_ROOT_ID):
if self.is_root:
return True
return False
@ -611,8 +659,8 @@ class User(AbstractBaseUser):
if hasattr(obj, "can_be_edited_by") and obj.can_be_edited_by(self):
return True
if hasattr(obj, "edit_groups"):
for g in obj.edit_groups.all():
if self.is_in_group(g.name):
for pk in obj.edit_groups.values_list("pk", flat=True):
if self.is_in_group(pk=pk):
return True
if isinstance(obj, User) and obj == self:
return True
@ -627,15 +675,15 @@ class User(AbstractBaseUser):
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
return True
if hasattr(obj, "view_groups"):
for g in obj.view_groups.all():
if self.is_in_group(g.name):
for pk in obj.view_groups.values_list("pk", flat=True):
if self.is_in_group(pk=pk):
return True
if self.can_edit(obj):
return True
return False
def can_be_edited_by(self, user):
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user.is_root
return user.is_root or user.is_board_member
def can_be_viewed_by(self, user):
return (user.was_subscribed and self.is_subscriber_viewable) or user.is_root
@ -656,10 +704,6 @@ class User(AbstractBaseUser):
escape(self.get_display_name()),
)
@cached_property
def subscribed(self):
return self.is_in_group(settings.SITH_MAIN_MEMBERS_GROUP)
@cached_property
def preferences(self):
try:
@ -682,17 +726,16 @@ class User(AbstractBaseUser):
@cached_property
def clubs_with_rights(self):
return [
m.club.id
for m in self.memberships.filter(
models.Q(end_date__isnull=True) | models.Q(end_date__gte=timezone.now())
).all()
if m.club.has_rights_in_club(self)
]
"""
:return: the list of clubs where the user has rights
:rtype: list[club.models.Club]
"""
memberships = self.memberships.ongoing().board().select_related("club")
return [m.club for m in memberships]
@cached_property
def is_com_admin(self):
return self.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
return self.is_in_group(pk=settings.SITH_GROUP_COM_ADMIN_ID)
class AnonymousUser(AuthAnonymousUser):
@ -747,21 +790,18 @@ class AnonymousUser(AuthAnonymousUser):
def favorite_topics(self):
raise PermissionDenied
def is_in_group(self, group_name):
def is_in_group(self, *, pk: int = None, name: str = None) -> bool:
"""
The anonymous user is only the public group
The anonymous user is only in the public group
"""
group_id = 0
if isinstance(group_name, int): # Handle the case where group_name is an ID
g = Group.objects.filter(id=group_name).first()
if g:
group_name = g.name
group_id = g.id
else:
return False
if group_id == settings.SITH_GROUP_PUBLIC_ID:
return True
return False
allowed_id = settings.SITH_GROUP_PUBLIC_ID
if pk is not None:
return pk == allowed_id
elif name is not None:
group = get_group(name=name)
return group is not None and group.id == allowed_id
else:
raise ValueError("You must either provide the id or the name of the group")
def is_owner(self, obj):
return False
@ -879,14 +919,44 @@ class SithFile(models.Model):
class Meta:
verbose_name = _("file")
def is_owned_by(self, user):
if hasattr(self, "profile_of") and user.is_in_group(
settings.SITH_MAIN_BOARD_GROUP
def can_be_managed_by(self, user: User) -> bool:
"""
Tell if the user can manage the file (edit, delete, etc.) or not.
Apply the following rules:
- If the file is not in the SAS nor in the profiles directory, it can be "managed" by anyone -> return True
- If the file is in the SAS, only the SAS admins (or roots) can manage it -> return True if the user is in the SAS admin group or is a root
- If the file is in the profiles directory, only the roots can manage it -> return True if the user is a root
:returns: True if the file is managed by the SAS or within the profiles directory, False otherwise
"""
# If the file is not in the SAS nor in the profiles directory, it can be "managed" by anyone
profiles_dir = SithFile.objects.filter(name="profiles").first()
if not self.is_in_sas and not profiles_dir in self.get_parent_list():
return True
# If the file is in the SAS, only the SAS admins (or roots) can manage it
if self.is_in_sas and (
user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) or user.is_root
):
return True
if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID):
# If the file is in the profiles directory, only the roots can manage it
if profiles_dir in self.get_parent_list() and (
user.is_root or user.is_board_member
):
return True
if self.is_in_sas and user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID):
return False
def is_owned_by(self, user):
if user.is_anonymous:
return False
if hasattr(self, "profile_of") and user.is_board_member:
return True
if user.is_com_admin:
return True
if self.is_in_sas and user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID):
return True
return user.id == self.owner.id
@ -956,7 +1026,7 @@ class SithFile(models.Model):
def save(self, *args, **kwargs):
sas = SithFile.objects.filter(id=settings.SITH_SAS_ROOT_DIR_ID).first()
self.is_in_sas = sas in self.get_parent_list()
self.is_in_sas = sas in self.get_parent_list() or self == sas
copy_rights = False
if self.id is None:
copy_rights = True
@ -1090,12 +1160,6 @@ class SithFile(models.Model):
return Album.objects.filter(id=self.id).first()
def __str__(self):
if self.is_folder:
return _("Folder: ") + self.name
else:
return _("File: ") + self.name
def get_parent_list(self):
l = []
p = self.parent
@ -1176,6 +1240,7 @@ class Page(models.Model):
# Attention: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when
# playing with a Page object, use get_full_name() instead!
_full_name = models.CharField(_("page name"), max_length=255, blank=True)
# This function prevents generating migration upon settings change
def get_default_owner_group():
return settings.SITH_GROUP_ROOT_ID
@ -1492,6 +1557,8 @@ class Gift(models.Model):
return self.label
def is_owned_by(self, user):
if user.is_anonymous:
return False
return user.is_board_member or user.is_root

17
core/signals.py Normal file
View File

@ -0,0 +1,17 @@
from django.core.cache import cache
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from core.models import User
@receiver(m2m_changed, sender=User.groups.through, dispatch_uid="user_groups_changed")
def user_groups_changed(sender, instance: User, **kwargs):
"""
Clear the cached clubs of the user
"""
# As a m2m relationship doesn't live within the model
# but rather on an intermediary table, there is no
# model method to override, meaning we must use
# a signal to invalidate the cache when a user is removed from a club
cache.delete(f"user_{instance.id}_groups")

View File

@ -43,7 +43,7 @@ nav.navbar {
justify-content: center;
display: flex !important;
}
> .menu,
> .link {
box-sizing: border-box;
@ -85,6 +85,22 @@ nav.navbar {
background-color: rgba(0, 0, 0, .2);
}
> .menu > .head,
> .link {
color: white;
padding: 10px 20px;
box-sizing: border-box;
@media (max-width: 500px) {
padding: 10px;
}
}
.link:hover,
.menu:hover {
background-color: rgba(0, 0, 0, .2);
}
> .menu:hover > .content,
> .menu > .head:hover + .content,
> .menu > .content:hover {
@ -130,5 +146,5 @@ nav.navbar {
}
}
}
}
}
}

View File

@ -61,7 +61,7 @@
{% if not file.home_of and not file.home_of_club and file.parent %}
<p><a href="{{ url('core:file_delete', file_id=file.id, popup=popup) }}">{% trans %}Delete{% endtrans %}</a></p>
{% endif %}
{% if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %}
{% if user.is_com_admin %}
<p><a href="{{ url('core:file_moderate', file_id=file.id) }}">{% trans %}Moderate{% endtrans %}</a></p>
{% endif %}
{% endblock %}

View File

@ -67,7 +67,10 @@
</tbody>
</table>
{% if profile.mailing_subscriptions.exists() and (profile.id == user.id or user.is_root or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)) %}
{% if
profile.mailing_subscriptions.exists()
and (profile.id == user.id or user.is_root or user.is_com_admin)
%}
<h4>{% trans %}Subscribed mailing lists{% endtrans %}</h4>
{% for sub in profile.mailing_subscriptions.all() %}
<p>{{ sub.mailing.email }} <a href="{{ url('club:mailing_subscription_delete', mailing_subscription_id=sub.id) }}">{% trans %}Unsubscribe{% endtrans %}</a></p>

View File

@ -136,7 +136,12 @@
</div>
</div>
</main>
{% if user.memberships.filter(end_date=None).exists() or user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user == profile or user.is_in_group(settings.SITH_BAR_MANAGER_BOARD_GROUP) %}
{% if
user == profile
or user.memberships.ongoing().exists()
or user.is_board_member
or user.is_in_group(name=settings.SITH_BAR_MANAGER_BOARD_GROUP)
%}
{# if the user is member of a club, he can view the subscription state #}
<hr>
{% if profile.is_subscribed %}

View File

@ -35,7 +35,7 @@
{%- else -%}
<em>{% trans %}To edit your profile picture, ask a member of the AE{% endtrans %}</em>
{%- endif -%}
{%- if user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) and form.instance.profile_pict.id -%}
{%- if user.is_board_member and form.instance.profile_pict.id -%}
<a href="{{ url('core:file_delete', file_id=form.instance.profile_pict.id, popup='') }}">
{%- trans -%}Delete{%- endtrans -%}
</a>
@ -55,7 +55,7 @@
<div class="profile-picture-edit">
<p>{{ form["avatar_pict"].label }}</p>
{{ form["avatar_pict"] }}
{%- if user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) and form.instance.avatar_pict.id -%}
{%- if user.is_board_member and form.instance.avatar_pict.id -%}
<a href="{{ url('core:file_delete', file_id=form.instance.avatar_pict.id, popup='') }}">
{%- trans -%}Delete{%- endtrans -%}
</a>
@ -75,7 +75,7 @@
<div class="profile-picture-edit">
<p>{{ form["scrub_pict"].label }}</p>
{{ form["scrub_pict"] }}
{%- if user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) and form.instance.scrub_pict.id -%}
{%- if user.is_board_member and form.instance.scrub_pict.id -%}
<a href="{{ url('core:file_delete', file_id=form.instance.scrub_pict.id, popup='') }}">
{%- trans -%}Delete{%-endtrans -%}
</a>

View File

@ -35,18 +35,21 @@
{% endif %}
{% set is_admin_on_a_counter = false %}
{% for b in settings.SITH_COUNTER_BARS if user.is_in_group(b[1] + " admin") %}
{% for b in settings.SITH_COUNTER_BARS if user.is_in_group(name=b[1] + " admin") %}
{% set is_admin_on_a_counter = true %}
{% endfor %}
{% if
user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) or user.is_root
or is_admin_on_a_counter
is_admin_on_a_counter
or user.is_root
or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
%}
<div>
<h4>{% trans %}Counters{% endtrans %}</h4>
<ul>
{% if user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) or user.is_root %}
{% if user.is_root
or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
%}
<li><a href="{{ url('counter:admin_list') }}">{% trans %}General counters management{% endtrans %}</a></li>
<li><a href="{{ url('counter:product_list') }}">{% trans %}Products management{% endtrans %}</a></li>
<li><a href="{{ url('counter:producttype_list') }}">{% trans %}Product types management{% endtrans %}</a></li>
@ -57,7 +60,7 @@
</ul>
<ul>
{% for b in settings.SITH_COUNTER_BARS %}
{% if user.is_in_group(b[1]+" admin") %}
{% if user.is_in_group(name=b[1]+" admin") %}
{% set c = Counter.objects.filter(id=b[0]).first() %}
<li class="rows counter">
@ -85,13 +88,16 @@
{% endif %}
{% if
user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or user.is_root
or user.memberships.filter(end_date=None).filter(role__gte=7).all() | length > 10
user.is_root
or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or user.memberships.ongoing().filter(role__gte=7).count() > 10
%}
<div>
<h4>{% trans %}Accounting{% endtrans %}</h4>
<ul>
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or user.is_root %}
{% if user.is_root
or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
%}
<li><a href="{{ url('accounting:refound_account') }}">{% trans %}Refound Account{% endtrans %}</a></li>
<li><a href="{{ url('accounting:bank_list') }}">{% trans %}General accounting{% endtrans %}</a></li>
<li><a href="{{ url('accounting:co_list') }}">{% trans %}Company list{% endtrans %}</a></li>
@ -118,11 +124,15 @@
</div>
{% endif %}
{% if user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or user.is_root %}
{% if
user.is_root
or user.is_com_admin
or user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID)
%}
<div>
<h4>{% trans %}Communication{% endtrans %}</h4>
<ul>
{% if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or user.is_root %}
{% if user.is_com_admin or user.is_root %}
<li><a href="{{ url('com:weekmail_article') }}">{% trans %}Create weekmail article{% endtrans %}</a></li>
<li><a href="{{ url('com:weekmail') }}">{% trans %}Weekmail{% endtrans %}</a></li>
<li><a href="{{ url('com:weekmail_destinations') }}">{% trans %}Weekmail destinations{% endtrans %}</a></li>
@ -135,7 +145,7 @@
<li><a href="{{ url('com:poster_list') }}">{% trans %}Posters{% endtrans %}</a></li>
<li><a href="{{ url('com:screen_list') }}">{% trans %}Screens{% endtrans %}</a></li>
{% endif %}
{% if user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) %}
{% if user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID) %}
<li><a href="{{ url('sas:moderation') }}">{% trans %}Moderate pictures{% endtrans %}</a></li>
{% endif %}
</ul>
@ -153,7 +163,10 @@
</div>
{% endif %}
{% if user.is_in_group(settings.SITH_GROUP_PEDAGOGY_ADMIN_ID) or user.is_root %}
{% if
user.is_root
or user.is_in_group(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)
%}
<div>
<h4>{% trans %}Pedagogy{% endtrans %}</h4>
<ul>

View File

@ -15,13 +15,18 @@
#
import os
from datetime import timedelta
from django.core.cache import cache
from django.test import Client, TestCase
from django.urls import reverse
from django.core.management import call_command
from django.utils.timezone import now
from core.models import User, Group, Page
from club.models import Membership
from core.models import User, Group, Page, AnonymousUser
from core.markdown import markdown
from sith import settings
"""
to run these tests :
@ -30,11 +35,9 @@ to run these tests :
class UserRegistrationTest(TestCase):
def setUp(self):
try:
Group.objects.create(name="root")
except Exception as e:
print(e)
@classmethod
def setUpTestData(cls):
User.objects.all().delete()
def test_register_user_form_ok(self):
"""
@ -282,19 +285,8 @@ class MarkdownTest(TestCase):
class PageHandlingTest(TestCase):
def setUp(self):
self.root_group = Group.objects.create(name="root")
u = User(
username="root",
last_name="",
first_name="Bibou",
email="ae.info@utbm.fr",
date_of_birth="1942-06-12",
is_superuser=True,
is_staff=True,
)
u.set_password("plop")
u.save()
self.client.login(username="root", password="plop")
self.root_group = Group.objects.get(name="Root")
def test_create_page_ok(self):
"""
@ -321,12 +313,20 @@ class PageHandlingTest(TestCase):
"""
Should create a page correctly
"""
# remove all other pages to make sure there is no side effect
Page.objects.all().delete()
self.client.post(
reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": "1"}
reverse("core:page_new"),
{"parent": "", "name": "guy", "owner_group": str(self.root_group.id)},
)
page = Page.objects.first()
response = self.client.post(
reverse("core:page_new"),
{"parent": "1", "name": "bibou", "owner_group": "1"},
{
"parent": str(page.id),
"name": "bibou",
"owner_group": str(self.root_group.id),
},
)
response = self.client.get(
reverse("core:page", kwargs={"page_name": "guy/bibou"})
@ -392,9 +392,6 @@ http://git.an
class UserToolsTest(TestCase):
def setUp(self):
call_command("populate")
def test_anonymous_user_unauthorized(self):
response = self.client.get(reverse("core:user_tools"))
self.assertEqual(response.status_code, 403)
@ -432,13 +429,12 @@ class UserToolsTest(TestCase):
class FileHandlingTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.subscriber = User.objects.get(username="subscriber")
def setUp(self):
try:
call_command("populate")
self.subscriber = User.objects.filter(username="subscriber").first()
self.client.login(username="subscriber", password="plop")
except Exception as e:
print(e)
self.client.login(username="subscriber", password="plop")
def test_create_folder_home(self):
response = self.client.post(
@ -466,3 +462,150 @@ class FileHandlingTest(TestCase):
)
self.assertTrue(response.status_code == 200)
self.assertTrue("ls</a>" in str(response.content))
class UserIsInGroupTest(TestCase):
"""
Test that the User.is_in_group() and AnonymousUser.is_in_group()
work as intended
"""
@classmethod
def setUpTestData(cls):
from club.models import Club
cls.root_group = Group.objects.get(name="Root")
cls.public = Group.objects.get(name="Public")
cls.subscribers = Group.objects.get(name="Subscribers")
cls.old_subscribers = Group.objects.get(name="Old subscribers")
cls.accounting_admin = Group.objects.get(name="Accounting admin")
cls.com_admin = Group.objects.get(name="Communication admin")
cls.counter_admin = Group.objects.get(name="Counter admin")
cls.banned_alcohol = Group.objects.get(name="Banned from buying alcohol")
cls.banned_counters = Group.objects.get(name="Banned from counters")
cls.banned_subscription = Group.objects.get(name="Banned to subscribe")
cls.sas_admin = Group.objects.get(name="SAS admin")
cls.club = Club.objects.create(
name="Fake Club",
unix_name="fake-club",
address="Fake address",
)
cls.main_club = Club.objects.get(id=1)
def setUp(self) -> None:
self.toto = User.objects.create(
username="toto", first_name="a", last_name="b", email="a.b@toto.fr"
)
self.skia = User.objects.get(username="skia")
def assert_in_public_group(self, user):
self.assertTrue(user.is_in_group(pk=self.public.id))
self.assertTrue(user.is_in_group(name=self.public.name))
def assert_in_club_metagroups(self, user, club):
meta_groups_board = club.unix_name + settings.SITH_BOARD_SUFFIX
meta_groups_members = club.unix_name + settings.SITH_MEMBER_SUFFIX
self.assertFalse(user.is_in_group(name=meta_groups_board))
self.assertFalse(user.is_in_group(name=meta_groups_members))
def assert_only_in_public_group(self, user):
self.assert_in_public_group(user)
for group in (
self.root_group,
self.banned_counters,
self.accounting_admin,
self.sas_admin,
self.subscribers,
self.old_subscribers,
):
self.assertFalse(user.is_in_group(pk=group.pk))
self.assertFalse(user.is_in_group(name=group.name))
meta_groups_board = self.club.unix_name + settings.SITH_BOARD_SUFFIX
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
self.assertFalse(user.is_in_group(name=meta_groups_board))
self.assertFalse(user.is_in_group(name=meta_groups_members))
def test_anonymous_user(self):
"""
Test that anonymous users are only in the public group
"""
user = AnonymousUser()
self.assert_only_in_public_group(user)
def test_not_subscribed_user(self):
"""
Test that users who never subscribed are only in the public group
"""
self.assert_only_in_public_group(self.toto)
def test_wrong_parameter_fail(self):
"""
Test that when neither the pk nor the name argument is given,
the function raises a ValueError
"""
with self.assertRaises(ValueError):
self.toto.is_in_group()
def test_number_queries(self):
"""
Test that the number of db queries is stable
and that less queries are made when making a new call
"""
# make sure Skia is in at least one group
self.skia.groups.add(Group.objects.first().pk)
skia_groups = self.skia.groups.all()
group_in = skia_groups.first()
cache.clear()
# Test when the user is in the group
with self.assertNumQueries(2):
self.skia.is_in_group(pk=group_in.id)
with self.assertNumQueries(0):
self.skia.is_in_group(pk=group_in.id)
ids = skia_groups.values_list("pk", flat=True)
group_not_in = Group.objects.exclude(pk__in=ids).first()
cache.clear()
# Test when the user is not in the group
with self.assertNumQueries(2):
self.skia.is_in_group(pk=group_not_in.id)
with self.assertNumQueries(0):
self.skia.is_in_group(pk=group_not_in.id)
def test_cache_properly_cleared_membership(self):
"""
Test that when the membership of a user end,
the cache is properly invalidated
"""
membership = Membership.objects.create(
club=self.club, user=self.toto, end_date=None
)
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
cache.clear()
self.assertTrue(self.toto.is_in_group(name=meta_groups_members))
self.assertEqual(
membership, cache.get(f"membership_{self.club.id}_{self.toto.id}")
)
membership.end_date = now() - timedelta(minutes=5)
membership.save()
cached_membership = cache.get(f"membership_{self.club.id}_{self.toto.id}")
self.assertEqual(cached_membership, "not_member")
self.assertFalse(self.toto.is_in_group(name=meta_groups_members))
def test_cache_properly_cleared_group(self):
"""
Test that when a user is removed from a group,
the is_in_group_method return False when calling it again
"""
self.toto.groups.add(self.com_admin.pk)
self.assertTrue(self.toto.is_in_group(pk=self.com_admin.pk))
self.toto.groups.remove(self.com_admin.pk)
self.assertFalse(self.toto.is_in_group(pk=self.com_admin.pk))
def test_not_existing_group(self):
"""
Test that searching for a not existing group
returns False
"""
self.assertFalse(self.skia.is_in_group(name="This doesn't exist"))

View File

@ -35,11 +35,13 @@ def get_git_revision_short_hash() -> str:
"""
Return the short hash of the current commit
"""
return (
subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
.decode("ascii")
.strip()
)
try:
output = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
if isinstance(output, bytes):
return output.decode("ascii").strip()
return output.strip()
except subprocess.CalledProcessError:
return ""
def get_start_of_semester(d=date.today()):

View File

@ -162,7 +162,6 @@ class GenericContentPermissionMixinBuilder(View):
return cls.permission_function(obj, user)
def dispatch(self, request, *arg, **kwargs):
if hasattr(self, "get_object") and callable(self.get_object):
self.object = self.get_object()
if not self.get_permission_function(self.object, request.user):

View File

@ -23,7 +23,7 @@ from django.views.generic.detail import SingleObjectMixin
from django.forms.models import modelform_factory
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django.http import HttpResponse
from django.http import Http404, HttpResponse
from wsgiref.util import FileWrapper
from django.urls import reverse
from django.core.exceptions import PermissionDenied
@ -34,7 +34,12 @@ import os
from ajax_select import make_ajax_field
from core.models import SithFile, RealGroup, Notification
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, can_view, not_found
from core.views import (
CanViewMixin,
CanEditMixin,
CanEditPropMixin,
can_view,
)
from counter.models import Counter
@ -58,6 +63,11 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"):
raise PermissionDenied
name = f.__getattribute__(file_attr).name
filepath = os.path.join(settings.MEDIA_ROOT, name)
# check if file exists on disk
if not os.path.exists(filepath.encode("utf-8")):
raise Http404()
with open(filepath.encode("utf-8"), "rb") as filename:
wrapper = FileWrapper(filename)
response = HttpResponse(wrapper, content_type=f.mime_type)
@ -152,6 +162,13 @@ class FileEditView(CanEditMixin, UpdateView):
template_name = "core/file_edit.jinja"
context_object_name = "file"
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if not self.object.can_be_managed_by(request.user):
raise PermissionDenied
return super(FileEditView, self).get(request, *args, **kwargs)
def get_form_class(self):
fields = ["name", "is_moderated"]
if self.object.is_file:
@ -197,6 +214,13 @@ class FileEditPropView(CanEditPropMixin, UpdateView):
context_object_name = "file"
form_class = FileEditPropForm
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if not self.object.can_be_managed_by(request.user):
raise PermissionDenied
return super(FileEditPropView, self).get(request, *args, **kwargs)
def get_form(self, form_class=None):
form = super(FileEditPropView, self).get_form(form_class)
form.fields["parent"].queryset = SithFile.objects.filter(is_folder=True)
@ -269,6 +293,9 @@ class FileView(CanViewMixin, DetailView, FormMixin):
def get(self, request, *args, **kwargs):
self.form = self.get_form()
if not self.object.can_be_managed_by(request.user):
raise PermissionDenied
if "clipboard" not in request.session.keys():
request.session["clipboard"] = []
return super(FileView, self).get(request, *args, **kwargs)
@ -316,6 +343,13 @@ class FileDeleteView(CanEditPropMixin, DeleteView):
template_name = "core/file_delete_confirm.jinja"
context_object_name = "file"
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if not self.object.can_be_managed_by(request.user):
raise PermissionDenied
return super(FileDeleteView, self).get(request, *args, **kwargs)
def get_success_url(self):
self.object.file.delete() # Doing it here or overloading delete() is the same, so let's do it here
if "next" in self.request.GET.keys():

View File

@ -82,6 +82,11 @@ class PageRevView(CanViewMixin, DetailView):
def dispatch(self, request, *args, **kwargs):
res = super(PageRevView, self).dispatch(request, *args, **kwargs)
self.object = self.get_object()
if self.object is None:
return redirect("core:page_create", page_name=self.kwargs["page_name"])
if self.object.need_club_redirection:
return redirect(
"club:club_view_rev", club_id=self.object.club.id, rev_id=kwargs["rev"]

View File

@ -31,6 +31,7 @@ from django.utils import html
from django.views.generic import ListView, TemplateView
from django.conf import settings
from django.utils.text import slugify
from django.db.models.query import QuerySet
import json
@ -51,12 +52,15 @@ class NotificationList(ListView):
model = Notification
template_name = "core/notification_list.jinja"
def get_queryset(self):
def get_queryset(self) -> QuerySet[Notification]:
if self.request.user.is_anonymous:
return Notification.objects.none()
# TODO: Bulk update in django 2.2
if "see_all" in self.request.GET.keys():
for n in self.request.user.notifications.filter(viewed=False):
n.viewed = True
n.save()
return self.request.user.notifications.order_by("-date")[:20]

View File

@ -254,10 +254,11 @@ class UserTabsMixin(TabedViewMixin):
if user.customer and (
user == self.request.user
or self.request.user.is_in_group(
settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
)
or self.request.user.is_in_group(
settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX
name=settings.SITH_BAR_MANAGER["unix_name"]
+ settings.SITH_BOARD_SUFFIX
)
or self.request.user.is_root
):
@ -320,7 +321,7 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
last_album = None
for picture in picture_qs:
album = picture.parent
if album.id != last_album:
if album.id != last_album and album not in kwargs["albums"]:
kwargs["albums"].append(album)
kwargs["pictures"][album.id] = []
last_album = album.id
@ -413,6 +414,7 @@ class UserGodfathersTreePictureView(CanViewMixin, DetailView):
self.graph = pgv.AGraph(strict=False, directed=True)
family = set()
self.level = 1
# Since the tree isn't very deep, we can build it recursively
def crawl_family(user):
if self.level > self.depth:
@ -487,9 +489,9 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
if not (
profile == request.user
or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or request.user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or request.user.is_in_group(
settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX
name=settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX
)
or request.user.is_root
):
@ -717,8 +719,12 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
def get_context_data(self, **kwargs):
kwargs = super(UserPreferencesView, self).get_context_data(**kwargs)
if not hasattr(self.object, "trombi_user"):
if not (
hasattr(self.object, "trombi_user") and self.request.user.trombi_user.trombi
):
kwargs["trombi_form"] = UserTrombiForm()
if hasattr(self.object, "customer"):
kwargs["student_card_form"] = StudentCardForm()
return kwargs
@ -771,9 +777,9 @@ class UserAccountBase(UserTabsMixin, DetailView):
res = super(UserAccountBase, self).dispatch(request, *arg, **kwargs)
if (
self.object == request.user
or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or request.user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or request.user.is_in_group(
settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX
name=settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX
)
or request.user.is_root
):