Django: session_key is an empty string - python

I'm using an analytics platform to track user interactions, and want to associate events by session id so I can track individual users' behaviors. So, I am using request.session.session_key as the user id I'm passing the event logging function. I don't use the session for anything else, and am not saving or modifying variables. I did set up middleware correctly, so far so good.
However, when I check the analytics platform, I see that the id is often (but not always) blank. I looked up some answers, and thought I could solve this by running request.session.save() before accessing the session_key. However, I can't run .save() because I'm not actually modifying any session variables, so Django throws an error.
What do I need to do to make sure the session always has an id?
Some code from my very simple get view:
def get(self, req: HttpRequest):
form = self.form_class(None)
# this is where I tried req.session.save()
mixpanel.track(
req.session.session_key,
"Form Page Displayed",
{"path": req.path, "route": req.method},
)
return render(req, self.template_name, {"form": form})

Related

Prevent repopulation and/or resubmit of Django form after using the back button

The Problem
We have the following setup.
Pretty standard Django class based view (inherits from CreateView, which is what I'll call it form now on).
After a successful POST and form validation, the object is created, and the user is redirect_to'd the DetailView of the created record.
Some users decide that they are not happy with the data they entered. They press the back button.
The HTML generated by the CreateView is fetched form browser cache, and repopulated with the data they entered.
To the user, this feels like an edit, so they change the data and submit again.
The result is 2 records, with minor differences.
What have we tried?
At first I thought the Post-Redirect-Get (PRG) pattern that Django uses was supposed to prevent this. After investigating, it seems that PRG is only meant to prevent the dreaded "Do you want to resubmit the form?" dialog. Dead end.
After hitting the back button, everything is fetched from cache, so we have no chance of interacting with the user from our Django code. To try and prevent local caching, we have decorated the CreateView with #never_cache. This does nothing for us, the page is still retrieved form cache.
What are we considering?
We are considering dirty JavaScript tricks that do an onLoad check of window.referrer, and a manual clean of the form and/or notice to user if the referrer looks like the DetailView mentioned earlier. Of course this feel totally wrong. Then again, so do semi-duplicate records in our DB.
However, it seems so unlikely that we are the first to be bothered by this that I wanted to ask around here on StackOverflow.
Ideally, we would tell the browser that caching the form is a big NO, and the browser would listen. Again, we already use #never_cache, but apparently this is not enough. Happens in Chrome, Safari and Firefox.
Looking forward to any insights! Thanks!
Maybe don't process the POST request when it's coming from a referrer other than the same page?
from urllib import parse
class CreateView(...):
def post(self, *args, **kwargs):
referer = 'HTTP_REFERER' in self.request.META and parse.urlparse(self.request.META['HTTP_REFERER'])
if referer and (referer.netloc != self.request.META.get('HTTP_HOST') or referer.path != self.request.META.get('PATH_INFO')):
return self.get(*args, **kwargs)
...
I know I'm late to this party but this may help anybody else looking for an answer.
Having found this while tearing my hair out over the same problem, here is my solution using human factors rather than technical ones. The user won't use the back button if after submitting from a CreateView, he ends up in an UpdateView of the newly created object that looks exactly the same apart from the title and the buttons at the bottom.
A technical solution might be to create a model field to hold a UUID and create a UUID passed into the create form as a hidden field. When submit is pressed, form_valid could check in the DB for an object with that UUID and refuse to create what would be a duplicate (unique=True would enforce that at DB level).
Here's example code (slightly redacted to remove stuff my employer might not want in public). It uses django-crispy-forms to make things pretty and easy. The Create view is entered from a button on a table of customers which passes the customer account number, not the Django id of its record.
Urls
url(r'enter/(?P<customer>[-\w]+)/$', JobEntryView.as_view(), name='job_entry'),
url(r'update1/(?P<pk>\d+)/$', JobEntryUpdateView.as_view(), name='entry_update'),
Views
class JobEntryView( LoginRequiredMixin, CreateView):
model=Job
form_class=JobEntryForm
template_name='utils/generic_crispy_form.html' # basically just {% crispy form %}
def get_form( self, form_class=None):
self.customer = get_object_or_404(
Customer, account = self.kwargs.get('customer','?') )
self.crispy_title = f"Create job for {self.customer.account} ({self.customer.fullname})"
return super().get_form( form_class)
def form_valid( self, form): # insert created_by'class
#form.instance.entered_by = self.request.user
form.instance.customer = self.customer
return super().form_valid(form)
def get_success_url( self):
return reverse( 'jobs:entry_update', kwargs={'pk':self.object.pk, } )
# redirect to this after entry ... user hopefully won't use back because it's here already
class JobEntryUpdateView( LoginRequiredMixin, CrispyCMVPlugin, UpdateView):
model=Job
form_class=JobEntryForm
template_name='utils/generic_crispy_form.html'
def get_form( self, form_class=None):
self.customer = self.object.customer
self.crispy_title = f"Update job {self.object.jobno} for {self.object.customer.account} ({self.object.customer.fullname})"
form = super().get_form( form_class)
form.helper[-1] = ButtonHolder( Submit('update', 'Update', ), Submit('done', 'Done', ), )
return form
def get_success_url( self):
print( self.request.POST )
if self.request.POST.get('done',None):
return reverse('jobs:ok')
return reverse( 'jobs:entry_update',
kwargs={'pk':self.object.pk, } ) # loop until user clicks Done

UndefinedError : 'user' is undefined

I am currently developing a Flask app (have been for the past year) and I'm encountering a rather... Weird bug. I've got a few files that are always included in my Jinja2 templates (navbars), and they use the users' name and avatar. As a consequence, everytime I render a template, I pass it the user. I recently noticed an error on my prod server :
<img alt="image" class="img-circle" src="{{ user.image }}" style="width: 48px;"/>
File "/usr/local/lib/python2.7/dist-packages/jinja2/environment.py", line 397, in getattr
return getattr(obj, attribute)
jinja2.exceptions.UndefinedError: 'user' is undefined
This is in one of my navbars. The method that renders this template uses this :
#mod.route('/broken_pus', methods=['POST', 'GET'])
def view_broken_pus():
return render_template("view_broken_pus.html", user=g.user, urls_for_active_clients=DeletedURLs.objects()[0].urls_for_active_clients, other_urls=DeletedURLs.objects()[0].other_urls)
As you can see, I pass the user=g.user. I do this on every single view of my website. And it works everywhere, EXCEPT on this method, which is pretty small. I have plenty of other routes like that, with just a render template, so I don't get what's the problem.
I also get it on another method, bigger, which always worked before :
#mod.route('/users/add', methods=['GET', 'POST'])
#requires_roles("admin", "project-leader")
def add():
"""
Method adding a new user.
"""
# We do not use WTForms there since we need custom checkboxes for the role
# Instead we use basic HTML and treat the checkboxes here
if request.method == 'POST':
user = User(name=request.form.get('name'),
email=request.form.get('email'))
l = []
# big switch assignement
user.role = l
try:
user.save()
except errors.NotUniqueError:
flash(u'User %s already in database.' % user.name, 'danger')
return redirect(url_for('home'))
flash(u'User %s registered.' % user.name, 'success')
return redirect(url_for('home'))
return render_template('add_user.html', page=url_for('users.add'), user=g.user, clients=Client.objects())
When I first load the form for adding a user, it works. When I add it, for some reason, I get the error (and the user is not saved in the database).
Since this works perfectly on local, I'm starting to suspect a problem on the production server itself. We use nginx and uwsgi for the app, and I recently implemented some Celery tasks. Got any idea ?
Thanks in advance.
Check out flask source for render_template:
It just calls template.render(context), but after the call to before_render_template.send(app, template=template, context=context)
From this, I think there is some before_render_template handler, that modifies context installed.
To debug this down, I may try to call something like this:
from flask import app
#mod.route('/broken_pus', methods=['POST', 'GET'])
def view_broken_pus():
template = app.jinja_env.get_or_select_template("view_broken_pus.html")
return template.render(dict(
user=g.user,
urls_for_active_clients=DeletedURLs.objects()[0].urls_for_active_clients,
other_urls=DeletedURLs.objects()[0].other_urls,
))
If this will work, I will need to dig in who modifies context in before_render_template slot.
I suspect threading. If g is some sort of global reference then you may need to ensure that it is set up on threading.local or that threading locks are used to ensure that no thread can get hold of g.user before some 'other' thread messes with it.
See how do I make a 2.7 python context manager threadsafe for a way to handle 'globals' without sacrificing thread safety.

Difficulty having Django find database user

I'm writing a web app which has a page for admin tasks. One of the tasks is that the admin users must be able to edit other users details. Alas, I've fallen at quite a simple roadblock.
I've set up a very simple jQuery AJAX Get request, successfully transferring a string to the server and back. This is just background, but not the issue. The issue lies in retrieving other user's objects.
At the moment, with a username I know exists, this code which is accessed in views.py, produces a 500 Internal Server Error.
#login_required
def user_edit_getuser(request):
# Like before, get the request's context.
context = RequestContext(request)
inputname = request.GET['inputNameSend']
user_obj = User.objects.get(inputname)
return HttpResponse(inputname) #later will return a JSON String
get takes keyword arguments only: the key is the field to look up.
user_obj = User.objects.get(username=inputname)
Also, you should probably deal with the possibility that the GET request has no inputNameSend key.
For JS development, you can usually see the error page in the Chrome dev tools/Firebug console in the Network tab.

Retrieve Current User in Django Outside Template

Currently I have a form that was built using the form wizard, the form is processed using a separate script that I wrote. I need to pass the current user (user currently logged in) to this script so that I can run an insert query to my database. Any suggestions on how to do this?
class QuestionWizard(SessionWizardView):
def done(self, form_list, **kwargs):
import process_form
userID = request.user.id
result = process_form.main(form_list,userID)
return render_to_response('done.html', {
#'form_data': [form.cleaned_data for form in form_list],
'data_return': result[0],
})
I believe the form wizard done function only accepts two variables.
It turns out it was a lot simpler than I anticipated. Inside of the done function I added the following:
user_id = self.request.user.id
And then passed the user_id into the process_form function.
Annoying that I overlooked it, but glad that I figured it out.

Django -- Allowing Users To Only View Their Own Page

I'm nearing what I think is the end of development for a Django application I'm building. The key view in this application is a user dashboard to display metrics of some kind. Basically I don't want users to be able to see the dashboards of other users. Right now my view looks like this:
#login_required
#permission_required('social_followup.add_list')
def user_dashboard(request, list_id):
try:
user_list = models.List.objects.get(pk=list_id)
except models.List.DoesNotExist:
raise Http404
return TemplateResponse(request, 'dashboard/view.html', {'user_list': user_list})
the url for this view is like this:
url(r'u/dashboard/(?P<list_id>\d+)/$', views.user_dashboard, name='user_dashboard'),
Right now any logged in user can just change the list_id in the URL and access a different dashboard. How can I make it so a user can only view the dashboard for their own list_id, without removing the list_id parameter from the URL? I'm pretty new to this part of Django and don't really know which direction to go in.
Just pull request.user and make sure this List is theirs.
You haven't described your model, but it should be straight forward.
Perhaps you have a user ID stored in your List model? In that case,
if not request.user == user_list.user:
response = http.HttpResponse()
response.status_code = 403
return response
I solve similiar situations with a reusable mixin. You can add login_required by means of a method decorator for dispatch method or in urlpatterns for the view.
class OwnershipMixin(object):
"""
Mixin providing a dispatch overload that checks object ownership. is_staff and is_supervisor
are considered object owners as well. This mixin must be loaded before any class based views
are loaded for example class SomeView(OwnershipMixin, ListView)
"""
def dispatch(self, request, *args, **kwargs):
self.request = request
self.args = args
self.kwargs = kwargs
# we need to manually "wake up" self.request.user which is still a SimpleLazyObject at this point
# and manually obtain this object's owner information.
current_user = self.request.user._wrapped if hasattr(self.request.user, '_wrapped') else self.request.user
object_owner = getattr(self.get_object(), 'author')
if current_user != object_owner and not current_user.is_superuser and not current_user.is_staff:
raise PermissionDenied
return super(OwnershipMixin, self).dispatch(request, *args, **kwargs)
You need to have some information stored about what list or lists a user can access, and then include that in the user_list lookup. Let's assume the simple case where List has a single owner, a foreign key to the User model. That's a many-to-one relationship between lists and users; no list is owned by more than one user, but a user can have multiple lists. Then you want something like this:
try:
user_list = models.List.objects.get(pk=list_id, owner=request.user)
except models.List.DoesNotExist:
raise Http404
Whether to return 404 or 403 is to some extent a matter of opinion; the definition for 403 says:
If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information available to the client, the status code 404 (Not Found) can be used instead.
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4
If you do return a 404, you can use the django shortcut function get_object_or_404 instead of the explicit try/except - there's nothing wrong with doing it explicitly, but the need is common enough that there's a convenience function to do it.

Categories