Commit 3a615383 authored by Florent Chehab's avatar Florent Chehab
Browse files

feat(standard filtering, tweaks): REST Api and other

* Filtering on client request should now be performed with the standard `?attr=...` syntaxe
* Frontend updated for this new syntaxe
* Backend and frontend documentation updated with new changes
* Updated the location of the the api documentation to `/api-doc`
* Fixed bug preventing api-doc to render
* backend python requirements updated
* Updated dockerfile / docker-compose to make sure we wait for the db

Fixes #97 #80
parent 243f43bf
...@@ -21,3 +21,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ...@@ -21,3 +21,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN pip install --upgrade pip RUN pip install --upgrade pip
COPY requirements.txt /usr/src/app/requirements.txt COPY requirements.txt /usr/src/app/requirements.txt
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
## Add the wait script to the image to wait for the database to be up for sure
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait
...@@ -359,6 +359,12 @@ class EssentialModuleViewSet(BaseModelViewSet): ...@@ -359,6 +359,12 @@ class EssentialModuleViewSet(BaseModelViewSet):
This allows to not do this query for all elements and improves This allows to not do this query for all elements and improves
performances. You can look at the comment below for more information. performances. You can look at the comment below for more information.
""" """
# When generating the API documentation (url: /api-doc) the request would be None
# and we don't need to do anything special
if self.request is None:
return super().get_serializer_context()
fake_edit_request = FakeRequest(self.request.user, "PUT") fake_edit_request = FakeRequest(self.request.user, "PUT")
user_can_edit = True user_can_edit = True
......
...@@ -26,8 +26,5 @@ class CampusTaggedItemSerializer(TaggedItemSerializer): ...@@ -26,8 +26,5 @@ class CampusTaggedItemSerializer(TaggedItemSerializer):
class CampusTaggedItemViewSet(TaggedItemViewSet): class CampusTaggedItemViewSet(TaggedItemViewSet):
queryset = CampusTaggedItem.objects.all() # pylint: disable=E1101 queryset = CampusTaggedItem.objects.all() # pylint: disable=E1101
serializer_class = CampusTaggedItemSerializer serializer_class = CampusTaggedItemSerializer
end_point_route = r"campusTaggedItems/(?P<campus_id>[0-9]+)" end_point_route = "campusTaggedItems"
filterset_fields = ("campus",)
def get_queryset(self):
campus_id = self.kwargs["campus_id"]
return super().get_queryset().filter(campus=campus_id).distinct()
...@@ -26,8 +26,5 @@ class CityTaggedItemSerializer(TaggedItemSerializer): ...@@ -26,8 +26,5 @@ class CityTaggedItemSerializer(TaggedItemSerializer):
class CityTaggedItemViewSet(TaggedItemViewSet): class CityTaggedItemViewSet(TaggedItemViewSet):
queryset = CityTaggedItem.objects.all() # pylint: disable=E1101 queryset = CityTaggedItem.objects.all() # pylint: disable=E1101
serializer_class = CityTaggedItemSerializer serializer_class = CityTaggedItemSerializer
end_point_route = r"cityTaggedItems/(?P<city_id>[0-9]+)" end_point_route = "cityTaggedItems"
filterset_fields = ("city",)
def get_queryset(self):
city_id = self.kwargs["city_id"]
return super().get_queryset().filter(city=city_id).distinct()
...@@ -19,8 +19,5 @@ class CountryDriViewSet(ModuleViewSet): ...@@ -19,8 +19,5 @@ class CountryDriViewSet(ModuleViewSet):
queryset = CountryDri.objects.all() # pylint: disable=E1101 queryset = CountryDri.objects.all() # pylint: disable=E1101
serializer_class = CountryDriSerializer serializer_class = CountryDriSerializer
permission_classes = (IsStaff | IsDri | NoPost,) permission_classes = (IsStaff | IsDri | NoPost,)
end_point_route = r"countryDri/(?P<country_id>[a-zA-Z]+)" end_point_route = "countryDri"
filterset_fields = ("countries",)
def get_queryset(self):
country_id = self.kwargs["country_id"]
return super().get_queryset().filter(countries__pk=country_id).distinct()
...@@ -21,8 +21,5 @@ class CountryScholarshipSerializer(ScholarshipSerializer): ...@@ -21,8 +21,5 @@ class CountryScholarshipSerializer(ScholarshipSerializer):
class CountryScholarshipViewSet(ScholarshipViewSet): class CountryScholarshipViewSet(ScholarshipViewSet):
queryset = CountryScholarship.objects.all() # pylint: disable=E1101 queryset = CountryScholarship.objects.all() # pylint: disable=E1101
serializer_class = CountryScholarshipSerializer serializer_class = CountryScholarshipSerializer
end_point_route = r"countryScholarships/(?P<country_id>[a-zA-Z]+)" end_point_route = "countryScholarships"
filterset_fields = ("countries",)
def get_queryset(self):
country_id = self.kwargs["country_id"]
return super().get_queryset().filter(countries__pk=country_id).distinct()
...@@ -26,8 +26,5 @@ class CountryTaggedItemSerializer(TaggedItemSerializer): ...@@ -26,8 +26,5 @@ class CountryTaggedItemSerializer(TaggedItemSerializer):
class CountryTaggedItemViewSet(TaggedItemViewSet): class CountryTaggedItemViewSet(TaggedItemViewSet):
queryset = CountryTaggedItem.objects.all() # pylint: disable=E1101 queryset = CountryTaggedItem.objects.all() # pylint: disable=E1101
serializer_class = CountryTaggedItemSerializer serializer_class = CountryTaggedItemSerializer
end_point_route = r"countryTaggedItems/(?P<country_id>[a-zA-Z]+)" end_point_route = "countryTaggedItems"
filterset_fields = ("country",)
def get_queryset(self):
country_id = self.kwargs["country_id"]
return super().get_queryset().filter(country=country_id).distinct()
...@@ -8,7 +8,6 @@ from backend_app.models.abstract.essentialModule import ( ...@@ -8,7 +8,6 @@ from backend_app.models.abstract.essentialModule import (
EssentialModuleSerializer, EssentialModuleSerializer,
EssentialModuleViewSet, EssentialModuleViewSet,
) )
from backend_app.permissions.app_permissions import ReadOnly
from backend_app.validators.tags import validate_extension from backend_app.validators.tags import validate_extension
...@@ -42,5 +41,4 @@ class UniversitySerializer(EssentialModuleSerializer): ...@@ -42,5 +41,4 @@ class UniversitySerializer(EssentialModuleSerializer):
class UniversityViewSet(EssentialModuleViewSet): class UniversityViewSet(EssentialModuleViewSet):
serializer_class = UniversitySerializer serializer_class = UniversitySerializer
queryset = University.objects.all() # pylint: disable=E1101 queryset = University.objects.all() # pylint: disable=E1101
permission_classes = (ReadOnly,)
end_point_route = "universities" end_point_route = "universities"
...@@ -19,8 +19,5 @@ class UniversityDriViewSet(ModuleViewSet): ...@@ -19,8 +19,5 @@ class UniversityDriViewSet(ModuleViewSet):
queryset = UniversityDri.objects.all() # pylint: disable=E1101 queryset = UniversityDri.objects.all() # pylint: disable=E1101
serializer_class = UniversityDriSerializer serializer_class = UniversityDriSerializer
permission_classes = (IsStaff | IsDri | ReadOnly,) permission_classes = (IsStaff | IsDri | ReadOnly,)
end_point_route = r"universityDri/(?P<univ_id>[0-9]+)" end_point_route = "universityDri"
filterset_fields = ("universities",)
def get_queryset(self):
univ_id = self.kwargs["univ_id"]
return super().get_queryset().filter(universities__pk=univ_id).distinct()
...@@ -23,8 +23,5 @@ class UniversityScholarshipSerializer(ScholarshipSerializer): ...@@ -23,8 +23,5 @@ class UniversityScholarshipSerializer(ScholarshipSerializer):
class UniversityScholarshipViewSet(ScholarshipViewSet): class UniversityScholarshipViewSet(ScholarshipViewSet):
queryset = UniversityScholarship.objects.all() # pylint: disable=E1101 queryset = UniversityScholarship.objects.all() # pylint: disable=E1101
serializer_class = UniversityScholarshipSerializer serializer_class = UniversityScholarshipSerializer
end_point_route = r"universityScholarships/(?P<univ_id>[0-9]+)" end_point_route = "universityScholarships"
filterset_fields = ("universities",)
def get_queryset(self):
univ_id = self.kwargs["univ_id"]
return super().get_queryset().filter(universities__pk=univ_id).distinct()
...@@ -28,8 +28,5 @@ class UniversityTaggedItemViewSet(TaggedItemViewSet): ...@@ -28,8 +28,5 @@ class UniversityTaggedItemViewSet(TaggedItemViewSet):
queryset = UniversityTaggedItem.objects.all() # pylint: disable=E1101 queryset = UniversityTaggedItem.objects.all() # pylint: disable=E1101
serializer_class = UniversityTaggedItemSerializer serializer_class = UniversityTaggedItemSerializer
permission_classes = (IsOwner,) permission_classes = (IsOwner,)
end_point_route = r"universityTaggedItems/(?P<univ_id>[0-9]+)" end_point_route = "universityTaggedItems"
filterset_fields = ("university",)
def get_queryset(self):
univ_id = self.kwargs["univ_id"]
return super().get_queryset().filter(university__pk=univ_id).distinct()
...@@ -56,7 +56,6 @@ class VersionSerializer(BaseModelSerializer): ...@@ -56,7 +56,6 @@ class VersionSerializer(BaseModelSerializer):
djangoSerializers.deserialize(obj.format, data, ignorenonexistent=True) djangoSerializers.deserialize(obj.format, data, ignorenonexistent=True)
)[0] )[0]
# Version is valid, # Version is valid,
print(self.get_serializers_mapping())
obj_serializer = self.get_serializers_mapping()[type(tmp.object).__name__] obj_serializer = self.get_serializers_mapping()[type(tmp.object).__name__]
new_context = dict(self.context) new_context = dict(self.context)
new_context["view"].action = "list" new_context["view"].action = "list"
......
...@@ -210,5 +210,5 @@ class ModerationTestCase(WithUserTestCase): ...@@ -210,5 +210,5 @@ class ModerationTestCase(WithUserTestCase):
c.save() c.save()
data = {"comment": "", "useful_links": [], "universities": [c.pk]} data = {"comment": "", "useful_links": [], "universities": [c.pk]}
api_end_point = "/api/universityDri/{}/".format(c.pk) api_end_point = "/api/universityDri/?universities={}/".format(c.pk)
self._submit_post_test(self.dri_client, data, api_end_point) self._submit_post_test(self.dri_client, data, api_end_point)
...@@ -10,7 +10,7 @@ class WriteAccessTestCase(WithUserTestCase): ...@@ -10,7 +10,7 @@ class WriteAccessTestCase(WithUserTestCase):
@classmethod @classmethod
def setUpMoreTestData(cls): def setUpMoreTestData(cls):
cls.univ = University.objects.create(name="Univ de test", utc_id=100) cls.univ = University.objects.create(name="Univ de test", utc_id=100)
cls.api_dri = "/api/universityDri/{}/".format(cls.univ.pk) cls.api_dri = "/api/universityDri/?universities={}/".format(cls.univ.pk)
cls.post_data = dict( cls.post_data = dict(
universities=[cls.univ.pk], title="qsdlkjqsmlkdj", useful_links="[]" universities=[cls.univ.pk], title="qsdlkjqsmlkdj", useful_links="[]"
) )
......
...@@ -8,7 +8,7 @@ from backend_app.viewsets import ALL_API_VIEWSETS, ALL_API_VIEW_VIEWSETS ...@@ -8,7 +8,7 @@ from backend_app.viewsets import ALL_API_VIEWSETS, ALL_API_VIEW_VIEWSETS
# Building the API routing # Building the API routing
####### #######
urlpatterns = [url(r"^api-docs/", include_docs_urls(title="Outgoing API"))] urlpatterns = [url(r"^api-doc/", include_docs_urls(title="REX-DRI API"))]
# router will hold all api related endpoints # router will hold all api related endpoints
router = routers.DefaultRouter() router = routers.DefaultRouter()
......
...@@ -67,6 +67,7 @@ INSTALLED_APPS = [ ...@@ -67,6 +67,7 @@ INSTALLED_APPS = [
"backend_app", "backend_app",
"base_app", "base_app",
"webpack_loader", "webpack_loader",
"django_filters",
] ]
# #
...@@ -87,6 +88,7 @@ REST_FRAMEWORK = { ...@@ -87,6 +88,7 @@ REST_FRAMEWORK = {
"rest_framework.authentication.SessionAuthentication", "rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication", "rest_framework.authentication.TokenAuthentication",
), ),
"DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
} }
# #
......
...@@ -9,3 +9,5 @@ while [ ! -f `dirname $0`/../frontend/webpack-stats.json ]; do ...@@ -9,3 +9,5 @@ while [ ! -f `dirname $0`/../frontend/webpack-stats.json ]; do
done done
echo "Frontend staticfiles are ready (NB: not necessarly up-to-date)." echo "Frontend staticfiles are ready (NB: not necessarly up-to-date)."
./manage.py collectstatic --noinput && ./manage.py runserver 0.0.0.0:8000
...@@ -3,6 +3,7 @@ Django==2.1.7 ...@@ -3,6 +3,7 @@ Django==2.1.7
psycopg2-binary==2.7.7 psycopg2-binary==2.7.7
django-cas-ng==3.6.0 django-cas-ng==3.6.0
djangorestframework==3.9.1 djangorestframework==3.9.1
django-filter==2.1.0 # easy filtering on API
coreapi==2.3.3 # Automatic API doc generation coreapi==2.3.3 # Automatic API doc generation
django-reversion==3.0.3 django-reversion==3.0.3
django-reversion-compare==0.8.6 django-reversion-compare==0.8.6
......
...@@ -16,6 +16,7 @@ services: ...@@ -16,6 +16,7 @@ services:
image: registry.gitlab.utc.fr/rex-dri/rex-dri/backend:latest image: registry.gitlab.utc.fr/rex-dri/rex-dri/backend:latest
# To use a locally build one, comment above, uncomment bellow. # To use a locally build one, comment above, uncomment bellow.
# build: ./backend # build: ./backend
restart: on-failure
volumes: volumes:
# "Copy" the repo to the workdir. # "Copy" the repo to the workdir.
- .:/usr/src/app/ - .:/usr/src/app/
...@@ -35,10 +36,12 @@ services: ...@@ -35,10 +36,12 @@ services:
- POSTGRES_DB=postgres - POSTGRES_DB=postgres
- POSTGRES_USER=postgres - POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres - POSTGRES_PASSWORD=postgres
# Specify host to wait for; ie we want to wait for the db for sure
- WAIT_HOSTS=database:5432
# Run the django developpement server on image startup. # Run the django developpement server on image startup.
command: /bin/sh -c "cd backend && ./waitForFrontend.sh && ./manage.py collectstatic --noinput && ./manage.py runserver 0.0.0.0:8000" command: /bin/sh -c "/wait && cd backend && ./entry.sh"
depends_on: depends_on:
# Required that the `database` and `frontend` service is up and running. # Required that the `database` and `frontend` service are up and running.
- database - database
- frontend - frontend
......
...@@ -5,7 +5,7 @@ API ...@@ -5,7 +5,7 @@ API
The backend `Django` app, with `Django Rest Framework` acts as an API. It is available at the URI `/api`. The backend `Django` app, with `Django Rest Framework` acts as an API. It is available at the URI `/api`.
An automated documentation is generated and available at the URI `/api-docs`. An automated documentation is generated and available at the URI `/api-doc`.
## Authentication ## Authentication
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment