diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index 4c1c5379..5a282143 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -16,7 +16,7 @@ # details. # # You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple # Place - Suite 330, Boston, MA 02111-1307, USA. # # @@ -110,7 +110,7 @@ class Command(BaseCommand): p.save(force_lock=True) club_root = SithFile.objects.create(name="clubs", owner=root) - sas = SithFile.objects.create(name="SAS", owner=root) + sas = SithFile.objects.create(name="SAS", owner=root, is_in_sas=True) main_club = Club.objects.create( id=1, name="AE", address="6 Boulevard Anatole France, 90000 Belfort" ) diff --git a/sas/forms.py b/sas/forms.py index af3547c8..a478ce43 100644 --- a/sas/forms.py +++ b/sas/forms.py @@ -50,13 +50,15 @@ class AlbumEditForm(forms.ModelForm): model = Album fields = ["name", "date", "file", "parent", "edit_groups"] widgets = { - "parent": AutoCompleteSelectAlbum, "edit_groups": AutoCompleteSelectMultipleGroup, } name = forms.CharField(max_length=Album.NAME_MAX_LENGTH, label=_("file name")) date = forms.DateField(label=_("Date"), widget=SelectDate, required=True) recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) + parent = forms.ModelChoiceField( + Album.objects.all(), required=True, widget=AutoCompleteSelectAlbum + ) class PictureModerationRequestForm(forms.ModelForm): diff --git a/sas/models.py b/sas/models.py index 64f6c15b..5d8cee44 100644 --- a/sas/models.py +++ b/sas/models.py @@ -205,7 +205,13 @@ class AlbumQuerySet(models.QuerySet): class SASAlbumManager(models.Manager): def get_queryset(self): - return super().get_queryset().filter(is_in_sas=True, is_folder=True) + return ( + super() + .get_queryset() + .filter( + Q(id=settings.SITH_SAS_ROOT_DIR_ID) | Q(is_in_sas=True, is_folder=True) + ) + ) class Album(SasFile): diff --git a/sas/tests/test_views.py b/sas/tests/test_views.py index 8d67997d..9aed6e94 100644 --- a/sas/tests/test_views.py +++ b/sas/tests/test_views.py @@ -20,12 +20,14 @@ from django.conf import settings from django.core.cache import cache from django.test import Client, TestCase from django.urls import reverse +from django.utils import timezone from model_bakery import baker from pytest_django.asserts import assertHTMLEqual, assertInHTML, assertRedirects from core.baker_recipes import old_subscriber_user, subscriber_user from core.models import Group, User from sas.baker_recipes import picture_recipe +from sas.forms import AlbumEditForm from sas.models import Album, Picture # Create your tests here. @@ -133,6 +135,171 @@ class TestAlbumUpload: assert not album.children.exists() +@pytest.mark.django_db +class TestAlbumEdit: + @pytest.fixture + def sas_root(self) -> Album: + return Album.objects.get(id=settings.SITH_SAS_ROOT_DIR_ID) + + @pytest.fixture + def album(self) -> Album: + return baker.make( + Album, parent_id=settings.SITH_SAS_ROOT_DIR_ID, is_moderated=True + ) + + @pytest.mark.parametrize( + "user", + [ + None, + subscriber_user.make, + ], + ) + def test_permission_denied( + self, + client: Client, + album: Album, + request: pytest.FixtureRequest, + user: Callable[[], User] | None, + ): + if user: + client.force_login(user()) + + response = client.get(reverse("sas:album_edit", kwargs={"album_id": album.pk})) + assert response.status_code == 403 + response = client.post(reverse("sas:album_edit", kwargs={"album_id": album.pk})) + assert response.status_code == 403 + + def test_sas_root_read_only(self, client: Client, sas_root: Album): + moderator = baker.make( + User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] + ) + client.force_login(moderator) + response = client.get( + reverse("sas:album_edit", kwargs={"album_id": sas_root.pk}) + ) + assert response.status_code == 404 + response = client.post( + reverse("sas:album_edit", kwargs={"album_id": sas_root.pk}) + ) + assert response.status_code == 404 + + @pytest.mark.parametrize( + ("excluded", "is_valid"), + [ + ("name", False), + ("date", False), + ("file", True), + ("parent", False), + ("edit_groups", True), + ("recursive", True), + ], + ) + def test_form_required(self, album: Album, excluded: str, is_valid: bool): # noqa: FBT001 + data = { + "name": album.name[: Album.NAME_MAX_LENGTH], + "parent": baker.make(Album, parent=album.parent, is_moderated=True).pk, + "date": timezone.now().strftime("%Y-%m-%d"), + "file": "/random/path", + "edit_groups": [settings.SITH_GROUP_SAS_ADMIN_ID], + "recursive": False, + } + del data[excluded] + assert AlbumEditForm(data=data).is_valid() == is_valid + + def test_form_album_name(self, album: Album): + data = { + "name": album.name[: Album.NAME_MAX_LENGTH], + "parent": album.pk, + "date": timezone.now().strftime("%Y-%m-%d"), + } + assert AlbumEditForm(data=data).is_valid() + + data["name"] = album.name[: Album.NAME_MAX_LENGTH + 1] + assert not AlbumEditForm(data=data).is_valid() + + def test_update_recursive_parent(self, client: Client, album: Album): + client.force_login(User.objects.get(username="root")) + + payload = { + "name": album.name[: Album.NAME_MAX_LENGTH], + "parent": album.pk, + "date": timezone.now().strftime("%Y-%m-%d"), + } + response = client.post( + reverse( + "sas:album_edit", + kwargs={"album_id": album.pk}, + ), + payload, + ) + assertInHTML( + "
  • Boucle dans l'arborescence des dossiers
  • ", + response.text, + ) + assert response.status_code == 200 + + @pytest.mark.parametrize( + "user", + [ + lambda: User.objects.get(username="root"), + lambda: baker.make( + User, groups=[Group.objects.get(pk=settings.SITH_GROUP_SAS_ADMIN_ID)] + ), + ], + ) + def test_update( + self, + client: Client, + album: Album, + sas_root: Album, + user: Callable[[], User], + ): + client.force_login(user()) + + # Prepare a good payload + expected_redirect = reverse("sas:album", kwargs={"album_id": album.pk}) + expected_date = timezone.now() + payload = { + "name": album.name[: Album.NAME_MAX_LENGTH], + "parent": baker.make(Album, parent=sas_root, is_moderated=True).pk, + "date": expected_date.strftime("%Y-%m-%d"), + "recursive": False, + } + + # Test successful update + 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 + ) + + # 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): @classmethod def setUpTestData(cls): diff --git a/sas/views.py b/sas/views.py index a2145a94..7e58b97b 100644 --- a/sas/views.py +++ b/sas/views.py @@ -37,7 +37,7 @@ from sas.forms import ( PictureModerationRequestForm, PictureUploadForm, ) -from sas.models import Album, PeoplePictureRelation, Picture +from sas.models import Album, AlbumQuerySet, PeoplePictureRelation, Picture class AlbumCreateFragment(FragmentMixin, CreateView): @@ -266,6 +266,9 @@ class AlbumEditView(CanEditMixin, UpdateView): template_name = "core/edit.jinja" 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): ret = super().form_valid(form) if form.cleaned_data["recursive"]: