Commit 98621b4643d32eb06541e22b72f79fb0d8f5e8c8
1 parent
42cd9e29
Exists in
dev
add DRF, pyros api views, pyros_api python script, fixing period assignation whe…
…n submitting a sequence
Showing
17 changed files
with
236 additions
and
6 deletions
Show diff stats
CHANGELOG
1 | +08-02-2022 (AKo) : v0.3.6.0 | |
2 | + - Add DRF (Django Rest Framework) to requirements | |
3 | + - Add Pyros API (User and sequences with sequence submission) | |
4 | + - Add PyrosAPI script to use the API via Python command line | |
5 | + - Fixed error when submitting the sequence : the period wasn't associated | |
6 | + | |
1 | 7 | 07-02-2022 (AKo) : v0.3.5.0 |
2 | 8 | - Adding INVENTORY section to observatory configuration |
3 | 9 | - Logout now redirect to home page | ... | ... |
VERSION
install/requirements.txt
... | ... | @@ -0,0 +1,86 @@ |
1 | +import requests, sys, yaml | |
2 | + | |
3 | +class PyrosAPI: | |
4 | + """ | |
5 | + Request Pyros API with an PyrosUser account linked to the username and password | |
6 | + """ | |
7 | + BASE_PYROS_URL = "http://localhost:8000/api/" | |
8 | + def __init__(self, username : str, password : str ) -> None: | |
9 | + """ | |
10 | + Initialize PyrosAPI class by getting the authentification token required to use the API | |
11 | + | |
12 | + Args: | |
13 | + username : Username of the user (mail adress) | |
14 | + password : Password of the user | |
15 | + """ | |
16 | + self.get_token(username, password) | |
17 | + | |
18 | + | |
19 | + def get_token(self,username : str,password : str): | |
20 | + """ | |
21 | + get the authentification token linked to the user account | |
22 | + | |
23 | + Args: | |
24 | + username : Username of the user (mail adress) | |
25 | + password : Password of the user | |
26 | + """ | |
27 | + url = f"{self.BASE_PYROS_URL}api-token-auth/" | |
28 | + request = requests.post(url, data={"username":username,"password":password}) | |
29 | + json_response = request.json() | |
30 | + self.token = json_response["token"] | |
31 | + print(f"Token is {self.token}") | |
32 | + | |
33 | + def get_url(self,url : str) -> dict: | |
34 | + """ | |
35 | + Query the url and return the response as json | |
36 | + | |
37 | + Args: | |
38 | + url : Url to be requested without the base url of the website | |
39 | + | |
40 | + Returns: | |
41 | + dict : Json object that represents the response of the API | |
42 | + """ | |
43 | + headers = {"Authorization" : f"Token {self.token}"} | |
44 | + | |
45 | + request = requests.get(self.BASE_PYROS_URL+url, headers=headers) | |
46 | + return request.json() | |
47 | + | |
48 | + def submit_sequence_file(self,file : str) -> dict: | |
49 | + """ | |
50 | + Submit sequence file by querying the API via PUT method | |
51 | + | |
52 | + Args: | |
53 | + file : File path to the sequence file to be uploaded | |
54 | + | |
55 | + Returns: | |
56 | + dict : Json object that represents the response of the API | |
57 | + """ | |
58 | + headers = { | |
59 | + "Authorization" : f"Token {self.token}", | |
60 | + 'Content-type': 'application/json', | |
61 | + } | |
62 | + url = f"{self.BASE_PYROS_URL}submit_sequence" | |
63 | + yaml_file = open(file,"r") | |
64 | + sequence_file_as_dict = yaml.safe_load(yaml_file) | |
65 | + print(f"File content : {sequence_file_as_dict}") | |
66 | + request = requests.put(url, headers=headers, json=sequence_file_as_dict) | |
67 | + print(f"Request status code {request.status_code}") | |
68 | + return request.json() | |
69 | + | |
70 | + | |
71 | +def main(): | |
72 | + username = sys.argv[1] | |
73 | + password = sys.argv[2] | |
74 | + url = sys.argv[3] | |
75 | + yaml_file = None | |
76 | + api = PyrosAPI(username,password) | |
77 | + if len(sys.argv) > 4 and sys.argv[4]: | |
78 | + yaml_file = sys.argv[4] | |
79 | + print(api.submit_sequence_file(yaml_file)) | |
80 | + else: | |
81 | + print(username,password,url) | |
82 | + print(api.get_url(url)) | |
83 | + | |
84 | + | |
85 | +if __name__ == "__main__": | |
86 | + main() | ... | ... |
... | ... | @@ -0,0 +1,16 @@ |
1 | +from common.models import PyrosUser, Sequence | |
2 | +from rest_framework import serializers | |
3 | + | |
4 | +class UserSerializer(serializers.ModelSerializer): | |
5 | + class Meta: | |
6 | + model = PyrosUser | |
7 | + fields = ['url', 'username', 'email', 'institute', "user_level", "motive_of_registration"] | |
8 | + depth = 1 | |
9 | + | |
10 | + | |
11 | + | |
12 | +class SequenceSerializer(serializers.ModelSerializer): | |
13 | + pyros_user = UserSerializer(read_only=True) | |
14 | + class Meta: | |
15 | + model = Sequence | |
16 | + fields = "__all__" | |
0 | 17 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,14 @@ |
1 | +from django.urls import path, include | |
2 | +from . import views | |
3 | +from rest_framework.authtoken.views import obtain_auth_token | |
4 | +from rest_framework import routers | |
5 | + | |
6 | +router = routers.DefaultRouter() | |
7 | +router.register(r'users', views.UserViewSet) | |
8 | +router.register(r'sequences', views.SequenceViewSet) | |
9 | +urlpatterns = [ | |
10 | + path('', include(router.urls)), | |
11 | + path('hello/', views.Users.as_view(), name='hello'), | |
12 | + path('api-token-auth/', obtain_auth_token, name='api_token_auth'), | |
13 | + path("submit_sequence",views.submit_sequence_with_json, name="api_submit_sequence"), | |
14 | +] + router.urls | |
0 | 15 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,63 @@ |
1 | +from django.shortcuts import render | |
2 | +from rest_framework.views import APIView | |
3 | +from rest_framework.response import Response | |
4 | +from rest_framework import viewsets | |
5 | +from rest_framework.permissions import IsAuthenticated, AllowAny | |
6 | +from rest_framework.decorators import api_view, permission_classes | |
7 | +from django.core.validators import ValidationError | |
8 | +from src.core.pyros_django.user_manager import views as user_views | |
9 | +from api.serializers import SequenceSerializer, UserSerializer | |
10 | +from common.models import PyrosUser, Sequence | |
11 | +from routine_manager.functions import check_sequence_file_validity | |
12 | +# Create your views here. | |
13 | + | |
14 | + | |
15 | +class Users(APIView): | |
16 | + | |
17 | + permission_classes = (IsAuthenticated,) | |
18 | + | |
19 | + def get(self, request): | |
20 | + content = {"message" : "Hello"} | |
21 | + return Response(content) | |
22 | + | |
23 | + @api_view(["GET"]) | |
24 | + def user_logout(request): | |
25 | + | |
26 | + request.user.auth_token.delete() | |
27 | + | |
28 | + user_views.logout() | |
29 | + | |
30 | + return Response('User Logged out successfully') | |
31 | + | |
32 | + | |
33 | +class UserViewSet(viewsets.ModelViewSet): | |
34 | + """ | |
35 | + API endpoint that allows users to be viewed or edited. | |
36 | + """ | |
37 | + queryset = PyrosUser.objects.all().order_by('-date_joined') | |
38 | + serializer_class = UserSerializer | |
39 | + permission_classes = [IsAuthenticated] | |
40 | + | |
41 | + | |
42 | +class SequenceViewSet(viewsets.ModelViewSet): | |
43 | + """ | |
44 | + API endpoint that allows users to be viewed or edited. | |
45 | + """ | |
46 | + queryset = Sequence.objects.all().order_by("-created") | |
47 | + serializer_class = SequenceSerializer | |
48 | + permission_classes = [IsAuthenticated] | |
49 | + | |
50 | + def get_queryset(self): | |
51 | + """ | |
52 | + This view should return a list of all the purchases | |
53 | + for the currently authenticated user. | |
54 | + """ | |
55 | + user = self.request.user | |
56 | + return Sequence.objects.filter(pyros_user=user).order_by("-created") | |
57 | + | |
58 | +@api_view(["PUT"]) | |
59 | +def submit_sequence_with_json(request): | |
60 | + sequence_json = request.data | |
61 | + print(sequence_json) | |
62 | + response = check_sequence_file_validity(sequence_json,request) | |
63 | + return Response(response) | ... | ... |
src/core/pyros_django/pyros/settings.py
... | ... | @@ -192,6 +192,10 @@ INSTALLED_APPS = [ |
192 | 192 | 'test_without_migrations', |
193 | 193 | 'bootstrap3', |
194 | 194 | |
195 | + # Django Rest Framework | |
196 | + "rest_framework", | |
197 | + "rest_framework.authtoken", | |
198 | + | |
195 | 199 | # PYROS APPS |
196 | 200 | 'dashboard', |
197 | 201 | 'scheduler', |
... | ... | @@ -206,9 +210,17 @@ INSTALLED_APPS = [ |
206 | 210 | 'devices', |
207 | 211 | 'scientific_program', |
208 | 212 | 'obsconfig', |
213 | + "api", | |
209 | 214 | #'kombu.transport.django' |
210 | 215 | ] |
211 | 216 | |
217 | +REST_FRAMEWORK = { | |
218 | + 'DEFAULT_AUTHENTICATION_CLASSES': [ | |
219 | + 'rest_framework.authentication.TokenAuthentication', | |
220 | + 'rest_framework.authentication.SessionAuthentication', | |
221 | + ], | |
222 | +} | |
223 | + | |
212 | 224 | MIDDLEWARE = [ |
213 | 225 | 'django.middleware.security.SecurityMiddleware', |
214 | 226 | 'django.contrib.sessions.middleware.SessionMiddleware', | ... | ... |
src/core/pyros_django/pyros/urls.py
... | ... | @@ -42,6 +42,7 @@ urlpatterns = [ |
42 | 42 | url(r'^user_manager/', include('user_manager.urls')), |
43 | 43 | url(r'^scientific_program/', include('scientific_program.urls')), |
44 | 44 | url(r'^obs_config/', include('obsconfig.urls')), |
45 | + url(r'^api/', include('api.urls')), | |
45 | 46 | url(r'^$', dashboard.views.index) |
46 | 47 | ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) |
47 | 48 | ... | ... |
src/core/pyros_django/routine_manager/functions.py
1 | 1 | import ast |
2 | 2 | from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig |
3 | 3 | from .forms import SequenceForm, AlbumForm, PlanForm |
4 | -from common.models import PyrosUser, ScientificProgram, Sequence, Album, Plan | |
4 | +from common.models import PyrosUser, ScientificProgram, Sequence, Album, Plan, Period | |
5 | 5 | import datetime |
6 | 6 | import os, yaml |
7 | 7 | |
... | ... | @@ -19,8 +19,9 @@ def check_sequence_file_validity(yaml_content,request): |
19 | 19 | "errors": [], |
20 | 20 | } |
21 | 21 | index_value_sp = yaml_content["sequence"]["scientific_program"]["value"] |
22 | + values = yaml_content["sequence"]["scientific_program"]["values"] | |
22 | 23 | if index_value_sp < 0 or index_value_sp > len(yaml_content["sequence"]["scientific_program"]["values"]): |
23 | - result["errors"].append(f"Value of scientific program isn't valid, index out of bounds") | |
24 | + result["errors"].append(f"Value of scientific program isn't valid, index out of bounds ({index_value_sp} > {len(values)})") | |
24 | 25 | index_value_sp = 0 |
25 | 26 | chosen_sp = ScientificProgram.objects.get(name=yaml_content["sequence"]["scientific_program"]["values"][index_value_sp]) |
26 | 27 | if chosen_sp in sp_list: |
... | ... | @@ -37,8 +38,9 @@ def check_sequence_file_validity(yaml_content,request): |
37 | 38 | else: |
38 | 39 | if yaml_content["sequence"][field].get("values"): |
39 | 40 | index_value = yaml_content["sequence"][field]["value"] |
41 | + values = yaml_content["sequence"][field]["values"] | |
40 | 42 | if index_value < 0 or index_value > len(yaml_content["sequence"][field]["values"]): |
41 | - result["errors"].append(f"Value of {field} isn't valid, index out of bounds") | |
43 | + result["errors"].append(f"Value of {field} isn't valid, index out of bounds ({index_value} > {len(values)})") | |
42 | 44 | index_value = 0 |
43 | 45 | value = yaml_content["sequence"][field]["values"][index_value] |
44 | 46 | else: |
... | ... | @@ -102,8 +104,9 @@ def check_sequence_file_validity(yaml_content,request): |
102 | 104 | else: |
103 | 105 | if plan[field].get("values"): |
104 | 106 | index_value = plan[field]["value"] |
107 | + values = plan[field]["values"] | |
105 | 108 | if index_value < 0 or index_value > len(plan[field]["values"]): |
106 | - result["errors"].append(f"Value of Plan field '{field}' isn't valid, index out of bounds") | |
109 | + result["errors"].append(f"Value of Plan field '{field}' isn't valid, index out of bounds ({index_value} > {len(values)})") | |
107 | 110 | index_value = 0 |
108 | 111 | value = plan[field]["values"][index_value] |
109 | 112 | try: |
... | ... | @@ -129,9 +132,15 @@ def check_sequence_file_validity(yaml_content,request): |
129 | 132 | |
130 | 133 | seq.status = Sequence.TOBEPLANNED |
131 | 134 | seq.complete = True |
135 | + period = Period.objects.exploitation_period() | |
136 | + if Period.objects.next_period() != None and Period.objects.next_period().start_date < seq.start_date.date(): | |
137 | + period = Period.objects.next_period() | |
138 | + seq.period = period | |
132 | 139 | seq.save() |
133 | 140 | if len(result["errors"]) != 0: |
134 | 141 | result["succeed"] = False |
135 | 142 | seq.delete() |
143 | + else: | |
144 | + result["sequence_id"] = seq.id | |
136 | 145 | return result |
137 | 146 | ... | ... |