Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: How to "fork" a session in django

I have a server which feeds several applications.

Please imagine that I'm a user registered in 2 or more of those applications and I use one different login info when accessing each one.

Now, as that user, I use the same browser with different tabs to access those applications... Logging the first time (for the 1rst application) everything goes as intended, but when I access the 2nd application (as the second user), that request will access the same request.session object. When I invoke the login (from the auth framework) the current user will be compared with the user in the actual request.session (request.session[SESSION_KEY] != user.id) and request.session.flush() will be called.

This means that I will loose all of the request.session content for the user that accessed the 1st application, and that same user request.session will be "marked" to be the second user request.session from that point.

What I wanted, in this situation, was to have a function/method that permits to create a new request.session for the second user, leaving the original as it is.

Edited after the first answer: First of all, thank you for the answer. I was trying not to detail too much in order to avoid too oriented answers, but now I think I should do it:

Ok, I've called "it" applications before, but in truth, my project serves requests in order to offer the same final "product" (a game, for instance). (I have several django applications inside my project. Each one with specific orientations and backend's depending of business considerations applied.)

It would be more detailed if I told that I have different entry points URL's, use the correct business backend in order to process the request and retrieve a game.

My main URL is the same (namespace) and my project has only one settings file.

enter image description here

like image 588
samthgreat_pt Avatar asked Nov 04 '22 15:11

samthgreat_pt


1 Answers

There may be several answers to your question depending on whether you are ready to change the "use case" or not:

a) You can't change the use case: it's not possible because one Django session is bound to a browser session be it several browser windows instance or tabs.

b) You can change the use case: The user can still achieve this using several browsers (or profiles (or private browsing mode in chrome/chromium)) without any modification to your code.

c) You can implement an "user"-switch feature in your website that allow user to have several active profile in the same session in different windows, it's similar in purpose to github organization-switch feature or facebook page/organization-switch but you can have several user's profiles in several tabs which is not the case on github or facebook.

To achieve c) you need to have a "SubProfile" model instances attached to your "User" model, and activate the right SubProfile on each incoming request based on query string parameters and persist subprofile information across requests.

1) I guess you already have something like that Subprofile a model with a foreign key to django.contrib.auth.models.User, you may also have a view that allow an user to change its subprofile. In order to make subprofile-switch work, it needs to persist the information in the current tab session which subprofile it is using, for that it needs to add a parameter in the query string because it's the only place bound the tab and not the user-session. For instance "subprofile=123". You need to properly validate the subprofile with a form et al., the view looks like this:

def select_subprofile(request):
   if request.method == 'POST':
      form = SubProfileSelectForm(request)
      if form.is_valid():   
          subprofile = form.cleaned_data['subprofile']
          url = '%s?subprofile' % (reverse('homepage'), subprofile) 
          return redirect(url)  # the redirect is something like '/homepage?subprofile=123'
   else:
      form = SubProfileSelectForm()
   return render(request, 'myapp/subprofile_select.html', {'form':form})

This view can be the first page of each game.

2) After that, you need to retrieve the subprofile of the user for the current tab. For this matter we will use the query string in a middleware (look for howtos on SO and example middlewares bundled with Django if you don't know what it is) can be used to attach current SubProfile instance to request.user. The middleware will for each incoming request attach the SubProfile instance corresponding to the current subprofile information found in query string to current user object, the middleware looks like this:

class SubProfileMiddleware(object):

    def process_request(self, request):
        subprofile = request.GET.get('subprofile', None)
        if subprofile:
            # it's important to check for user here or malicious users will be
            # able to use Subprofiles of other users
            subprofile = Subprofile.objects.filter(user=request.user, id=subprofile)
            # This can also be written 
            # subprofile = request.user.subprofile_set.filter(id=subprofile)
            if not subprofile:
                # this is a malicious user
                raise Http403
            else:
                request.user.subprofile = subprofile
        else:
             # set default subprofile
             request.user.subprofile = self.user.default_subprofile

This way you have access in every view of your app to a SubProfile instance on subprofile attribute of request.user. If there is a valid query string subprofile=123 the user will have these subprofile active, if not it's the default subprofile.

Say your application is an application with Organization models each of which instances have walls, on which user's can post message using a subprofile, the function to post a message on a wall has the following signature post_on_organization_wall(subprofile, message, organization), the view that use this function will look like this:

def organization_wall_post(request, organization):
    organization = Organization.objects.get_object_or_404(organization)
    if request.method == 'POST':
        form = MessageForm(request.POST)
        if form.is_valid():
             post_on_organization_wall(request.user.subprofile, message, organisation)
    else:
        form = MessageForm()
    return render(request, 'organisation/wall_post.html', {'form': form})

3) Now you need to persist subprofile information across requests. Simplest way to do that is replace everycall to {% url %} to your own url template tag which checks for the request query string presence of subprofile key and add it to the requested url. You can reuse the code of Django's url template tag.

like image 154
amirouche Avatar answered Nov 09 '22 14:11

amirouche