2

Is it possible to block a code in view from execution by all users accessing the view while it is being executed by a single user? Kind of single-thread view.

I need it because i generate the python executable with pyinstaller in this view and passing a username into the executable through the config file.

For example:

class CliConfig(APIView):

    def get(self, request, format=None):
        try:
            config['DEFAULT']['username'] = request.user

            #make a build with pyinstaller
            bin_file = open(*generated filepath *, 'rb')
            response = Response(FileWrapper(bin_file), content_type='application/octet-stream')
            response['Content-Disposition'] = 'attachment; filename="%s"' % '*filename*'
            return response
        finally:
            config['DEFAULT']['username'] = ''

So, basically what i want is to generate a python executable which will have a unique username it it's settings, in django rest framwork APIView. I don't see other approach except passing the username through the settings file. If there is a way - would appreciate an advise.

python 3.6.5, djangorestframework==3.8.2, pyinstaller==3.3.1

user1935987
  • 3,136
  • 9
  • 56
  • 108
  • How is generated filepath created? How do you feel about having a unique one per request rather than preventing other people from generating an installer? EDIT: Ah, I see the problem. You want to lock around modifying your `config['DEFAULT']['username']`. – wholevinski Jul 18 '18 at 17:06
  • 1
    Why not create a differently named config file for each request? The tempfile module may be helpful for this. – samfrances Jul 18 '18 at 17:06
  • in case of differently named config the build will not be aware of new config name – user1935987 Jul 18 '18 at 19:07
  • Presumably you have multiple worker processes running in parallell. If you have only a single process and a single thread (like in the django development runserver) only one request will be processed at a time, and you are guaranteed no race conditions. If you have multiple wsgi worker processes, modifying `settings` in one process will not have any effect in other processes. You have to create a lock that is shared between workers. For example a database lock. https://docs.djangoproject.com/en/2.0/ref/models/querysets/#select-for-update – Håken Lid Jul 21 '18 at 15:59
  • I believe this question is a duplicate of this (can't submit this as a duplicate, due to the bounty) https://stackoverflow.com/questions/1123200/how-to-lock-a-critical-section-in-django If you don't need this to scale to multiple servers, the simplest solution is to use either a database lock or a file system lock. – Håken Lid Jul 21 '18 at 16:13

2 Answers2

5

Why do you store the username in the config? Shouldn't this generation be per user?

Anyhow it is not good practice to do time-consuming tasks inside views.
Use Celery for long-term tasks that will generate executables and will accept any variables without stopping Django. At the end of this task Celery can send executable to email or something.

from celery import Celery

app = Celery('hello', broker='amqp://guest@localhost//')


@app.task
def generate_executable(username):
    # make a build with pyinstaller with username

    bin_file = open(*generated filepath *, 'rb')
    response = Response(FileWrapper(bin_file), content_type='application/octet-stream')
    response['Content-Disposition'] = 'attachment; filename="%s"' % '*filename*'

    # send email and/or returns as task result

    return response


class CliConfig(APIView):

    def get(self, request, format=None):
        task = generate_executable(request.user)
        task.delay()

        return Response({"status": "started", "task_id": task.task_id})
wowkin2
  • 5,895
  • 5
  • 23
  • 66
2

Take a look at Django-channels project. channels give abstraction to developer and support many protocols including HTTP. You can rewrite critical pages to channels consumers. So you will be able to write asynchronious code in block manner using async/await constructions. https://channels.readthedocs.io/en/latest/topics/consumers.html#asynchttpconsumer

Also you can show lock status by Javascript and use Redis lock mechanism: https://redis.io/topics/distlock

# <settings_dir>/settings.py
ASGI_APPLICATION = "my_app.routing.application"


# my_app/routing.py
from channels.routing import ProtocolTypeRouter

from .consumers import LockConsumer


application = ProtocolTypeRouter({
    "websocket": AuthMiddlewareStack(
        URLRouter([
            url("^ws/lock$", LockConsumer),
        ])
    ),
})


# my_app/consumers.py
class LockConsumer(JsonWebsocketConsumer):

    def connect(self):
        if self.scope['user'].is_authenticated:
            self.accept()
        else:
            self.close(code='unauthorized')
        pass

    def receive_json(self, content, **kwargs):
       # process lock procedures
       # try to lock by REDIS API
       # if locked -> show alarm to user by javascript 


# template, see fuul doc here http://channels.readthedocs.io/en/latest/javascript.html
...
<script src="{% static "websocketbridge.js" %}"></script>
....
Pavel Minenkov
  • 368
  • 2
  • 9