mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-11-04 09:12:11 +01:00 
			
		
		
		
	Unit tests for API pages, closes #83
Signed-off-by: Yohann D'ANELLO <yohann.danello@gmail.com>
This commit is contained in:
		@@ -15,7 +15,7 @@ class ActivityTypeViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `ActivityType` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/activity/type/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = ActivityType.objects.all()
 | 
			
		||||
    queryset = ActivityType.objects.order_by('id')
 | 
			
		||||
    serializer_class = ActivityTypeSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend]
 | 
			
		||||
    filterset_fields = ['name', 'manage_entries', 'can_invite', 'guest_entry_fee', ]
 | 
			
		||||
@@ -27,7 +27,7 @@ class ActivityViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Activity` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/activity/activity/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Activity.objects.all()
 | 
			
		||||
    queryset = Activity.objects.order_by('id')
 | 
			
		||||
    serializer_class = ActivitySerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['name', 'description', 'activity_type', 'location', 'creater', 'organizer', 'attendees_club',
 | 
			
		||||
@@ -45,7 +45,7 @@ class GuestViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Guest` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/activity/guest/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Guest.objects.all()
 | 
			
		||||
    queryset = Guest.objects.order_by('id')
 | 
			
		||||
    serializer_class = GuestSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['activity', 'activity__name', 'last_name', 'first_name', 'inviter', 'inviter__alias__name',
 | 
			
		||||
@@ -60,7 +60,7 @@ class EntryViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Entry` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/activity/entry/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Entry.objects.all()
 | 
			
		||||
    queryset = Entry.objects.order_by('id')
 | 
			
		||||
    serializer_class = EntrySerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['activity', 'time', 'note', 'guest', ]
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,16 @@
 | 
			
		||||
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
 | 
			
		||||
from api.tests import TestAPI
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from activity.models import Activity, ActivityType, Guest, Entry
 | 
			
		||||
from member.models import Club
 | 
			
		||||
 | 
			
		||||
from ..api.views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet
 | 
			
		||||
from ..models import Activity, ActivityType, Guest, Entry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestActivities(TestCase):
 | 
			
		||||
    """
 | 
			
		||||
@@ -173,3 +176,43 @@ class TestActivities(TestCase):
 | 
			
		||||
        """
 | 
			
		||||
        response = self.client.get(reverse("activity:calendar_ics"))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestActivityAPI(TestAPI):
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
 | 
			
		||||
        self.activity = Activity.objects.create(
 | 
			
		||||
            name="Activity",
 | 
			
		||||
            description="This is a test activity\non two very very long lines\nbecause this is very important.",
 | 
			
		||||
            location="Earth",
 | 
			
		||||
            activity_type=ActivityType.objects.get(name="Pot"),
 | 
			
		||||
            creater=self.user,
 | 
			
		||||
            organizer=Club.objects.get(name="Kfet"),
 | 
			
		||||
            attendees_club=Club.objects.get(name="Kfet"),
 | 
			
		||||
            date_start=timezone.now(),
 | 
			
		||||
            date_end=timezone.now() + timedelta(days=2),
 | 
			
		||||
            valid=True,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.guest = Guest.objects.create(
 | 
			
		||||
            activity=self.activity,
 | 
			
		||||
            inviter=self.user.note,
 | 
			
		||||
            last_name="GUEST",
 | 
			
		||||
            first_name="Guest",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.entry = Entry.objects.create(
 | 
			
		||||
            activity=self.activity,
 | 
			
		||||
            note=self.user.note,
 | 
			
		||||
            guest=self.guest,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_activity_api(self):
 | 
			
		||||
        """
 | 
			
		||||
        Load API pages for the activity app and test all filters
 | 
			
		||||
        """
 | 
			
		||||
        self.check_viewset(ActivityViewSet, "/api/activity/activity/")
 | 
			
		||||
        self.check_viewset(ActivityTypeViewSet, "/api/activity/type/")
 | 
			
		||||
        self.check_viewset(EntryViewSet, "/api/activity/entry/")
 | 
			
		||||
        self.check_viewset(GuestViewSet, "/api/activity/guest/")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										161
									
								
								apps/api/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								apps/api/tests.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from urllib.parse import quote_plus
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django_filters.rest_framework import DjangoFilterBackend, OrderingFilter
 | 
			
		||||
from note.models import NoteClub, NoteUser, Alias, Note
 | 
			
		||||
from phonenumbers import PhoneNumber
 | 
			
		||||
from rest_framework.filters import SearchFilter
 | 
			
		||||
 | 
			
		||||
from .viewsets import ContentTypeViewSet, UserViewSet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestAPI(TestCase):
 | 
			
		||||
    """
 | 
			
		||||
    Load API pages and check that filters are working.
 | 
			
		||||
    """
 | 
			
		||||
    fixtures = ('initial', )
 | 
			
		||||
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        self.user = User.objects.create_superuser(
 | 
			
		||||
            username="adminapi",
 | 
			
		||||
            password="adminapi",
 | 
			
		||||
            email="adminapi@example.com",
 | 
			
		||||
            last_name="Admin",
 | 
			
		||||
            first_name="Admin",
 | 
			
		||||
        )
 | 
			
		||||
        self.client.force_login(self.user)
 | 
			
		||||
 | 
			
		||||
        sess = self.client.session
 | 
			
		||||
        sess["permission_mask"] = 42
 | 
			
		||||
        sess.save()
 | 
			
		||||
 | 
			
		||||
    def check_viewset(self, viewset, url):
 | 
			
		||||
        """
 | 
			
		||||
        This function should be called inside a unit test.
 | 
			
		||||
        This loads the viewset and for each filter entry, it checks that the filter is running good.
 | 
			
		||||
        """
 | 
			
		||||
        resp = self.client.get(url + "?format=json")
 | 
			
		||||
        self.assertEqual(resp.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        model = viewset.serializer_class.Meta.model
 | 
			
		||||
 | 
			
		||||
        if not model.objects.exists():  # pragma: no cover
 | 
			
		||||
            print(f"Warning: unable to test API filters for the model {model._meta.verbose_name} "
 | 
			
		||||
                  "since there is no instance of it.")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if hasattr(viewset, "filter_backends"):
 | 
			
		||||
            backends = viewset.filter_backends
 | 
			
		||||
            obj = model.objects.last()
 | 
			
		||||
 | 
			
		||||
            if DjangoFilterBackend in backends:
 | 
			
		||||
                # Specific search
 | 
			
		||||
                for field in viewset.filterset_fields:
 | 
			
		||||
                    obj = self.fix_note_object(obj, field)
 | 
			
		||||
 | 
			
		||||
                    value = self.get_value(obj, field)
 | 
			
		||||
                    if value is None:  # pragma: no cover
 | 
			
		||||
                        print(f"Warning: the filter {field} for the model {model._meta.verbose_name} "
 | 
			
		||||
                              "has not been tested.")
 | 
			
		||||
                        continue
 | 
			
		||||
                    resp = self.client.get(url + f"?format=json&{field}={quote_plus(str(value))}")
 | 
			
		||||
                    self.assertEqual(resp.status_code, 200, f"The filter {field} for the model "
 | 
			
		||||
                                                            f"{model._meta.verbose_name} does not work. "
 | 
			
		||||
                                                            f"Given parameter: {value}")
 | 
			
		||||
                    content = json.loads(resp.content)
 | 
			
		||||
                    self.assertGreater(content["count"], 0, f"The filter {field} for the model "
 | 
			
		||||
                                                            f"{model._meta.verbose_name} does not work. "
 | 
			
		||||
                                                            f"Given parameter: {value}")
 | 
			
		||||
 | 
			
		||||
            if OrderingFilter in backends:
 | 
			
		||||
                # Ensure that ordering is working well
 | 
			
		||||
                for field in viewset.ordering_fields:
 | 
			
		||||
                    resp = self.client.get(url + f"?ordering={field}")
 | 
			
		||||
                    self.assertEqual(resp.status_code, 200)
 | 
			
		||||
                    resp = self.client.get(url + f"?ordering=-{field}")
 | 
			
		||||
                    self.assertEqual(resp.status_code, 200)
 | 
			
		||||
 | 
			
		||||
            if SearchFilter in backends:
 | 
			
		||||
                # Basic search
 | 
			
		||||
                for field in viewset.search_fields:
 | 
			
		||||
                    obj = self.fix_note_object(obj, field)
 | 
			
		||||
 | 
			
		||||
                    if field[0] == '$' or field[0] == '=':
 | 
			
		||||
                        field = field[1:]
 | 
			
		||||
                    value = self.get_value(obj, field)
 | 
			
		||||
                    if value is None:  # pragma: no cover
 | 
			
		||||
                        print(f"Warning: the filter {field} for the model {model._meta.verbose_name} "
 | 
			
		||||
                              "has not been tested.")
 | 
			
		||||
                        continue
 | 
			
		||||
                    resp = self.client.get(url + f"?format=json&search={quote_plus(str(value))}")
 | 
			
		||||
                    self.assertEqual(resp.status_code, 200, f"The filter {field} for the model "
 | 
			
		||||
                                                            f"{model._meta.verbose_name} does not work. "
 | 
			
		||||
                                                            f"Given parameter: {value}")
 | 
			
		||||
                    content = json.loads(resp.content)
 | 
			
		||||
                    self.assertGreater(content["count"], 0, f"The filter {field} for the model "
 | 
			
		||||
                                                            f"{model._meta.verbose_name} does not work. "
 | 
			
		||||
                                                            f"Given parameter: {value}")
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_value(obj, key: str):
 | 
			
		||||
        """
 | 
			
		||||
        Resolve the queryset filter to get the Python value of an object.
 | 
			
		||||
        """
 | 
			
		||||
        if hasattr(obj, "all"):
 | 
			
		||||
            # obj is a RelatedManager
 | 
			
		||||
            obj = obj.last()
 | 
			
		||||
 | 
			
		||||
        if obj is None:  # pragma: no cover
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        if '__' not in key:
 | 
			
		||||
            obj = getattr(obj, key)
 | 
			
		||||
            if hasattr(obj, "pk"):
 | 
			
		||||
                return obj.pk
 | 
			
		||||
            elif hasattr(obj, "all"):
 | 
			
		||||
                if not obj.exists():  # pragma: no cover
 | 
			
		||||
                    return None
 | 
			
		||||
                return obj.last().pk
 | 
			
		||||
            elif isinstance(obj, bool):
 | 
			
		||||
                return int(obj)
 | 
			
		||||
            elif isinstance(obj, datetime):
 | 
			
		||||
                return obj.isoformat()
 | 
			
		||||
            elif isinstance(obj, PhoneNumber):
 | 
			
		||||
                return obj.raw_input
 | 
			
		||||
            return obj
 | 
			
		||||
 | 
			
		||||
        key, remaining = key.split('__', 1)
 | 
			
		||||
        return TestAPI.get_value(getattr(obj, key), remaining)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def fix_note_object(obj, field):
 | 
			
		||||
        """
 | 
			
		||||
        When querying an object that has a noteclub or a noteuser field,
 | 
			
		||||
        ensure that the object has a good value.
 | 
			
		||||
        """
 | 
			
		||||
        if isinstance(obj, Alias):
 | 
			
		||||
            if "noteuser" in field:
 | 
			
		||||
                return NoteUser.objects.last().alias.last()
 | 
			
		||||
            elif "noteclub" in field:
 | 
			
		||||
                return NoteClub.objects.last().alias.last()
 | 
			
		||||
        elif isinstance(obj, Note):
 | 
			
		||||
            if "noteuser" in field:
 | 
			
		||||
                return NoteUser.objects.last()
 | 
			
		||||
            elif "noteclub" in field:
 | 
			
		||||
                return NoteClub.objects.last()
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestBasicAPI(TestAPI):
 | 
			
		||||
    def test_user_api(self):
 | 
			
		||||
        """
 | 
			
		||||
        Load the user page.
 | 
			
		||||
        """
 | 
			
		||||
        self.check_viewset(ContentTypeViewSet, "/api/models/")
 | 
			
		||||
        self.check_viewset(UserViewSet, "/api/user/")
 | 
			
		||||
@@ -6,6 +6,7 @@ from django_filters.rest_framework import DjangoFilterBackend
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from rest_framework.filters import SearchFilter
 | 
			
		||||
from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet
 | 
			
		||||
from permission.backends import PermissionBackend
 | 
			
		||||
from note_kfet.middlewares import get_current_session
 | 
			
		||||
@@ -48,12 +49,13 @@ class UserViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    """
 | 
			
		||||
    REST API View set.
 | 
			
		||||
    The djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/users/
 | 
			
		||||
    then render it on /api/user/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = User.objects.all()
 | 
			
		||||
    queryset = User.objects
 | 
			
		||||
    serializer_class = UserSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend]
 | 
			
		||||
    filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active', ]
 | 
			
		||||
    filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active',
 | 
			
		||||
                        'note__alias__name', 'note__alias__normalized_name', ]
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        queryset = super().get_queryset()
 | 
			
		||||
@@ -106,7 +108,10 @@ class ContentTypeViewSet(ReadOnlyModelViewSet):
 | 
			
		||||
    """
 | 
			
		||||
    REST API View set.
 | 
			
		||||
    The djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/users/
 | 
			
		||||
    then render it on /api/models/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = ContentType.objects.all()
 | 
			
		||||
    queryset = ContentType.objects.order_by('id')
 | 
			
		||||
    serializer_class = ContentTypeSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['id', 'app_label', 'model', ]
 | 
			
		||||
    search_fields = ['$app_label', '$model', ]
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ class ChangelogViewSet(ReadOnlyProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Changelog` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/logs/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Changelog.objects.all()
 | 
			
		||||
    queryset = Changelog.objects.order_by('id')
 | 
			
		||||
    serializer_class = ChangelogSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, OrderingFilter]
 | 
			
		||||
    filterset_fields = ['model', 'action', "instance_pk", 'user', 'ip', ]
 | 
			
		||||
 
 | 
			
		||||
