RequestSerializer.py 8.46 KB
from django.conf import settings
from common.models import *
import xml.etree.ElementTree as ET
import sys
from xml.dom import minidom
from .validators import check_plan_validity
import re
from decimal import Decimal
from jdcal import gcal2jd, jd2gcal
import time
import datetime
import logger.config as l
import scheduler
log = l.setupLogger("RequestSerializer", "RequestSerializer")

def prettify(elem):
    """
        :returns : A pretty-printed XML string for the Element elem
    """
    rough_string = ET.tostring(elem, 'utf-8')
    reparsed = minidom.parseString(rough_string)
    return reparsed.toprettyxml(indent="    ")

class RequestSerializer():
    submit = False
    relative = False
    """
        Serializes and unserializes a request, with all children (sequences, albums and plans)
        Format : XML
        Only serializes the wanted fields

        Entry point(s) :
            - serialize
    """

    def serialize(self, req, file_name):
        """
            Serializes the request into the given file
        """
        request = ET.Element("request", name=req.name, scientific_program=req.scientific_program.name,
                             target_type=req.target_type)
        for seq in req.sequences.all():
            sequence = ET.SubElement(request, "sequence", name=seq.name, target_coords=seq.target_coords,
                                     jd1=str(seq.jd1), jd2=str(seq.jd2))
            sequence.set("duration", str(seq.duration * 86400))
            for alb in seq.albums.all():
                album = ET.SubElement(sequence, "album", name=alb.name, detector=alb.detector.device.name)
                for plan in alb.plans.all():
                    pl = ET.SubElement(album, "plan", name=plan.name, filter=plan.filter.device.name, nb_images=str(plan.nb_images))
                    pl.set("duration", str(plan.duration * 86400))

        with open(file_name, "w") as file:
            file.write(prettify(request))

    def unserialize(self, xml_data, pyros_user):
        TIMESTAMP_JD = 2440587.500000
        """
            Unserializes the request read into file_name
            Throws exceptions in case of invalid file or values

            Directly saves the objects in DB
            The request still need to be submitted afterward !

            :returns : "" (empty string) in case of success, error message (string) instead
        """

        try:
            root = ET.fromstring(xml_data)
        except ET.ParseError as E:
            return "Invalid file : " + str(E)

        self.pyros_user = pyros_user
        self.request = None

        """ self.sequences will be a [(seq, album_list), ...], and same thing for album """
        self.sequences = []
        ret = self.unserialize_request(root)
        if ret != "":
            return ret

        if (settings.DEBUG):
            log.info(self.submit)
        if self.submit:
            self.request.submitted = 1
        self.request.save()
        for sequence, albums in self.sequences:
            if self.relative:
                sequence.jd1 = (time.time() + float(sequence.jd1)) / 86400 + TIMESTAMP_JD
                sequence.jd2 = (time.time() + float(sequence.jd2)) / 86400 + TIMESTAMP_JD
            sequence.request = self.request
            sequence.save()
            for album, plans in albums:
                album.sequence = sequence
                album.save()
                for plan in plans:
                    plan.album = album
                    plan.save()
                    check_plan_validity(plan)
        if (self.request.submitted):
            scheduler.tasks.scheduling.delay(first_schedule=True, alert=False)
        return ""

    def unserialize_request(self, request):
        '''
            Receives an xml.etree request and unserialize it
        '''

        if request.tag != "request":
            return "Main object should be a request (found %s instead)" % (request.tag,)
        possible_attribs = ["name", "scientific_program", "target_type", "submitted", "relative"]

        self.request = Request(pyros_user=self.pyros_user, is_alert=False, complete=False, submitted=False)

        for name, value in request.attrib.items():
            if name not in possible_attribs:
                return "Unknown attribute %s in request" % (name,)
            if name == "scientific_program":
                try:
                    sp = ScientificProgram.objects.get(name=value)
                    self.request.scientific_program = sp
                except Exception as E:
                    print(str(E))
                    return "Invalid scientific program %s" % (value,)
            elif name == "submitted":
                try:
                    self.submit = bool(int(value))
                except Exception as E:
                    print(str(E))
                    return ("Invalid value for submitted")
            elif name == "relative":
                try:
                    self.relative = bool(int(value))
                except Exception as E:
                    print(str(E))
                    return ("Invalid value for submitted")
            else:
                self.request.__dict__[name] = value

        for sequence in request:
            ret = self.unserialize_sequence(sequence)
            if ret != "":
                return ret
        return ""

    def unserialize_sequence(self, sequence):
        if sequence.tag != "sequence":
            return "A request can only have 'sequence' children (found %s instead)" % (sequence.tag,)
        possible_attribs = ["name", "target_coords", "jd1", "jd2", "duration"]

        seq = Sequence(request=self.request, status=Sequence.INCOMPLETE)

        for name, value in sequence.attrib.items():
            if name not in possible_attribs:
                return "Unknown attribute %s in sequence" % (name,)
            if name == "name":
                seq.name = value
            elif name == "target_coords":
                seq.target_coords = value
            elif name == "jd1":
                seq.jd1 = Decimal(value)
            elif name == "jd2":
                seq.jd2 = Decimal(value)
            elif name == "duration":
                seq.duration = Decimal(value) / 86400

        # TODO: faire des checks ?? j'imagine, par exemple pour la cohérence des valeurs
        album_list = []
        self.sequences.append((seq, album_list))

        for album in sequence:
            ret = self.unserialize_album(album, seq, album_list)
            if ret != "":
                return ret
        return ""

    def unserialize_album(self, album, sequence, album_list):
        if album.tag != "album":
            return "A sequence can only have 'album' children (found %s instead)" % (album.tag,)
        possible_attribs = ["name", "detector"]

        alb = Album(sequence=sequence)

        for name, value in album.attrib.items():
            if name not in possible_attribs:
                return "Unknown attribute %s in album" % (name,)
            if name == "detector":
                try:
                    detector = Detector.objects.get(device__name=value)
                    alb.detector = detector
                except Exception as E:
                    print(str(E))
                    return "Invalid detector %s" % (value,)
            elif name == "name":
                alb.name = value
        plan_list = []
        album_list.append((alb, plan_list))

        for plan in album:
            ret = self.unserialize_plan(plan, alb, plan_list)
            if ret != "":
                return ret
        return ""


    def unserialize_plan(self, plan, album, plan_list):
        if plan.tag != "plan":
            return "An album can only have 'plan' children (found %s instead)" % (plan.tag,)
        possible_attribs = ["name", "filter", "duration", "nb_images"]

        pl = Plan(album=album)

        for name, value in plan.attrib.items():
            if name not in possible_attribs:
                return "Unknown attribute %s in plan" % (name,)
            if name == "filter":
                try:
                    filter = Filter.objects.get(device__name=value)
                    pl.filter = filter
                except Exception as E:
                    return "Invalid filter %s" % (value,)
            elif name == "name":
                pl.name = value
            elif name == "duration":
                pl.duration = Decimal(value) / 86400
            elif name == "nb_images":
                pl.nb_images = int(value)

        # TODO: quelques checks ...

        plan_list.append(pl)
        return ""