Commit 98621b4643d32eb06541e22b72f79fb0d8f5e8c8

Authored by Alexis Koralewski
1 parent 42cd9e29
Exists in dev

add DRF, pyros api views, pyros_api python script, fixing period assignation whe…

…n submitting a sequence
  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 07-02-2022 (AKo) : v0.3.5.0 7 07-02-2022 (AKo) : v0.3.5.0
2 - Adding INVENTORY section to observatory configuration 8 - Adding INVENTORY section to observatory configuration
3 - Logout now redirect to home page 9 - Logout now redirect to home page
1 -0.3.5.1  
2 \ No newline at end of file 1 \ No newline at end of file
  2 +0.3.6.0
3 \ No newline at end of file 3 \ No newline at end of file
install/requirements.txt
@@ -5,7 +5,8 @@ Django==3.2 @@ -5,7 +5,8 @@ Django==3.2
5 django-admin-tools==0.9.1 5 django-admin-tools==0.9.1
6 django-bootstrap3==21.1 6 django-bootstrap3==21.1
7 django-extensions==3.1.5 7 django-extensions==3.1.5
8 - 8 +# for Django Rest Framework (DRF)
  9 +djangorestframework
9 # for Choices 10 # for Choices
10 django-model-utils==4.2.0 11 django-model-utils==4.2.0
11 django-suit==0.2.28 12 django-suit==0.2.28
pyros_api.py 0 → 100644
@@ -0,0 +1,86 @@ @@ -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()
src/core/pyros_django/api/__init__.py 0 → 100644
src/core/pyros_django/api/admin.py 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +from django.contrib import admin
  2 +
  3 +# Register your models here.
src/core/pyros_django/api/apps.py 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +from django.apps import AppConfig
  2 +
  3 +
  4 +class ApiConfig(AppConfig):
  5 + default_auto_field = 'django.db.models.BigAutoField'
  6 + name = 'api'
src/core/pyros_django/api/migrations/__init__.py 0 → 100644
src/core/pyros_django/api/models.py 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +from django.db import models
  2 +
  3 +# Create your models here.
src/core/pyros_django/api/serializers.py 0 → 100644
@@ -0,0 +1,16 @@ @@ -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 \ No newline at end of file 17 \ No newline at end of file
src/core/pyros_django/api/settings.py 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +from src.core.pyros_django import pyros_settings
  2 +
  3 +
  4 +REST_FRAMEWORK = {
  5 + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
  6 + 'PAGE_SIZE': pyros_settings.NB_ELEMENT_PER_PAGE
  7 +}
0 \ No newline at end of file 8 \ No newline at end of file
src/core/pyros_django/api/tests.py 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +from django.test import TestCase
  2 +
  3 +# Create your tests here.
