Compare commits

...

3 Commits

Author SHA1 Message Date
imperosol d36d672d0b actually fix bug where you can't select /SAS as a parent album 2026-04-22 00:07:39 +02:00
klmp200 da3602329c Merge pull request #1355 from ae-utbm/profile_whitelist
Fix hidden user can't search itself
2026-04-20 21:43:52 +02:00
klmp200 8b18999514 Fix hidden user can't search itself 2026-04-20 20:17:39 +02:00
7 changed files with 90 additions and 81 deletions
+3 -1
View File
@@ -110,7 +110,9 @@ class Command(BaseCommand):
p.save(force_lock=True) p.save(force_lock=True)
club_root = SithFile.objects.create(name="clubs", owner=root) club_root = SithFile.objects.create(name="clubs", owner=root)
sas = SithFile.objects.create(name="SAS", owner=root, is_in_sas=True) sas = SithFile.objects.create(
name="SAS", owner=root, id=settings.SITH_SAS_ROOT_DIR_ID
)
main_club = Club.objects.create( main_club = Club.objects.create(
id=1, name="AE", address="6 Boulevard Anatole France, 90000 Belfort" id=1, name="AE", address="6 Boulevard Anatole France, 90000 Belfort"
) )
+7 -3
View File
@@ -131,7 +131,9 @@ class UserQuerySet(models.QuerySet):
if user.has_perm("core.view_hidden_user"): if user.has_perm("core.view_hidden_user"):
return self return self
if user.has_perm("core.view_user"): if user.has_perm("core.view_user"):
return self.filter(Q(is_viewable=True) | Q(whitelisted_users=user)) return self.filter(
Q(is_viewable=True) | Q(whitelisted_users=user) | Q(pk=user.pk)
)
if user.is_anonymous: if user.is_anonymous:
return self.none() return self.none()
return self.filter(id=user.id) return self.filter(id=user.id)
@@ -884,8 +886,10 @@ class SithFile(models.Model):
return self.get_parent_path() + "/" + self.name return self.get_parent_path() + "/" + self.name
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
sas = SithFile.objects.filter(id=settings.SITH_SAS_ROOT_DIR_ID).first() sas_id = settings.SITH_SAS_ROOT_DIR_ID
self.is_in_sas = sas in self.get_parent_list() or self == sas self.is_in_sas = self.id == sas_id or any(
p.id == sas_id for p in self.get_parent_list()
)
adding = self._state.adding adding = self._state.adding
super().save(*args, **kwargs) super().save(*args, **kwargs)
if adding: if adding:
+11
View File
@@ -344,3 +344,14 @@ def test_quick_upload_image(
assert ( assert (
parsed["name"] == Path(file.name).stem[: QuickUploadImage.IMAGE_NAME_SIZE - 1] parsed["name"] == Path(file.name).stem[: QuickUploadImage.IMAGE_NAME_SIZE - 1]
) )
@pytest.mark.django_db
def test_populated_sas_is_in_sas():
"""Test that, in the data generated by the populate command,
the SAS has value is_in_sas=True.
If it's not the case, it has no incidence in prod, but it's annoying
in dev and may cause misunderstandings.
"""
assert SithFile.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID).is_in_sas
+12 -4
View File
@@ -410,12 +410,20 @@ class TestUserQuerySetViewableBy:
assert set(viewable) == set(users) assert set(viewable) == set(users)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"user_factory", [old_subscriber_user.make, subscriber_user.make] "user_factory",
[
old_subscriber_user.make,
lambda: old_subscriber_user.make(is_viewable=False),
subscriber_user.make,
lambda: subscriber_user.make(is_viewable=False),
],
) )
def test_subscriber(self, users: list[User], user_factory): def test_can_search(self, users: list[User], user_factory):
user = user_factory() user = user_factory()
viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user) viewable = User.objects.filter(
assert set(viewable) == {users[0], users[1]} id__in=[u.id for u in [*users, user]]
).viewable_by(user)
assert set(viewable) == {user, users[0], users[1]}
def test_whitelist(self, users: list[User]): def test_whitelist(self, users: list[User]):
user = subscriber_user.make() user = subscriber_user.make()
+1 -7
View File
@@ -205,13 +205,7 @@ class AlbumQuerySet(models.QuerySet):
class SASAlbumManager(models.Manager): class SASAlbumManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return ( return super().get_queryset().filter(Q(is_in_sas=True, is_folder=True))
super()
.get_queryset()
.filter(
Q(id=settings.SITH_SAS_ROOT_DIR_ID) | Q(is_in_sas=True, is_folder=True)
)
)
class Album(SasFile): class Album(SasFile):
+48 -61
View File
@@ -20,7 +20,7 @@ from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils.timezone import localdate
from model_bakery import baker from model_bakery import baker
from pytest_django.asserts import assertHTMLEqual, assertInHTML, assertRedirects from pytest_django.asserts import assertHTMLEqual, assertInHTML, assertRedirects
@@ -66,6 +66,24 @@ def test_main_page_no_form_for_regular_users(client: Client):
assert len(forms) == 0 assert len(forms) == 0
@pytest.mark.django_db
def test_main_page_displayed_albums(client: Client):
"""Test that the right data is displayed on the SAS main page"""
sas = Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID)
Album.objects.exclude(id=sas.id).delete()
album_a = baker.make(Album, parent=sas, is_moderated=True)
album_b = baker.make(Album, parent=album_a, is_moderated=True)
album_c = baker.make(Album, parent=sas, is_moderated=True)
baker.make(Album, parent=sas, is_moderated=False)
client.force_login(subscriber_user.make())
res = client.get(reverse("sas:main"))
# album_b is not a direct child of the SAS, so it shouldn't be displayed
# in the categories, but it should appear in the latest albums.
# album_d isn't moderated, so it shouldn't appear at all for a simple user.
assert res.context_data["latest"] == [album_c, album_b, album_a]
assert res.context_data["categories"] == [album_a, album_c]
@pytest.mark.django_db @pytest.mark.django_db
def test_main_page_content_anonymous(client: Client): def test_main_page_content_anonymous(client: Client):
"""Test that public users see only an incentive to login""" """Test that public users see only an incentive to login"""
@@ -149,11 +167,7 @@ class TestAlbumEdit:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"user", "user",
[ [None, lambda: baker.make(User), subscriber_user.make],
None,
lambda: baker.make(User),
subscriber_user.make,
],
) )
def test_permission_denied( def test_permission_denied(
self, self,
@@ -164,9 +178,10 @@ class TestAlbumEdit:
if user: if user:
client.force_login(user()) client.force_login(user())
response = client.get(reverse("sas:album_edit", kwargs={"album_id": album.pk})) url = reverse("sas:album_edit", kwargs={"album_id": album.pk})
response = client.get(url)
assert response.status_code == 403 assert response.status_code == 403
response = client.post(reverse("sas:album_edit", kwargs={"album_id": album.pk})) response = client.post(url)
assert response.status_code == 403 assert response.status_code == 403
def test_sas_root_read_only(self, client: Client, sas_root: Album): def test_sas_root_read_only(self, client: Client, sas_root: Album):
@@ -174,13 +189,10 @@ class TestAlbumEdit:
User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)]
) )
client.force_login(moderator) client.force_login(moderator)
response = client.get( url = reverse("sas:album_edit", kwargs={"album_id": sas_root.pk})
reverse("sas:album_edit", kwargs={"album_id": sas_root.pk}) response = client.get(url)
)
assert response.status_code == 404 assert response.status_code == 404
response = client.post( response = client.post(url)
reverse("sas:album_edit", kwargs={"album_id": sas_root.pk})
)
assert response.status_code == 404 assert response.status_code == 404
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -198,7 +210,7 @@ class TestAlbumEdit:
data = { data = {
"name": album.name[: Album.NAME_MAX_LENGTH], "name": album.name[: Album.NAME_MAX_LENGTH],
"parent": baker.make(Album, parent=album.parent, is_moderated=True).pk, "parent": baker.make(Album, parent=album.parent, is_moderated=True).pk,
"date": timezone.now().strftime("%Y-%m-%d"), "date": localdate().strftime("%Y-%m-%d"),
"file": "/random/path", "file": "/random/path",
"edit_groups": [settings.SITH_GROUP_SAS_ADMIN_ID], "edit_groups": [settings.SITH_GROUP_SAS_ADMIN_ID],
"recursive": False, "recursive": False,
@@ -210,7 +222,7 @@ class TestAlbumEdit:
data = { data = {
"name": album.name[: Album.NAME_MAX_LENGTH], "name": album.name[: Album.NAME_MAX_LENGTH],
"parent": album.pk, "parent": album.pk,
"date": timezone.now().strftime("%Y-%m-%d"), "date": localdate().strftime("%Y-%m-%d"),
} }
assert AlbumEditForm(data=data).is_valid() assert AlbumEditForm(data=data).is_valid()
@@ -223,19 +235,12 @@ class TestAlbumEdit:
payload = { payload = {
"name": album.name[: Album.NAME_MAX_LENGTH], "name": album.name[: Album.NAME_MAX_LENGTH],
"parent": album.pk, "parent": album.pk,
"date": timezone.now().strftime("%Y-%m-%d"), "date": localdate().strftime("%Y-%m-%d"),
} }
response = client.post( response = client.post(
reverse( reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload
"sas:album_edit",
kwargs={"album_id": album.pk},
),
payload,
)
assertInHTML(
"<li>Boucle dans l'arborescence des dossiers</li>",
response.text,
) )
assertInHTML("<li>Boucle dans l'arborescence des dossiers</li>", response.text)
assert response.status_code == 200 assert response.status_code == 200
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -247,57 +252,39 @@ class TestAlbumEdit:
), ),
], ],
) )
@pytest.mark.parametrize(
"parent",
[
lambda: baker.make(
Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True
),
lambda: Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID),
],
)
def test_update( def test_update(
self, self,
client: Client, client: Client,
album: Album, album: Album,
sas_root: Album, sas_root: Album,
user: Callable[[], User], user: Callable[[], User],
parent: Callable[[], Album],
): ):
client.force_login(user()) client.force_login(user())
# Prepare a good payload
expected_redirect = reverse("sas:album", kwargs={"album_id": album.pk}) expected_redirect = reverse("sas:album", kwargs={"album_id": album.pk})
expected_date = timezone.now()
payload = { payload = {
"name": album.name[: Album.NAME_MAX_LENGTH], "name": album.name[: Album.NAME_MAX_LENGTH],
"parent": baker.make(Album, parent=sas_root, is_moderated=True).pk, "parent": parent().id,
"date": expected_date.strftime("%Y-%m-%d"), "date": localdate().strftime("%Y-%m-%d"),
"recursive": False, "recursive": False,
} }
# Test successful update
response = client.post( response = client.post(
reverse( reverse("sas:album_edit", kwargs={"album_id": album.pk}), payload
"sas:album_edit",
kwargs={"album_id": album.pk},
),
payload,
) )
assertRedirects(response, expected_redirect) assertRedirects(response, expected_redirect)
updated_album = Album.objects.get(id=album.pk) album.refresh_from_db()
assert updated_album.name == payload["name"] assert album.name == payload["name"]
assert updated_album.parent.id == payload["parent"] assert album.parent.id == payload["parent"]
assert timezone.localdate(updated_album.date) == timezone.localdate( assert localdate(album.date) == localdate()
expected_date
)
# Test root album can be used as parent
payload["parent"] = sas_root.pk
response = client.post(
reverse(
"sas:album_edit",
kwargs={"album_id": album.pk},
),
payload,
)
assertRedirects(response, expected_redirect)
updated_album = Album.objects.get(id=album.pk)
assert updated_album.name == payload["name"]
assert updated_album.parent.id == payload["parent"]
assert timezone.localdate(updated_album.date) == timezone.localdate(
expected_date
)
class TestSasModeration(TestCase): class TestSasModeration(TestCase):
+8 -5
View File
@@ -37,7 +37,7 @@ from sas.forms import (
PictureModerationRequestForm, PictureModerationRequestForm,
PictureUploadForm, PictureUploadForm,
) )
from sas.models import Album, AlbumQuerySet, PeoplePictureRelation, Picture from sas.models import Album, PeoplePictureRelation, Picture
class AlbumCreateFragment(FragmentMixin, CreateView): class AlbumCreateFragment(FragmentMixin, CreateView):
@@ -85,7 +85,9 @@ class SASMainView(UseFragmentsMixin, TemplateView):
kwargs["categories"] = list( kwargs["categories"] = list(
albums_qs.filter(parent_id=settings.SITH_SAS_ROOT_DIR_ID).order_by("id") albums_qs.filter(parent_id=settings.SITH_SAS_ROOT_DIR_ID).order_by("id")
) )
kwargs["latest"] = list(albums_qs.order_by("-id")[:5]) kwargs["latest"] = list(
albums_qs.exclude(id=settings.SITH_SAS_ROOT_DIR_ID).order_by("-id")[:5]
)
return kwargs return kwargs
@@ -126,6 +128,9 @@ def send_thumb(request, picture_id):
class AlbumView(CanViewMixin, UseFragmentsMixin, DetailView): class AlbumView(CanViewMixin, UseFragmentsMixin, DetailView):
model = Album model = Album
# exclude the SAS from the album accessible with this view
# the SAS can be viewed only with SASMainView
queryset = Album.objects.exclude(id=settings.SITH_SAS_ROOT_DIR_ID)
pk_url_kwarg = "album_id" pk_url_kwarg = "album_id"
template_name = "sas/album.jinja" template_name = "sas/album.jinja"
@@ -262,13 +267,11 @@ class PictureAskRemovalView(CanViewMixin, DetailView, FormView):
class AlbumEditView(CanEditMixin, UpdateView): class AlbumEditView(CanEditMixin, UpdateView):
model = Album model = Album
queryset = Album.objects.exclude(id=settings.SITH_SAS_ROOT_DIR_ID)
form_class = AlbumEditForm form_class = AlbumEditForm
template_name = "core/edit.jinja" template_name = "core/edit.jinja"
pk_url_kwarg = "album_id" pk_url_kwarg = "album_id"
def get_queryset(self) -> AlbumQuerySet:
return super().get_queryset().exclude(id=settings.SITH_SAS_ROOT_DIR_ID)
def form_valid(self, form): def form_valid(self, form):
ret = super().form_valid(form) ret = super().form_valid(form)
if form.cleaned_data["recursive"]: if form.cleaned_data["recursive"]: