from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django.shortcuts import get_object_or_404, render, redirect from common.models import * from django.db.models import Q from django.contrib.auth.decorators import login_required from src.core.pyros_django.obsconfig.configpyros import ConfigPyros from .forms import RequestForm, SequenceForm, AlbumForm, PlanForm, uneditablePlanForm from .validators import check_plan_validity, check_album_validity, check_sequence_validity, check_request_validity from .RequestSerializer import RequestSerializer import scheduler from django.contrib import messages """ logger """ from django.conf import settings # "import utils.Logger as l #"log = l.setupLogger("routine_manager-views", "routine_manager-views") """ XML Export / Import utils """ from wsgiref.util import FileWrapper from django.utils.encoding import smart_str from django.http import HttpResponse import mimetypes import os, ast, datetime from pprint import pprint from src.pyros_logger import log SAVED_REQUESTS_FOLDER = "misc/saved_requests/" @login_required def requests_list(request, status=0, message=""): """ Retrieves and display the routines list (routine manager main page) """ if settings.DEBUG: #log.info("From requests_list") pass if status == "-1": error = True elif status == "1": success = True # TODO: uncomment for alert filter # requests_objs = Request.objects.filter(pyros_user=request.user).filter(is_alert=False).order_by("-updated") sequences_objs = Sequence.objects.filter(pyros_user=request.user).order_by("-updated") sequences = [] for seq in sequences_objs: nb_album = Album.objects.filter(sequence=seq).count nb_plan = Plan.objects.filter(album__in=Album.objects.filter(sequence=seq).values("id")).count sequences.append({"seq":seq, "nb_album":nb_album, "nb_plan":nb_plan}) #print("REQ/views: Les requetes sont:") # requests_objs = Request.objects.filter(pyros_user=request.user).order_by("-updated") # print("REQ/views: Les requetes sont:") # requests = [] # for req in requests_objs: # #print("- REQ/views: requete", req) # sequences = req.sequences # nb_executed = sequences.filter(status=Sequence.EXECUTED).count # nb_cancelled = sequences.filter(Q(status=Sequence.INVALID) | Q(status=Sequence.CANCELLED) # | Q(status=Sequence.UNPLANNABLE)).count() # requests.append({'req': req, 'nb_seq': sequences.count(), 'nb_executed': nb_executed, 'nb_cancelled': nb_cancelled}) print("REQ/views: ICI:") # opens template src/routine_manager/templates/routine_manager/requests_list.html with all local variables return render(request, "routine_manager/requests_list.html", locals()) @login_required def action_request(request, req_id, action, status=0, message=""): """ Apply actions (view, edit, delete) on a given request """ req = Request.objects.get(id=req_id) depth_level = 1 if settings.DEBUG: #log.info("From action_request") pass if status == "-1": error = True elif status == "1": success = True if req.submitted == True and action == "edit": error = True message = "You can't edit a submitted request" action = "view" if check_request_validity(req) == True: return redirect(unsubmit_request, req_id) if action == "edit": form = RequestForm(instance=req) edit = True return render(request, "routine_manager/view_request.html", locals()) elif action == "view": form = RequestForm(instance=req, readonly=True) edit = False return render(request, "routine_manager/view_request.html", locals()) elif action == "delete": req.delete() return redirect(requests_list, status='1', message="Successfully deleted the request") return redirect(requests_list) @login_required def request_validate(request, req_id): """ Called when the request form was validated. Possible actions : Cancel, Save, Save and add sequence, Delete """ action = request.POST.get("action") req = Request.objects.get(id=req_id) form = RequestForm(instance=req, data=request.POST) if (settings.DEBUG): #log.info("From request_validate") pass if action == "cancel": if req.name == "New request": req.delete() return redirect(requests_list, status=1, message="Cancelled request modification") elif action == "delete": return redirect(action_request, req_id, "delete") elif form.is_valid(): req = form.save() if action == "save": return redirect(action_request, req_id=req_id, action="edit", status=1, message="Request saved") if action == "save_and_add": return redirect(create_sequence, req_id=req_id) else: error = True message = "Please check your field's validity" depth_level = 1 edit = True return render(request, "routine_manager/view_request.html", locals()) @login_required def action_sequence(request, seq_id, action, status=0, message=""): """ Apply actions (view, edit, delete) on a given sequence """ unit_name = os.environ["unit_name"] config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name) seq_id = int(seq_id) seq = Sequence.objects.get(id=seq_id) depth_level = 1 if (settings.DEBUG): #log.info("From action_sequence") pass if status == "-1": error = True elif status == "1": success = True if seq.status != Sequence.INCOMPLETE and seq.status != Sequence.COMPLETE and action == "edit": error = True message = "You can't edit a submitted request" action = "view" horizon_line = config.getHorizonLine(config.unit_name) horizon_line_svg = f'
(values : {horizon_line})
' if request.session.get("role") == "Admin": sp_list = None else: sp_of_user = request.user.get_scientific_program() sp_list = ScientificProgram.objects.observable_programs().filter(id__in=sp_of_user) if action == "edit": sp_of_user = request.user.get_scientific_program() sp_list = ScientificProgram.objects.observable_programs().filter(id__in=sp_of_user) if len(sp_list) == 0 : messages.add_message(request,messages.INFO,"Can't submit a Sequence : there is no scientific program to be assigned with") return redirect(requests_list) form = SequenceForm(instance=seq, data_from_config=config.getEditableAttributesOfMount(config.unit_name), layouts = config.get_layouts(config.unit_name), sp_list=sp_list) edit = True return render(request, "routine_manager/view_sequence.html", locals()) elif action == "view": form = SequenceForm(instance=seq, data_from_config=config.getEditableAttributesOfMount(config.unit_name), layouts = config.get_layouts(config.unit_name), sp_list=sp_list) edit = False return render(request, "routine_manager/view_sequence.html", locals()) elif action == "delete": log.info(f"User {request.user} did action delete sequence {seq.name}") seq.delete() #if check_sequence_validity(seq) == True: #return redirect(unsubmit_request, req_id) return redirect(requests_list) return redirect(requests_list) @login_required def sequence_validate(request, seq_id): """ Called when the sequence form was validated. Possible actions : Cancel, Save, Save and add album, Delete """ if (settings.DEBUG): #log.info("From sequence_validate") pass unit_name = os.environ["unit_name"] config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name) seq_id = int(seq_id) action = request.POST.get("action") seq = Sequence.objects.get(id=seq_id) form = SequenceForm(instance=seq, data=request.POST, data_from_config=config.getEditableAttributesOfMount(config.unit_name), layouts = config.get_layouts(config.unit_name)) if action == "cancel": if seq.name == "New sequence": seq.delete() return redirect(requests_list) elif action == "delete": return redirect(action_sequence, seq_id, "delete") elif action == "check_validity": seq.save() check_sequence_validity(seq) for album in Album.objects.filter(sequence=seq): for plan in Plan.objects.filter(album=album): check_plan_validity(plan) return redirect(action_sequence,seq_id,"edit") elif form.is_valid(): seq = form.save() if action == "save": message="Sequence saved" messages.add_message(request,messages.INFO,message) return redirect(action_sequence, seq_id=seq_id, action="edit", status=1, message="Sequence saved") if action == "save_and_add": albums = config.getLayoutByName(unit_name=config.unit_name,name_of_layout=seq.config_attributes["layout"])["ALBUMS"] for album_name in albums: create_album(request,seq_id,album_name=album_name) #return redirect(create_album, seq_id=seq_id) messages.add_message(request,messages.INFO,"Created albums according to chosen layouts") return redirect(action_sequence, seq.id, "edit") if action == "save_and_submit": if check_sequence_validity(seq): seq.status = Sequence.TOBEPLANNED seq.save() message="Sequence submitted" messages.add_message(request,messages.INFO,message) log.info(f"User {request.user} did action submit sequence {seq} for period {seq.period} ") return redirect(action_sequence, seq_id=seq_id, action="edit", status=1, message="Sequence submitted") else: message = "Can't submit sequence because it's incomplete (Need at least 1 album with 1 plan)" messages.add_message(request,messages.ERROR,message) status = 1 edit = True return render(request, "routine_manager/view_sequence.html", locals()) else: error = True message = "Please check your field's validity" depth_level = 1 edit = True return render(request, "routine_manager/view_sequence.html", locals()) @login_required def action_album(request, alb_id, action, status=0, message=""): """ Apply actions (view, edit, delete) on a given album """ unit_name = os.environ["unit_name"] config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name) alb_id = int(alb_id) alb = Album.objects.get(id=alb_id) seq_id = alb.sequence.id depth_level = 2 seq = alb.sequence if (settings.DEBUG): #log.info("From action_album") pass if status == "-1": error = True elif status == "1": success = True if alb.sequence.status != Sequence.INCOMPLETE and alb.sequence.status != Sequence.COMPLETE and action == "edit": error = True message = "You can't edit a submitted request" action = "view" # if check_album_validity(alb) == True: #return redirect(unsubmit_request, req_id) # pass if action == "edit": form = AlbumForm(instance=alb,readonly=True) edit = True return render(request, "routine_manager/view_album.html", locals()) elif action == "view": form = AlbumForm(instance=alb, readonly=True) edit = False return render(request, "routine_manager/view_album.html", locals()) elif action == "delete": log.info(f"User {request.user} did action delete Album {alb.name} of Sequence {seq}") alb.delete() return redirect(action_sequence, seq_id=alb.sequence.id, action="edit", status='1', message="Successfully deleted the album") return redirect(requests_list) @login_required def album_validate(request, alb_id): """ Called when the album form was validated. Possible actions : Cancel, Save, Save and add plan, Delete """ unit_name = os.environ["unit_name"] config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name) alb_id = int(alb_id) action = request.POST.get("action") alb = Album.objects.get(id=alb_id) form = AlbumForm(instance=alb, data=request.POST) if (settings.DEBUG): #log.info("From album_validate") pass if action == "cancel": pass if alb.name == "New album": alb.delete() return redirect(action_sequence, seq_id=alb.sequence.id, action="edit", status='1', message="Cancelled album modification") elif action == "delete": return redirect(action_album, alb_id, "delete") elif form.is_valid(): alb = form.save() if action == "save": return redirect(action_sequence, seq_id=alb.sequence.id, action="edit") #return redirect(action_album, alb_id=alb_id, action="edit", status=1, message="Album saved") if action == "save_and_add": return redirect(create_plan, alb_id=alb_id) else: error = True message = "Please check your field's validity" depth_level = 2 edit = True seq_id = alb.sequence.id action = "edit" return render(request, "routine_manager/view_album.html", locals()) @login_required def action_plan(request, plan_id, action, status=0, message=""): """ Apply actions (view, edit, delete) on a given plan """ unit_name = os.environ["unit_name"] config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name) plan_id = int(plan_id) plan = Plan.objects.get(id=plan_id) album_name = plan.album.name channel = config.getAlbumByName(config.unit_name,album_name)["CHANNELS"][0] data_from_config = config.getEditableAttributesOfChannel(config.unit_name,channel) seq_id = plan.album.sequence.id seq = plan.album.sequence alb_id = plan.album.id depth_level = 3 # get index of plan within the album. Adding one for human comprehension index_of_plan_in_album = list(plan.album.plans.all().values_list("id",flat=True)).index(plan.id) + 1 if (settings.DEBUG): #log.info("From action_plan") pass if status == "-1": error = True elif status == "1": success = True if plan.album.sequence.status != Sequence.INCOMPLETE and plan.album.sequence.status != Sequence.COMPLETE and action == "edit": error = True message = "You can't edit a submitted sequence" action = "view" if action == "edit": form = PlanForm(edited_plan=plan, data_from_config=data_from_config) uneditableForm = uneditablePlanForm(data_from_config=config.getUneditableAttributesOfChannel(config.unit_name,channel)) edit = True return render(request, "routine_manager/view_plan.html", locals()) elif action == "view": form = PlanForm(edited_plan=plan, data_from_config=data_from_config,readonly=True) uneditableForm = uneditablePlanForm(data_from_config=config.getUneditableAttributesOfChannel(config.unit_name,channel)) edit = False return render(request, "routine_manager/view_plan.html", locals()) elif action == "delete": log.info(f"User {request.user} did action delete Plan {plan.id} of Sequence {seq}") plan.delete() check_sequence_validity(seq) check_album_validity(plan.album) return redirect(action_album, alb_id=plan.album.id, action="edit", status='1', message="Successfully deleted the plan") return redirect(requests_list) @login_required def plan_validate(request, plan_id): """ Called when the plan form was validated. Possible actions : Cancel, Save, Delete """ unit_name = os.environ["unit_name"] config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name) plan_id = int(plan_id) action = request.POST.get("action") plan = Plan.objects.get(id=plan_id) seq = plan.album.sequence # name_of_channel_group = plan.album.name_of_channel_group # channel = None # if name_of_channel_group == "All": # channel = config.getLayoutByName(config.unit_name,config.get_layouts(config.unit_name)["groups"][0]["name"]) # else: # # we retrieve config of the first channel of the group # channel = config.getLayoutByName(config.unit_name,name_of_channel_group)["channels"][0] album_name = plan.album.name channel = config.getAlbumByName(config.unit_name,album_name)["CHANNELS"][0] data_from_config = config.getEditableAttributesOfChannel(config.unit_name,channel) #form = PlanForm(instance=plan, data=request.POST) form = PlanForm(edited_plan=plan, data_from_config=data_from_config) if (settings.DEBUG): #log.info("From plan_validate") pass if action == "cancel": plan.delete() return redirect(action_album, alb_id=plan.album.id, action="edit", status='1', message="Cancelled plan modification") elif action == "delete": return redirect(action_plan, plan_id, "delete") elif form.is_valid(): # unused plan = form.save() if action == "save": action="edit" status=1 message="Plan saved" return redirect(action_plan, locals()) elif action == "save": if request.POST: post_data = request.POST.copy() post_data.pop("csrfmiddlewaretoken") post_data.pop("action") nb_images = post_data.pop("nb_images")[0] config_attributes = {} for key,value in post_data.items(): if type(value) == str: try: # linked values new_dict = {key:{}} splitted_values = value.split(";") config_attributes[key] = {} for splitted_value in splitted_values: subkey,subvalue = splitted_value.split(":") config_attributes[key][subkey] = ast.literal_eval(subvalue) except: # Do nothing, normal string config_attributes[key] = ast.literal_eval(value) plan.nb_images = int(nb_images) plan.config_attributes = config_attributes plan.save() # if check_plan_validity(plan) == True: #return redirect(unsubmit_request, req_id) # pass return redirect(action_album, plan.album.id, "edit") #return redirect(action_plan, plan_id, "edit", 1, "Plan saved") else: error = True message = "Please check your field's validity" edit = True seq_id = plan.album.sequence.id alb_id = plan.album.id depth_level = 3 return render(request, "routine_manager/view_plan.html", locals()) @login_required def create_request(request): """ Create a new request and redirects to the editing action on it """ req = Request.objects.create(pyros_user=request.user, name="New request", is_alert=False, autodeposit=False, complete=False, submitted=False) return redirect(action_request, req.id, "edit") @login_required def create_sequence(request): """ Create a new sequence and redirects to the editing action on it """ date_of_sequence = datetime.datetime.utcnow() date_of_sequence = date_of_sequence.strftime("%Y%m%dT%H%M%S") sp_of_user = request.user.get_scientific_program() sp_list = ScientificProgram.objects.observable_programs().filter(id__in=sp_of_user) if len(sp_list) == 0 : messages.add_message(request,messages.INFO,"Can't submit a Sequence : there is no scientific program to be assigned with") return redirect(requests_list) seq = Sequence.objects.create(pyros_user=request.user, name=f"seq_{str(date_of_sequence)}", status=Sequence.INCOMPLETE) log.info(f"User {request.user} did action create Sequence {seq.name}") return redirect(action_sequence, seq.id, "edit") @login_required def create_album(request, seq_id,album_name): """ Create a new album and redirects to the editing action on it """ seq = Sequence.objects.get(id=seq_id) alb = Album.objects.create(sequence=seq, name=album_name, complete=False) log.info(f"User {request.user} did action create Album {alb.id} of Sequence {seq.name}") #return redirect(action_album, alb.id, "edit") @login_required def create_plan(request, alb_id): """ Create a new plan and redirects to the editing action on it """ alb = Album.objects.get(id=alb_id) plan = Plan.objects.create(album=alb,nb_images=1) log.info(f"User {request.user} did action create Plan {plan.id} of Sequence {plan.album.sequence.name}") return redirect(action_plan, plan.id, "edit") @login_required def submit_request(request, req_id, redir): """ Submits a request and its sequences for scheduling """ if (settings.DEBUG): pass log.info("From submit_request") req = Request.objects.get(id=req_id) error = False if req.complete == False: error = True message = "A request must be complete to be submitted" elif req.submitted == True: error = True message = "The request is already submitted" if error == True: return redirect(action_request, req_id=req_id, action="view", status=-1, message=message) for seq in req.sequences.all(): seq.status = Sequence.TOBEPLANNED seq.save() req.submitted = True req.save() # TODO : changer le first_schedule ... ''' if settings.USE_CELERY: print("WITH CELERY") scheduler.tasks.scheduling.delay(first_schedule=True, alert=False) else: ''' print("Change 1st schedule") scheduler.tasks.scheduling().run(first_schedule=True, alert=False) message = "The request was submitted" if redir == "action_request": return redirect(action_request, req_id=req_id, action="view", status=1, message=message) else: return redirect(requests_list, status=1, message=message) @login_required def unsubmit_request(request, req_id): """ Unsubmits a request and remove its sequences from scheduling """ if (settings.DEBUG): pass log.info("From unsubmit_request") req = Request.objects.get(id=req_id) # TODO: uncomment pour la production # if req.sequences.filter(Q(status=Sequence.EXECUTED) | Q(status=Sequence.EXECUTING)).exists(): # message = "You can't unsubmit a request with executed sequences" # return redirect(action_request, req_id=req_id, action="view", status=-1, message=message) req.submitted = False req.save() sequences = req.sequences.filter(Q(status=Sequence.TOBEPLANNED) | Q(status=Sequence.PLANNED) | Q(status=Sequence.INVALID) | Q(status=Sequence.UNPLANNABLE)) for seq in sequences: seq.status = Sequence.COMPLETE seq.save() # TODO: uncomment # scheduler.tasks.scheduling.delay(first_schedule=True, alert=False) # TODO : changer le first_schedule ... if req.complete == True: message = "The request was unsubmitted" else: message = "The request was unsubmitted because it is incomplete" return redirect(action_request, req_id=req_id, action="edit", status=1, message=message) @login_required def export_request(request, req_id): """ Create an XML file with the given request, and send a download request to the user """ if (settings.DEBUG): #log.info("From export_request") pass req = Request.objects.get(id=req_id) if req.complete == False: message = "Request must be completely valid to be serialized" return redirect(action_request, req_id=req_id, action="view", status=-1, message=message) file_name = SAVED_REQUESTS_FOLDER + "request" + str(req.id) + ".xml" rs = RequestSerializer() rs.serialize(req, file_name) wrapper = FileWrapper(open(file_name, "r")) content_type = mimetypes.guess_type(file_name)[0] response = HttpResponse(wrapper, content_type=content_type) response['Content-Length'] = os.path.getsize(file_name) response['Content-Disposition'] = 'attachment; filename=%s' % smart_str(os.path.basename(file_name)) return response @login_required @csrf_exempt def import_request(request): """ Ask for a XML file, parse it and create a request in DB Don't do anything if there is a single error into the file """ if (settings.DEBUG): pass #log.info("From import_request") log.info(request) if request.method == "POST": file = request.FILES.get("request_file") if file is None: status = -1 message = "File does not exist" elif file.size > 1000000: status = -1 message = "File is too big (more than 1 000 000 bytes)" else: rs = RequestSerializer() message = rs.unserialize(file.read(), request.user) if message != "": status = -1 else: status = 1 message = "Request imported successfully. Please check it before submitting for scheduling." print("message is", message) else: status = -1 message = "Internal error" # Now, display the list of requests (updated with this new request) return redirect(requests_list, status=status, message=message) @login_required def test_create_plan(request): config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"]) if request.POST: post_data = request.POST.copy() post_data.pop("csrfmiddlewaretoken") nb_images = post_data.pop("nb_images")[0] config_attributes = {} for key,value in post_data.items(): if type(value) == str: try: # linked values new_dict = {key:{}} splitted_values = value.split(";") config_attributes[key] = {} for splitted_value in splitted_values: subkey,subvalue = splitted_value.split(":") config_attributes[key][subkey] = ast.literal_eval(subvalue) except: # Do nothing, normal string config_attributes[key] = ast.literal_eval(value) plan = Plan() plan.nb_images = int(nb_images) plan.config_attributes = config_attributes plan.save() form = PlanForm(data_from_config=config.getEditableAttributesOfChannel(config.unit_name,list(config.get_channels(config.unit_name).keys())[0])) uneditableForm = uneditablePlanForm(data_from_config=config.getUneditableAttributesOfChannel(config.unit_name,list(config.get_channels(config.unit_name).keys())[0])) return render(request,"routine_manager/testcreateplan.html",{"form":form,"uneditableForm":uneditableForm}) @login_required def create_albums(request,seq_id,layout): seq = Sequence.objects.get(id=seq_id) unit_name = os.environ["unit_name"] config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"], unit_name) albums = config.getLayoutByName(unit_name=config.unit_name,name_of_layout=layout)["ALBUMS"] for album_name in albums: create_album(request,seq_id,album_name=album_name) #return redirect(create_album, seq_id=seq_id) messages.add_message(request,messages.INFO,"Created album(s) according to chosen layout") return HttpResponse("ok") @login_required def delete_albums(request,seq_id): seq = Sequence.objects.get(id=seq_id) albums = Album.objects.filter(sequence=seq).delete() return HttpResponse(albums) @login_required def edit_plan(request,id): plan = get_object_or_404(Plan,pk=id) config = ConfigPyros(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"]) if request.POST: post_data = request.POST.copy() post_data.pop("csrfmiddlewaretoken") nb_images = post_data.pop("nb_images")[0] config_attributes = {} for key,value in post_data.items(): if type(value) == str: try: # linked values new_dict = {key:{}} splitted_values = value.split(";") config_attributes[key] = {} for splitted_value in splitted_values: subkey,subvalue = splitted_value.split(":") config_attributes[key][subkey] = ast.literal_eval(subvalue) except: # Do nothing, normal string config_attributes[key] = ast.literal_eval(value) plan.nb_images = int(nb_images) plan.config_attributes = config_attributes plan.save() form = PlanForm(data_from_config=config.getEditableAttributesOfChannel(config.unit_name,list(config.get_channels(config.unit_name).keys())[0]),edited_plan=plan) uneditableForm = uneditablePlanForm(data_from_config=config.getUneditableAttributesOfChannel(config.unit_name,list(config.get_channels(config.unit_name).keys())[0])) return render(request,"routine_manager/testcreateplan.html",{"form":form,"uneditableForm":uneditableForm})