src/core/pyros_django/api/urls.py 0 → 100644
@@ -0,0 +1,14 @@ @@ -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 \ No newline at end of file 15 \ No newline at end of file
src/core/pyros_django/api/views.py 0 → 100644
@@ -0,0 +1,63 @@ @@ -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,6 +192,10 @@ INSTALLED_APPS = [
192 'test_without_migrations', 192 'test_without_migrations',
193 'bootstrap3', 193 'bootstrap3',
194 194
  195 + # Django Rest Framework
  196 + "rest_framework",
  197 + "rest_framework.authtoken",
  198 +
195 # PYROS APPS 199 # PYROS APPS
196 'dashboard', 200 'dashboard',
197 'scheduler', 201 'scheduler',
@@ -206,9 +210,17 @@ INSTALLED_APPS = [ @@ -206,9 +210,17 @@ INSTALLED_APPS = [
206 'devices', 210 'devices',
207 'scientific_program', 211 'scientific_program',
208 'obsconfig', 212 'obsconfig',
  213 + "api",
209 #'kombu.transport.django' 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 MIDDLEWARE = [ 224 MIDDLEWARE = [
213 'django.middleware.security.SecurityMiddleware', 225 'django.middleware.security.SecurityMiddleware',
214 'django.contrib.sessions.middleware.SessionMiddleware', 226 'django.contrib.sessions.middleware.SessionMiddleware',
src/core/pyros_django/pyros/urls.py
@@ -42,6 +42,7 @@ urlpatterns = [ @@ -42,6 +42,7 @@ urlpatterns = [
42 url(r'^user_manager/', include('user_manager.urls')), 42 url(r'^user_manager/', include('user_manager.urls')),
43 url(r'^scientific_program/', include('scientific_program.urls')), 43 url(r'^scientific_program/', include('scientific_program.urls')),
44 url(r'^obs_config/', include('obsconfig.urls')), 44 url(r'^obs_config/', include('obsconfig.urls')),
  45 + url(r'^api/', include('api.urls')),
45 url(r'^$', dashboard.views.index) 46 url(r'^$', dashboard.views.index)
46 ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 47 ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
47 48
src/core/pyros_django/routine_manager/functions.py
1 import ast 1 import ast
2 from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig 2 from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig
3 from .forms import SequenceForm, AlbumForm, PlanForm 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 import datetime 5 import datetime
6 import os, yaml 6 import os, yaml
7 7
@@ -19,8 +19,9 @@ def check_sequence_file_validity(yaml_content,request): @@ -19,8 +19,9 @@ def check_sequence_file_validity(yaml_content,request):
19 "errors": [], 19 "errors": [],
20 } 20 }
21 index_value_sp = yaml_content["sequence"]["scientific_program"]["value"] 21 index_value_sp = yaml_content["sequence"]["scientific_program"]["value"]
  22 + values = yaml_content["sequence"]["scientific_program"]["values"]
22 if index_value_sp < 0 or index_value_sp > len(yaml_content["sequence"]["scientific_program"]["values"]): 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 index_value_sp = 0 25 index_value_sp = 0
25 chosen_sp = ScientificProgram.objects.get(name=yaml_content["sequence"]["scientific_program"]["values"][index_value_sp]) 26 chosen_sp = ScientificProgram.objects.get(name=yaml_content["sequence"]["scientific_program"]["values"][index_value_sp])
26 if chosen_sp in sp_list: 27 if chosen_sp in sp_list:
@@ -37,8 +38,9 @@ def check_sequence_file_validity(yaml_content,request): @@ -37,8 +38,9 @@ def check_sequence_file_validity(yaml_content,request):
37 else: 38 else:
38 if yaml_content["sequence"][field].get("values"): 39 if yaml_content["sequence"][field].get("values"):
39 index_value = yaml_content["sequence"][field]["value"] 40 index_value = yaml_content["sequence"][field]["value"]
  41 + values = yaml_content["sequence"][field]["values"]
40 if index_value < 0 or index_value > len(yaml_content["sequence"][field]["values"]): 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 index_value = 0 44 index_value = 0
43 value = yaml_content["sequence"][field]["values"][index_value] 45 value = yaml_content["sequence"][field]["values"][index_value]
44 else: 46 else:
@@ -102,8 +104,9 @@ def check_sequence_file_validity(yaml_content,request): @@ -102,8 +104,9 @@ def check_sequence_file_validity(yaml_content,request):
102 else: 104 else:
103 if plan[field].get("values"): 105 if plan[field].get("values"):
104 index_value = plan[field]["value"] 106 index_value = plan[field]["value"]
  107 + values = plan[field]["values"]
105 if index_value < 0 or index_value > len(plan[field]["values"]): 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 index_value = 0 110 index_value = 0
108 value = plan[field]["values"][index_value] 111 value = plan[field]["values"][index_value]
109 try: 112 try:
@@ -129,9 +132,15 @@ def check_sequence_file_validity(yaml_content,request): @@ -129,9 +132,15 @@ def check_sequence_file_validity(yaml_content,request):
129 132
130 seq.status = Sequence.TOBEPLANNED 133 seq.status = Sequence.TOBEPLANNED
131 seq.complete = True 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 seq.save() 139 seq.save()
133 if len(result["errors"]) != 0: 140 if len(result["errors"]) != 0:
134 result["succeed"] = False 141 result["succeed"] = False
135 seq.delete() 142 seq.delete()
  143 + else:
  144 + result["sequence_id"] = seq.id
136 return result 145 return result
137 146