So I am successfully storing a complex object (non-model) in my session in development. I've tried every session engine and cache type and they are all working in development (Pycharm). However, when I move the code to production, while no error are thrown, the session losses the object.
Here is the method I use to set the session object:
def instantiate_command_object(request):
try:
ssc = request.session['specimen_search_criteria']
logger.debug('found ssc session variable')
except KeyError:
logger.debug('failed to find ssc session variable')
ssc = SpecimenSearchCommand()
return ssc
Then in a method that runs asynchronously via an ajax call I start making changes to the object in the session:
def ajax_add_collection_to_search(request):
ssc = instantiate_command_object(request)
collection_id = request.GET.get('collection')
collection = Collection.objects.get(pk=collection_id)
if collection and collection not in ssc.collections:
ssc.collections.append(collection)
# save change to session
request.session['specimen_search_criteria'] = ssc
# refresh search results
ssc.search()
return render(request, '_search.html')
All this works as far as it goes. However, if I then refresh the browser, the session is lost. Here is a snippet from the template:
{% with criteria=request.session.specimen_search_criteria %}
<div class="search-criteria" id="search-criteria">
<div class="row">
Sesssion:
{{ request.session }}<br/>
Search:
{{ request.session.specimen_search_criteria }}<br/>
Created:
{{ request.session.specimen_search_criteria.key }}<br/>
Collections:
{{ request.session.specimen_search_criteria.collections }}<br/>
Again, in development I can refresh all day and the same object will be returned. In production, it will either create a new object or occasionally will return a previously created copy.
A few relevant items:
The production server is running Apache httpd with mod_wsgi. I've tried memcached, databasecache, etc. the behavior remains the same. Always works in development, never in production.
I've tried it with
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
and without. I can see the session info in the database and when I unpickle it it just seems to be pointing to a location in memory for the complex object.
I'm guessing this might have something to do with running in a multi-user environment, but again, I'm not using locmem and I've tried all of the caching approaches to no effect.
To be clear, the session itself seems to be fine, I can store a string or other simple item in it and it will stick. It's the complex object within the session that seems to be getting lost.
Edit: I might also point out that if I refresh the browser immediately following the return of the search criteria it will actually return successfully. Anything more than about a second and it will disappear.
Edit (adding code of SpecimenSearchCommand):
class SpecimenSearchCommand:
def __init__(self):
pass
created = datetime.datetime.now()
key = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(6))
jurisdictions = []
taxa = []
strata = []
collections = []
chrons = []
has_images = False
query = None # The active SQL query, not the actual result records
page_size = 50
current_page = 1
sort_order = 'number'
results = [] # Page of results from paginator
def is_empty(self):
if len(self.jurisdictions) == 0 and len(self.taxa) == 0 and len(self.strata) == 0 and \
len(self.collections) == 0 and len(self.chrons) == 0 and self.has_images is False:
return True
else:
return False
def get_results(self):
paginator = Paginator(self.query, self.page_size)
try:
self.results = paginator.page(self.current_page)
except PageNotAnInteger:
self.results = paginator.page(1)
except TypeError:
return []
except EmptyPage:
self.results = paginator.page(paginator.num_pages)
return self.results
def get_results_json(self):
points = []
for s in self.results:
if s.locality.latitude and s.locality.longitude:
points.append({"type": "Feature",
"geometry": {"type": "Point",
"coordinates": [s.locality.longitude, s.locality.latitude]},
"properties": {"specimen_id": s.id,
"sci_name": s.taxon.scientific_name(),
"cat_num": s.specimen_number(),
"jurisdiction": s.locality.jurisdiction.full_name()}
})
return json.dumps({"type": "FeatureCollection", "features": points})
def search(self):
if self.is_empty():
self.query = None
return
query = Specimen.objects.filter().distinct().order_by(self.sort_order)
if len(self.taxa) > 0:
query = query.filter(taxon__in=get_hierarchical_search_elements(self.taxa))
if len(self.jurisdictions) > 0:
query = query.filter(locality__jurisdiction__in=get_hierarchical_search_elements(self.jurisdictions))
if len(self.strata) > 0:
query = query.filter(stratum__in=get_hierarchical_search_elements(self.strata))
if len(self.chrons) > 0:
query = query.filter(chron__in=get_hierarchical_search_elements(self.chrons))
if len(self.collections) > 0:
query = query.filter(collection__in=get_hierarchical_search_elements(self.collections))
if self.has_images:
query = query.filter(images__isnull=False)
self.query = query
return
def get_hierarchical_search_elements(elements):
search_elements = []
for element in elements:
search_elements = set().union(search_elements, element.get_descendants(True))
return search_elements