1

I'm working on a Django project (Django 1.10, Python 3.4.3), where I am trying to upload a file in a python subprocess. I need to upload the file and calculate some sums on it as I upload it and we want the system to still be usable while it is doing that, so we thought putting those tasks in a subprocess would work.

Right now, I'm working on just getting the file uploaded.

FIRST ATTEMPT:

app/views/UploadView.py

class NewUploadView(TemplateView):
    template_name = "../templates/simulation/upload.html"

    @transaction.atomic
    def post(self, request):
        if request.method == 'POST':
            if not request.FILES['input_file']:
                return HttpResponseBadRequest("No 'input_file' is provided")
            else:
                sim_name = self.request.POST.get(u"name", None)

                p = Popen(["/.virtualenvs/env/bin/python3.4", "manage.py", 
                    "load_simulation", str(request.FILES['input_file'], 
                    str(sim_name)])

                p.communicate()

               # Redirect to appropriate page  
               HttpResponseRedirect(reverse('home.display_simulations'))`

app/management/commands/load_simulation.py

import csv
import os

from django.conf import settings
from django.core.management.base import BaseCommand

from website.apps.home.models import Simulation, Location, Data, SimulationModel


class Command(BaseCommand):
    help = 'Upload csv data file and save objects in database'

    def add_arguments(self, parser):
        parser.add_argument("my_file", type=str)
        parser.add_argument("simulation_name", type=str)

    def handle(self, *args, **options):
        """
        Upload data file
        :param args:
        :param options:
        :return:
        """
        my_file = options["my_file"]
        simulation_name = options["simulation_name"]

        # Save the simulation object, with the data_file
        simulation = Simulation.objects.create(name=simulation_name, data_file=my_file)
        simulation.save()

        filename = os.path.join(settings.MEDIA_ROOT, "simulation_files", simulation.getfilename())
        file_obj = open(filename, 'r')
        dictreader = csv.DictReader(file_obj)
        line = None

        for line in dictreader:
            location = Location.objects.filter(
                department_code=line['department_code'],
                municipality_code=line['municipality_code'],
            ).first()

            if not location:
                location = Location.objects.create(
                    department=line['department'],
                    department_code=line['department_code'],
                    municipality=line['municipality'],
                    municipality_code=line['municipality_code'],
                )

            Data.objects.create(
                location=location,
                date=line['date'].split(" ")[0],  # Assuming date is in "YYYY-MM-DD HH:MM:SS" format
                simulation=simulation,
                value_low=line['value_low'],
                value_mid=line['value_mid'],
                value_high=line['value_high'],
            )

        simulation.is_uploaded = True
        simulation.save()
        print("DONE WITH LOAD SIMULATION!")

I have it set up so that the file will be saved in the /media/simulation_files directory. But the file wouldn't save to the server, which means it fails in the load_simulation.py file at the line where it tries to open the file since the file isn't at that location.

Then I realized that I'm passing in a TemporaryFile as a string, so maybe that's part of the problem.

SECOND ATTEMPT

I changed part of UploadView.py to take in the file as stdin.

        p = Popen(["/.virtualenvs/env/bin/python3.4", "manage.py",
                              "load_simulation", str(sim_name), is_historical],
                              stdin=PIPE)

        p.communicate(input=request.FILES['output_file'])

and modified load_simulation.py to begin as follows:

class Command(BaseCommand):
    help = 'Upload csv data file and save objects in database'

    def add_arguments(self, parser):
        parser.add_argument("my_file", type=str)
        parser.add_argument("simulation_name", type=str)

    def handle(self, *args, **options):
        """
        Upload data file
        :param args:
        :param options:
        :return:
        """

        simulation_name = options["simulation_name"]

        my_file = input()

        # Save the simulation object, with the data_file
        simulation = Simulation.objects.create(name=simulation_name, data_file=my_file.name)
        simulation.save()

        filename = os.path.join(settings.MEDIA_ROOT, "simulation_files", simulation.getfilename())
        file_obj = open(filename, 'r')
        dictreader = csv.DictReader(file_obj)

        ... same as before ...    

which I got from the following question/answer: Python3 subprocess communicate example

This gives me an error

TypeError: 'InMemoryUploadedFile' does not support the buffer interface

I'm new to working with subprocesses and am at a loss for what to try next. I do realize that p.communicate() may not be the best choice here, but I'm hoping that will work to get the basic functionality down and then I can improve it. Suggestions and advice are most appreciated!

Bellerofont
  • 1,081
  • 18
  • 17
  • 16
e-beth
  • 77
  • 1
  • 3
  • 9

1 Answers1

0

Alright, I solved my base issue of uploading a file in a subprocess. The file is saved to /media/simulation_files when the Simulation object is created. For some reason, this doesn't work when the Simulation is saved in the subprocess, so I moved that to the view and saved the object before I create the subprocess.

Then, the Popen only needs the id of the newly created Simulation as an argument, in order to access the whole simulation (including the data file).

Important:

I had to remove the @transaction.atomic decorator otherwise, the Simulation object created in the view will not be accessible in the subprocess.

tl;dr version:

UploadView.py

class NewerUploadView(TemplateView):
    template_name = "../templates/simulation/upload.html"

    def post(self, request):
        if request.method == 'POST':
            if not request.FILES['output_file']:
                return HttpResponseBadRequest("No 'output_file' is provided")
            else:
                sim_name = self.request.POST.get(u"name", None)

                # Create the Simulation here to save the file
                simulation = Simulation.objects.create(name=simulation_name, 
                                                       data_file=fp)
                simulation.save()

                # Get the new Simulation object
                new_sim = Simulation.objects.get(id=sim_id)

                p = Popen(["/.virtualenvs/env/bin/python3.4", "manage.py",
                    "load_simulation", str(new_sim.id)], stdout=PIPE, stderr=PIPE)
                p.communicate()

                # Redirect to appropriate page
                return HttpResponseRedirect(reverse('home.display_simulations'))

load_simulation.py

class Command(BaseCommand):
    help = 'Upload csv data file and save objects in database'

    def add_arguments(self, parser):
        parser.add_argument("sim_id", type=int)

    def handle(self, *args, **options):
        """
        Upload data file
        :param args:
        :param options:
        :return:
        """

        sim_id = options["sim_id"]
        sim = Simulation.objects.get(id=sim_id)
        filename = os.path.join(settings.MEDIA_ROOT, sim.data_file.name)
        file_obj = open(filename, 'r')
        dictreader = csv.DictReader(file_obj)
        line = None

        for line in dictreader:

            location = Location.objects.filter(
                department_code=line['department_code'],
                municipality_code=line['municipality_code'],
            ).first()

            if not location:
                location = Location.objects.create(
                    department=line['department'],
                    department_code=line['department_code'],
                    municipality=line['municipality'],
                    municipality_code=line['municipality_code'],
                )

            Data.objects.create(
                location=location,
                date=line['date'].split(" ")[0],  # Assuming "YYYY-MM-DD HH:MM:SS"
                simulation=sim,
                value_low=line['value_low'],
                value_mid=line['value_mid'],
                value_high=line['value_high'],
            )
        sim.is_uploaded = True
        sim.save()
        print("DONE WITH LOAD SIMULATION!")
e-beth
  • 77
  • 1
  • 3
  • 9