@@ -15,14 +15,14 @@ class ProfileViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Profile` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/members/profile/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Profile.objects.all()
 | 
			
		||||
    queryset = Profile.objects.order_by('id')
 | 
			
		||||
    serializer_class = ProfileSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['user', 'user__first_name', 'user__last_name', 'user__username', 'user__email',
 | 
			
		||||
                        'user__note__alias__name', 'user__note__alias__normalized_name', 'phone_number', "section",
 | 
			
		||||
                        'department', 'promotion', 'address', 'paid', 'ml_events_registration', 'ml_sport_registration',
 | 
			
		||||
                        'ml_art_registration', 'report_frequency', 'email_confirmed', 'registration_valid', ]
 | 
			
		||||
    search_fields = ['$user__first_name' '$user__last_name', '$user__username', '$user__email',
 | 
			
		||||
    search_fields = ['$user__first_name', '$user__last_name', '$user__username', '$user__email',
 | 
			
		||||
                     '$user__note__alias__name', '$user__note__alias__normalized_name', ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -32,7 +32,7 @@ class ClubViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Club` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/members/club/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Club.objects.all()
 | 
			
		||||
    queryset = Club.objects.order_by('id')
 | 
			
		||||
    serializer_class = ClubSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['name', 'email', 'note__alias__name', 'note__alias__normalized_name', 'parent_club',
 | 
			
		||||
@@ -47,7 +47,7 @@ class MembershipViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Membership` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/members/membership/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Membership.objects.all()
 | 
			
		||||
    queryset = Membership.objects.order_by('id')
 | 
			
		||||
    serializer_class = MembershipSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
 | 
			
		||||
    filterset_fields = ['club__name', 'club__email', 'club__note__alias__name', 'club__note__alias__normalized_name',
 | 
			
		||||
 
 | 
			
		||||
@@ -313,6 +313,7 @@ class Membership(models.Model):
 | 
			
		||||
 | 
			
		||||
    roles = models.ManyToManyField(
 | 
			
		||||
        "permission.Role",
 | 
			
		||||
        related_name="memberships",
 | 
			
		||||
        verbose_name=_("roles"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@
 | 
			
		||||
    <dd class="col-xl-6">
 | 
			
		||||
        <a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
 | 
			
		||||
            <i class="fa fa-edit"></i>
 | 
			
		||||
            {% trans 'Manage aliases' %} ({{ club.note.alias_set.all|length }})
 | 
			
		||||
            {% trans 'Manage aliases' %} ({{ club.note.alias.all|length }})
 | 
			
		||||
        </a>
 | 
			
		||||
    </dd>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
    <dd class="col-xl-6">
 | 
			
		||||
        <a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
 | 
			
		||||
            <i class="fa fa-edit"></i>
 | 
			
		||||
            {% trans 'Manage aliases' %} ({{ user_object.note.alias_set.all|length }})
 | 
			
		||||
            {% trans 'Manage aliases' %} ({{ user_object.note.alias.all|length }})
 | 
			
		||||
        </a>
 | 
			
		||||
    </dd>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,17 +5,20 @@ import hashlib
 | 
			
		||||
import os
 | 
			
		||||
from datetime import date, timedelta
 | 
			
		||||
 | 
			
		||||
from api.tests import TestAPI
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.core.files.uploadedfile import SimpleUploadedFile
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from member.models import Club, Membership, Profile
 | 
			
		||||
from note.models import Alias, NoteSpecial
 | 
			
		||||
from permission.models import Role
 | 
			
		||||
from treasury.models import SogeCredit
 | 
			
		||||
 | 
			
		||||
from ..api.views import ClubViewSet, MembershipViewSet, ProfileViewSet
 | 
			
		||||
from ..models import Club, Membership, Profile
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Create some users and clubs and test that all pages are rendering properly
 | 
			
		||||
and that memberships are working.
 | 
			
		||||
@@ -403,3 +406,36 @@ class TestMemberships(TestCase):
 | 
			
		||||
        self.user.password = "custom_nk15$1$" + salt + "|" + hashed
 | 
			
		||||
        self.user.save()
 | 
			
		||||
        self.assertTrue(self.user.check_password(password))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestMemberAPI(TestAPI):
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
 | 
			
		||||
        self.user.profile.registration_valid = True
 | 
			
		||||
        self.user.profile.email_confirmed = True
 | 
			
		||||
        self.user.profile.phone_number = "0600000000"
 | 
			
		||||
        self.user.profile.section = "1A0"
 | 
			
		||||
        self.user.profile.department = "A0"
 | 
			
		||||
        self.user.profile.address = "Earth"
 | 
			
		||||
        self.user.profile.save()
 | 
			
		||||
 | 
			
		||||
        self.club = Club.objects.create(
 | 
			
		||||
            name="totoclub",
 | 
			
		||||
            parent_club=Club.objects.get(name="BDE"),
 | 
			
		||||
            membership_start=date(year=1970, month=1, day=1),
 | 
			
		||||
            membership_end=date(year=2040, month=1, day=1),
 | 
			
		||||
            membership_duration=365 * 10,
 | 
			
		||||
        )
 | 
			
		||||
        self.bde_membership = Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE"))
 | 
			
		||||
        self.membership = Membership.objects.create(user=self.user, club=self.club)
 | 
			
		||||
        self.membership.roles.add(Role.objects.get(name="Bureau de club"))
 | 
			
		||||
        self.membership.save()
 | 
			
		||||
 | 
			
		||||
    def test_member_api(self):
 | 
			
		||||
        """
 | 
			
		||||
        Load API pages for the member app and test all filters
 | 
			
		||||
        """
 | 
			
		||||
        self.check_viewset(ClubViewSet, "/api/members/club/")
 | 
			
		||||
        self.check_viewset(ProfileViewSet, "/api/members/profile/")
 | 
			
		||||
        self.check_viewset(MembershipViewSet, "/api/members/membership/")
 | 
			
		||||
 
 | 
			
		||||
@@ -256,7 +256,7 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        note = context['object'].note
 | 
			
		||||
        context["aliases"] = AliasTable(
 | 
			
		||||
            note.alias_set.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all())
 | 
			
		||||
            note.alias.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all())
 | 
			
		||||
        context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
 | 
			
		||||
            note=context["object"].note,
 | 
			
		||||
            name="",
 | 
			
		||||
@@ -458,7 +458,7 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        note = context['object'].note
 | 
			
		||||
        context["aliases"] = AliasTable(note.alias_set.filter(
 | 
			
		||||
        context["aliases"] = AliasTable(note.alias.filter(
 | 
			
		||||
            PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all())
 | 
			
		||||
        context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias(
 | 
			
		||||
            note=context["object"].note,
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/note/note/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Note.objects.all()
 | 
			
		||||
    queryset = Note.objects.order_by('id')
 | 
			
		||||
    serializer_class = NotePolymorphicSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
 | 
			
		||||
    filterset_fields = ['alias__name', 'polymorphic_ctype', 'is_active', 'balance', 'last_negative', 'created_at', ]
 | 
			
		||||
@@ -58,7 +58,7 @@ class AliasViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Alias` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/aliases/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Alias.objects.all()
 | 
			
		||||
    queryset = Alias.objects
 | 
			
		||||
    serializer_class = AliasSerializer
 | 
			
		||||
    filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
 | 
			
		||||
    search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
 | 
			
		||||
@@ -109,7 +109,7 @@ class AliasViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
 | 
			
		||||
    queryset = Alias.objects.all()
 | 
			
		||||
    queryset = Alias.objects
 | 
			
		||||
    serializer_class = ConsumerSerializer
 | 
			
		||||
    filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
 | 
			
		||||
    search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
 | 
			
		||||
@@ -160,7 +160,7 @@ class TemplateCategoryViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `TemplateCategory` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/note/transaction/category/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = TemplateCategory.objects.order_by("name").all()
 | 
			
		||||
    queryset = TemplateCategory.objects.order_by('name')
 | 
			
		||||
    serializer_class = TemplateCategorySerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['name', 'templates', 'templates__name']
 | 
			
		||||
@@ -173,7 +173,7 @@ class TransactionTemplateViewSet(viewsets.ModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/note/transaction/template/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = TransactionTemplate.objects.order_by("name").all()
 | 
			
		||||
    queryset = TransactionTemplate.objects.order_by('name')
 | 
			
		||||
    serializer_class = TransactionTemplateSerializer
 | 
			
		||||
    filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
 | 
			
		||||
    filterset_fields = ['name', 'amount', 'display', 'category', 'category__name', ]
 | 
			
		||||
@@ -187,7 +187,7 @@ class TransactionViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Transaction` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/note/transaction/transaction/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Transaction.objects.order_by("-created_at").all()
 | 
			
		||||
    queryset = Transaction.objects.order_by('-created_at')
 | 
			
		||||
    serializer_class = TransactionPolymorphicSerializer
 | 
			
		||||
    filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
 | 
			
		||||
    filterset_fields = ['source', 'source_alias', 'source__alias__name', 'source__alias__normalized_name',
 | 
			
		||||
 
 | 
			
		||||
@@ -248,6 +248,7 @@ class Alias(models.Model):
 | 
			
		||||
    note = models.ForeignKey(
 | 
			
		||||
        Note,
 | 
			
		||||
        on_delete=models.PROTECT,
 | 
			
		||||
        related_name="alias",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,20 @@
 | 
			
		||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from api.tests import TestAPI
 | 
			
		||||
from member.models import Club, Membership
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.contrib.contenttypes.models import ContentType
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from member.models import Club, Membership
 | 
			
		||||
from note.models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
 | 
			
		||||
    MembershipTransaction, SpecialTransaction, NoteSpecial, Alias
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from permission.models import Role
 | 
			
		||||
 | 
			
		||||
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\
 | 
			
		||||
    TransactionTemplateViewSet, TransactionViewSet
 | 
			
		||||
from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
 | 
			
		||||
    MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestTransactions(TestCase):
 | 
			
		||||
    fixtures = ('initial', )
 | 
			
		||||
@@ -297,8 +302,8 @@ class TestTransactions(TestCase):
 | 
			
		||||
 | 
			
		||||
    def test_render_search_transactions(self):
 | 
			
		||||
        response = self.client.get(reverse("note:transactions", args=(self.user.note.pk,)), data=dict(
 | 
			
		||||
            source=self.second_user.note.alias_set.first().id,
 | 
			
		||||
            destination=self.user.note.alias_set.first().id,
 | 
			
		||||
            source=self.second_user.note.alias.first().id,
 | 
			
		||||
            destination=self.user.note.alias.first().id,
 | 
			
		||||
            type=[ContentType.objects.get_for_model(Transaction).id],
 | 
			
		||||
            reason="test",
 | 
			
		||||
            valid=True,
 | 
			
		||||
@@ -363,3 +368,44 @@ class TestTransactions(TestCase):
 | 
			
		||||
        self.assertTrue(Alias.objects.filter(name="test_updated_alias").exists())
 | 
			
		||||
        response = self.client.delete("/api/note/alias/" + str(alias.pk) + "/")
 | 
			
		||||
        self.assertEqual(response.status_code, 204)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestNoteAPI(TestAPI):
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
 | 
			
		||||
        membership = Membership.objects.create(club=Club.objects.get(name="BDE"), user=self.user)
 | 
			
		||||
        membership.roles.add(Role.objects.get(name="Respo info"))
 | 
			
		||||
        membership.save()
 | 
			
		||||
        Membership.objects.create(club=Club.objects.get(name="Kfet"), user=self.user)
 | 
			
		||||
        self.user.note.last_negative = timezone.now()
 | 
			
		||||
        self.user.note.save()
 | 
			
		||||
 | 
			
		||||
        self.transaction = Transaction.objects.create(
 | 
			
		||||
            source=Note.objects.first(),
 | 
			
		||||
            destination=self.user.note,
 | 
			
		||||
            amount=4200,
 | 
			
		||||
            reason="Test transaction",
 | 
			
		||||
        )
 | 
			
		||||
        self.user.note.refresh_from_db()
 | 
			
		||||
        Alias.objects.create(note=self.user.note, name="I am a ¢omplex alias")
 | 
			
		||||
 | 
			
		||||
        self.category = TemplateCategory.objects.create(name="Test")
 | 
			
		||||
        self.template = TransactionTemplate.objects.create(
 | 
			
		||||
            name="Test",
 | 
			
		||||
            destination=Club.objects.get(name="BDE").note,
 | 
			
		||||
            category=self.category,
 | 
			
		||||
            amount=100,
 | 
			
		||||
            description="Test template",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_note_api(self):
 | 
			
		||||
        """
 | 
			
		||||
        Load API pages for the note app and test all filters
 | 
			
		||||
        """
 | 
			
		||||
        self.check_viewset(AliasViewSet, "/api/note/alias/")
 | 
			
		||||
        self.check_viewset(ConsumerViewSet, "/api/note/consumer/")
 | 
			
		||||
        self.check_viewset(NotePolymorphicViewSet, "/api/note/note/")
 | 
			
		||||
        self.check_viewset(TemplateCategoryViewSet, "/api/note/transaction/category/")
 | 
			
		||||
        self.check_viewset(TransactionTemplateViewSet, "/api/note/transaction/template/")
 | 
			
		||||
        self.check_viewset(TransactionViewSet, "/api/note/transaction/transaction/")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,10 @@
 | 
			
		||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from api.viewsets import ReadOnlyProtectedModelViewSet
 | 
			
		||||
from django_filters.rest_framework import DjangoFilterBackend
 | 
			
		||||
from rest_framework.filters import SearchFilter
 | 
			
		||||
 | 
			
		||||
from api.viewsets import ReadOnlyProtectedModelViewSet
 | 
			
		||||
 | 
			
		||||
from .serializers import PermissionSerializer, RoleSerializer
 | 
			
		||||
from ..models import Permission, Role
 | 
			
		||||
 | 
			
		||||
@@ -16,7 +15,7 @@ class PermissionViewSet(ReadOnlyProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Permission` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/permission/permission/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Permission.objects.all()
 | 
			
		||||
    queryset = Permission.objects.order_by('id')
 | 
			
		||||
    serializer_class = PermissionSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['model', 'type', 'query', 'mask', 'field', 'permanent', ]
 | 
			
		||||
@@ -29,8 +28,8 @@ class RoleViewSet(ReadOnlyProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `RolePermission` objects, serialize it to JSON with the given serializer
 | 
			
		||||
    then render it on /api/permission/roles/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Role.objects.all()
 | 
			
		||||
    queryset = Role.objects.order_by('id')
 | 
			
		||||
    serializer_class = RoleSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['name', 'permissions', 'for_club', 'membership_set__user', ]
 | 
			
		||||
    filterset_fields = ['name', 'permissions', 'for_club', 'memberships__user', ]
 | 
			
		||||
    SearchFilter = ['$name', '$for_club__name', ]
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ class InvoiceViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Invoice` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/treasury/invoice/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Invoice.objects.order_by("id").all()
 | 
			
		||||
    queryset = Invoice.objects.order_by('id')
 | 
			
		||||
    serializer_class = InvoiceSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['bde', 'object', 'description', 'name', 'address', 'date', 'acquitted', 'locked', ]
 | 
			
		||||
@@ -29,7 +29,7 @@ class ProductViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Product` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/treasury/product/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Product.objects.order_by("invoice_id", "id").all()
 | 
			
		||||
    queryset = Product.objects.order_by('invoice_id', 'id')
 | 
			
		||||
    serializer_class = ProductSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['invoice', 'designation', 'quantity', 'amount', ]
 | 
			
		||||
@@ -42,7 +42,7 @@ class RemittanceTypeViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `RemittanceType` objects, serialize it to JSON with the given serializer
 | 
			
		||||
    then render it on /api/treasury/remittance_type/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = RemittanceType.objects.order_by("id")
 | 
			
		||||
    queryset = RemittanceType.objects.order_by('id')
 | 
			
		||||
    serializer_class = RemittanceTypeSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['note', ]
 | 
			
		||||
@@ -55,10 +55,10 @@ class RemittanceViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Remittance` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/treasury/remittance/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Remittance.objects.order_by("id")
 | 
			
		||||
    queryset = Remittance.objects.order_by('id')
 | 
			
		||||
    serializer_class = RemittanceSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['date', 'remittance_type', 'comment', 'closed', 'specialtransactionproxy__transaction', ]
 | 
			
		||||
    filterset_fields = ['date', 'remittance_type', 'comment', 'closed', 'transaction_proxies__transaction', ]
 | 
			
		||||
    search_fields = ['$remittance_type__note__special_type', '$comment', ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -68,7 +68,7 @@ class SogeCreditViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `SogeCredit` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/treasury/soge_credit/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = SogeCredit.objects.order_by("id")
 | 
			
		||||
    queryset = SogeCredit.objects.order_by('id')
 | 
			
		||||
    serializer_class = SogeCreditSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['user', 'user__last_name', 'user__first_name', 'user__email', 'user__note__alias__name',
 | 
			
		||||
 
 | 
			
		||||
@@ -257,6 +257,7 @@ class SpecialTransactionProxy(models.Model):
 | 
			
		||||
        Remittance,
 | 
			
		||||
        on_delete=models.PROTECT,
 | 
			
		||||
        null=True,
 | 
			
		||||
        related_name="transaction_proxies",
 | 
			
		||||
        verbose_name=_("Remittance"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from api.tests import TestAPI
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
@@ -8,7 +9,10 @@ from django.test import TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from member.models import Membership, Club
 | 
			
		||||
from note.models import SpecialTransaction, NoteSpecial, Transaction
 | 
			
		||||
from treasury.models import Invoice, Product, Remittance, RemittanceType, SogeCredit
 | 
			
		||||
 | 
			
		||||
from ..api.views import InvoiceViewSet, ProductViewSet, RemittanceViewSet, RemittanceTypeViewSet, \
 | 
			
		||||
    SogeCreditViewSet
 | 
			
		||||
from ..models import Invoice, Product, Remittance, RemittanceType, SogeCredit
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestInvoices(TestCase):
 | 
			
		||||
@@ -399,3 +403,62 @@ class TestSogeCredits(TestCase):
 | 
			
		||||
        """
 | 
			
		||||
        response = self.client.get("/api/treasury/soge_credit/")
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestTreasuryAPI(TestAPI):
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
 | 
			
		||||
        self.invoice = Invoice.objects.create(
 | 
			
		||||
            id=1,
 | 
			
		||||
            object="Object",
 | 
			
		||||
            description="Description",
 | 
			
		||||
            name="Me",
 | 
			
		||||
            address="Earth",
 | 
			
		||||
            acquitted=False,
 | 
			
		||||
        )
 | 
			
		||||
        self.product = Product.objects.create(
 | 
			
		||||
            invoice=self.invoice,
 | 
			
		||||
            designation="Product",
 | 
			
		||||
            quantity=3,
 | 
			
		||||
            amount=3.14,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.credit = SpecialTransaction.objects.create(
 | 
			
		||||
            source=NoteSpecial.objects.get(special_type="Chèque"),
 | 
			
		||||
            destination=self.user.note,
 | 
			
		||||
            amount=4200,
 | 
			
		||||
            reason="Credit",
 | 
			
		||||
            last_name="TOTO",
 | 
			
		||||
            first_name="Toto",
 | 
			
		||||
            bank="Société générale",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.remittance = Remittance.objects.create(
 | 
			
		||||
            remittance_type=RemittanceType.objects.get(),
 | 
			
		||||
            comment="Test remittance",
 | 
			
		||||
            closed=False,
 | 
			
		||||
        )
 | 
			
		||||
        self.credit.specialtransactionproxy.remittance = self.remittance
 | 
			
		||||
        self.credit.specialtransactionproxy.save()
 | 
			
		||||
 | 
			
		||||
        self.kfet = Club.objects.get(name="Kfet")
 | 
			
		||||
        self.bde = self.kfet.parent_club
 | 
			
		||||
 | 
			
		||||
        self.kfet_membership = Membership(
 | 
			
		||||
            user=self.user,
 | 
			
		||||
            club=self.kfet,
 | 
			
		||||
        )
 | 
			
		||||
        self.kfet_membership._force_renew_parent = True
 | 
			
		||||
        self.kfet_membership._soge = True
 | 
			
		||||
        self.kfet_membership.save()
 | 
			
		||||
 | 
			
		||||
    def test_treasury_api(self):
 | 
			
		||||
        """
 | 
			
		||||
        Load API pages for the treasury app and test all filters
 | 
			
		||||
        """
 | 
			
		||||
        self.check_viewset(InvoiceViewSet, "/api/treasury/invoice/")
 | 
			
		||||
        self.check_viewset(ProductViewSet, "/api/treasury/product/")
 | 
			
		||||
        self.check_viewset(RemittanceViewSet, "/api/treasury/remittance/")
 | 
			
		||||
        self.check_viewset(RemittanceTypeViewSet, "/api/treasury/remittance_type/")
 | 
			
		||||
        self.check_viewset(SogeCreditViewSet, "/api/treasury/soge_credit/")
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ class WEIClubViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `WEIClub` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/wei/club/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = WEIClub.objects.all()
 | 
			
		||||
    queryset = WEIClub.objects.order_by('id')
 | 
			
		||||
    serializer_class = WEIClubSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['name', 'year', 'date_start', 'date_end', 'email', 'note__alias__name',
 | 
			
		||||
@@ -32,7 +32,7 @@ class BusViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `Bus` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/wei/bus/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Bus.objects
 | 
			
		||||
    queryset = Bus.objects.order_by('id')
 | 
			
		||||
    serializer_class = BusSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['name', 'wei', 'description', ]
 | 
			
		||||
@@ -45,7 +45,7 @@ class BusTeamViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/wei/team/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = BusTeam.objects
 | 
			
		||||
    queryset = BusTeam.objects.order_by('id')
 | 
			
		||||
    serializer_class = BusTeamSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['name', 'bus', 'color', 'description', 'bus__wei', ]
 | 
			
		||||
@@ -58,11 +58,11 @@ class WEIRoleViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `WEIRole` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/wei/role/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = WEIRole.objects
 | 
			
		||||
    queryset = WEIRole.objects.order_by('id')
 | 
			
		||||
    serializer_class = WEIRoleSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['name', 'permissions', 'for_club', 'membership_set__user', ]
 | 
			
		||||
    SearchFilter = ['$name', '$for_club__name', ]
 | 
			
		||||
    filterset_fields = ['name', 'permissions', 'memberships', ]
 | 
			
		||||
    search_fields = ['$name', ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WEIRegistrationViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
@@ -71,18 +71,17 @@ class WEIRegistrationViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all WEIRegistration objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/wei/registration/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = WEIRegistration.objects
 | 
			
		||||
    queryset = WEIRegistration.objects.order_by('id')
 | 
			
		||||
    serializer_class = WEIRegistrationSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, SearchFilter]
 | 
			
		||||
    filterset_fields = ['user', 'user__username', 'user__first_name', 'user__last_name', 'user__email',
 | 
			
		||||
                        'user__note__alias__name', 'user__note__alias__normalized_name', 'wei', 'wei__name',
 | 
			
		||||
                        'wei__email', 'wei__note__alias__name', 'wei__note__alias__normalized_name', 'wei__year',
 | 
			
		||||
                        'soge_credit', 'caution_check', 'birth_date', 'gender', 'clothing_cut', 'clothing_size',
 | 
			
		||||
                        'first_year', 'emergency_contact_name', 'emergency_contact_phone', ]
 | 
			
		||||
                        'wei__email', 'wei__year', 'soge_credit', 'caution_check', 'birth_date', 'gender',
 | 
			
		||||
                        'clothing_cut', 'clothing_size', 'first_year', 'emergency_contact_name',
 | 
			
		||||
                        'emergency_contact_phone', ]
 | 
			
		||||
    search_fields = ['$user__username', '$user__first_name', '$user__last_name', '$user__email',
 | 
			
		||||
                     '$user__note__alias__name', '$user__note__alias__normalized_name', '$wei__name',
 | 
			
		||||
                     '$wei__email', '$wei__note__alias__name', '$wei__note__alias__normalized_name',
 | 
			
		||||
                     '$health_issues', '$emergency_contact_name', '$emergency_contact_phone', ]
 | 
			
		||||
                     '$wei__email', '$health_issues', '$emergency_contact_name', '$emergency_contact_phone', ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class WEIMembershipViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
@@ -91,15 +90,16 @@ class WEIMembershipViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    The djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,
 | 
			
		||||
    then render it on /api/wei/membership/
 | 
			
		||||
    """
 | 
			
		||||
    queryset = WEIMembership.objects
 | 
			
		||||
    queryset = WEIMembership.objects.order_by('id')
 | 
			
		||||
    serializer_class = WEIMembershipSerializer
 | 
			
		||||
    filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
 | 
			
		||||
    filterset_fields = ['club__name', 'club__email', 'club__note__alias__name', 'club__note__alias__normalized_name',
 | 
			
		||||
                        'user__username', 'user__last_name', 'user__first_name', 'user__email',
 | 
			
		||||
                        'user__note__alias__name', 'user__note__alias__normalized_name', 'date_start', 'date_end',
 | 
			
		||||
                        'fee', 'roles', 'bus', 'bus__name', 'team', 'team__name', 'registration', ]
 | 
			
		||||
    filterset_fields = ['club__name', 'club__email', 'club__note__alias__name',
 | 
			
		||||
                        'club__note__alias__normalized_name', 'user__username', 'user__last_name',
 | 
			
		||||
                        'user__first_name', 'user__email', 'user__note__alias__name',
 | 
			
		||||
                        'user__note__alias__normalized_name', 'date_start', 'date_end', 'fee', 'roles', 'bus',
 | 
			
		||||
                        'bus__name', 'team', 'team__name', 'registration', ]
 | 
			
		||||
    ordering_fields = ['id', 'date_start', 'date_end', ]
 | 
			
		||||
    search_fields = ['$club__name', '$club__email', '$club__note__alias__name', '$club__note__alias__normalized_name',
 | 
			
		||||
                     '$user__username', '$user__last_name', '$user__first_name', '$user__email',
 | 
			
		||||
                     '$user__note__alias__name', '$user__note__alias__normalized_name', '$roles__name',
 | 
			
		||||
                     '$bus__name', '$team__name', ]
 | 
			
		||||
    search_fields = ['$club__name', '$club__email', '$club__note__alias__name',
 | 
			
		||||
                     '$club__note__alias__normalized_name', '$user__username', '$user__last_name',
 | 
			
		||||
                     '$user__first_name', '$user__email', '$user__note__alias__name',
 | 
			
		||||
                     '$user__note__alias__normalized_name', '$roles__name', '$bus__name', '$team__name', ]
 | 
			
		||||
 
 | 
			
		||||
@@ -61,10 +61,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
                    <dd class="col-xl-6">{{ club.note.balance | pretty_money }}</dd>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
                    {% if "note.change_alias"|has_perm:club.note.alias_set.first %}
 | 
			
		||||
                    {% if "note.change_alias"|has_perm:club.note.alias.first %}
 | 
			
		||||
                    <dt class="col-xl-4"><a
 | 
			
		||||
                            href="{% url 'member:club_alias' club.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
 | 
			
		||||
                    <dd class="col-xl-8 text-truncate">{{ club.note.alias_set.all|join:", " }}</dd>
 | 
			
		||||
                    <dd class="col-xl-8 text-truncate">{{ club.note.alias.all|join:", " }}</dd>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
                    <dt class="col-xl-4">{% trans 'email'|capfirst %}</dt>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,16 +4,19 @@
 | 
			
		||||
import subprocess
 | 
			
		||||
from datetime import timedelta, date
 | 
			
		||||
 | 
			
		||||
from api.tests import TestAPI
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.db.models import Q
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from member.models import Membership
 | 
			
		||||
from member.models import Membership, Club
 | 
			
		||||
from note.models import NoteClub, SpecialTransaction
 | 
			
		||||
from treasury.models import SogeCredit
 | 
			
		||||
 | 
			
		||||
from ..api.views import BusViewSet, BusTeamViewSet, WEIClubViewSet, WEIMembershipViewSet, WEIRegistrationViewSet, \
 | 
			
		||||
    WEIRoleViewSet
 | 
			
		||||
from ..forms import CurrentSurvey, WEISurveyAlgorithm, WEISurvey
 | 
			
		||||
from ..models import WEIClub, Bus, BusTeam, WEIRole, WEIRegistration, WEIMembership
 | 
			
		||||
 | 
			
		||||
@@ -807,3 +810,72 @@ class TestWEISurveyAlgorithm(TestCase):
 | 
			
		||||
 | 
			
		||||
    def test_survey_algorithm(self):
 | 
			
		||||
        CurrentSurvey.get_algorithm_class()().run_algorithm()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestWeiAPI(TestAPI):
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        super().setUp()
 | 
			
		||||
 | 
			
		||||
        self.year = timezone.now().year
 | 
			
		||||
        self.wei = WEIClub.objects.create(
 | 
			
		||||
            name="Test WEI",
 | 
			
		||||
            email="gc.wei@example.com",
 | 
			
		||||
            parent_club_id=2,
 | 
			
		||||
            membership_fee_paid=12500,
 | 
			
		||||
            membership_fee_unpaid=5500,
 | 
			
		||||
            membership_start=date(self.year, 1, 1),
 | 
			
		||||
            membership_end=date(self.year, 12, 31),
 | 
			
		||||
            membership_duration=396,
 | 
			
		||||
            year=self.year,
 | 
			
		||||
            date_start=date.today() + timedelta(days=2),
 | 
			
		||||
            date_end=date(self.year, 12, 31),
 | 
			
		||||
        )
 | 
			
		||||
        NoteClub.objects.create(club=self.wei)
 | 
			
		||||
        self.bus = Bus.objects.create(
 | 
			
		||||
            name="Test Bus",
 | 
			
		||||
            wei=self.wei,
 | 
			
		||||
            description="Test Bus",
 | 
			
		||||
        )
 | 
			
		||||
        self.team = BusTeam.objects.create(
 | 
			
		||||
            name="Test Team",
 | 
			
		||||
            bus=self.bus,
 | 
			
		||||
            color=0xFFFFFF,
 | 
			
		||||
            description="Test Team",
 | 
			
		||||
        )
 | 
			
		||||
        self.registration = WEIRegistration.objects.create(
 | 
			
		||||
            user_id=self.user.id,
 | 
			
		||||
            wei_id=self.wei.id,
 | 
			
		||||
            soge_credit=True,
 | 
			
		||||
            caution_check=True,
 | 
			
		||||
            birth_date=date(2000, 1, 1),
 | 
			
		||||
            gender="nonbinary",
 | 
			
		||||
            clothing_cut="male",
 | 
			
		||||
            clothing_size="XL",
 | 
			
		||||
            health_issues="I am a bot",
 | 
			
		||||
            emergency_contact_name="Pikachu",
 | 
			
		||||
            emergency_contact_phone="+33123456789",
 | 
			
		||||
            first_year=False,
 | 
			
		||||
        )
 | 
			
		||||
        Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE"))
 | 
			
		||||
        Membership.objects.create(user=self.user, club=Club.objects.get(name="Kfet"))
 | 
			
		||||
        self.membership = WEIMembership.objects.create(
 | 
			
		||||
            user=self.user,
 | 
			
		||||
            club=self.wei,
 | 
			
		||||
            fee=125,
 | 
			
		||||
            bus=self.bus,
 | 
			
		||||
            team=self.team,
 | 
			
		||||
            registration=self.registration,
 | 
			
		||||
        )
 | 
			
		||||
        self.membership.roles.add(WEIRole.objects.last())
 | 
			
		||||
        self.membership.save()
 | 
			
		||||
 | 
			
		||||
    def test_wei_api(self):
 | 
			
		||||
        """
 | 
			
		||||
        Load API pages for the treasury app and test all filters
 | 
			
		||||
        """
 | 
			
		||||
        self.check_viewset(WEIClubViewSet, "/api/wei/club/")
 | 
			
		||||
        self.check_viewset(BusViewSet, "/api/wei/bus/")
 | 
			
		||||
        self.check_viewset(BusTeamViewSet, "/api/wei/team/")
 | 
			
		||||
        self.check_viewset(WEIRoleViewSet, "/api/wei/role/")
 | 
			
		||||
        self.check_viewset(WEIRegistrationViewSet, "/api/wei/registration/")
 | 
			
		||||
        self.check_viewset(WEIMembershipViewSet, "/api/wei/membership/")
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user