def test_basic(self): data = [ (None, None), ('', ''), ('http://example.com/', 'http://example.com/'), ('http://example.com/#foo', 'http://example.com/'), ('http://example.com/?foo=bar', 'http://example.com/'), ('http://example.com:8000/', 'http://example.com/'), ('ftp://foo.bar/', ''), ('chrome://something', 'chrome://something'), ('about:home', 'about:home'), ] for url, expected in data: eq_(clean_url(url), expected)
def _handle_feedback_post(request, locale=None, product=None, version=None, channel=None): """Saves feedback post to db accounting for throttling :arg request: request we're handling the post for :arg locale: locale specified in the url :arg product: None or the Product :arg version: validated and sanitized version specified in the url :arg channel: validated and sanitized channel specified in the url """ if getattr(request, 'limited', False): # If we're throttled, then return the thanks page, but don't # add the response to the db. return HttpResponseRedirect(reverse('thanks')) # Get the form and run is_valid() so it goes through the # validation and cleaning machinery. We don't really care if it's # valid, though, since we will take what we got and do the best we # can with it. Error validation is now in JS. form = ResponseForm(request.POST) form.is_valid() get_data = request.GET.copy() data = form.cleaned_data description = data.get('description', u'').strip() if not description: # If there's no description, then there's nothing to do here, # so thank the user and move on. return HttpResponseRedirect(reverse('thanks')) opinion = models.Response( # Data coming from the user happy=data['happy'], url=clean_url(data.get('url', u'').strip()), description=description, # Pulled from the form data or the url locale=data.get('locale', locale), # Data from mobile devices which is probably only # applicable to mobile devices manufacturer=data.get('manufacturer', ''), device=data.get('device', ''), ) # Add user_agent and inferred data. user_agent = request.META.get('HTTP_USER_AGENT', '') if user_agent: browser = request.BROWSER opinion.user_agent = user_agent opinion.browser = browser.browser opinion.browser_version = browser.browser_version opinion.browser_platform = browser.platform if browser.platform == 'Windows': opinion.browser_platform += ' ' + browser.platform_version # source is src or utm_source source = ( get_data.pop('src', [u''])[0] or get_data.pop('utm_source', [u''])[0] ) if source: opinion.source = source[:100] campaign = get_data.pop('utm_campaign', [u''])[0] if campaign: opinion.campaign = campaign[:100] # If they sent "happy=1"/"happy=0" in the querystring, it will get # picked up by the javascript in the form and we can just drop it # here. get_data.pop('happy', None) platform = u'' if product: # If we have a product at this point, then it came from the # url and it's a Product instance and we need to turn it into # the product.db_name which is a string. product_db_name = product.db_name else: # Check the POST data for the product. product_db_name = data.get('product', '') # For the version, we try the url data, then the POST data. version = version or data.get('version', '') # At this point, we have a bunch of values, but we might be # missing some values, too. We're going to cautiously infer data # from the user agent where we're very confident it's appropriate # to do so. if request.BROWSER != UNKNOWN: # If we don't have a product, try to infer that from the user # agent information. if not product_db_name: product_db_name = models.Response.infer_product(request.BROWSER) # If we have a product and it matches the user agent browser, # then we can infer the version and platform from the user # agent if they're missing. if product_db_name: product = models.Product.objects.get(db_name=product_db_name) if product.browser and product.browser == request.BROWSER.browser: if not version: version = request.BROWSER.browser_version if not platform: platform = models.Response.infer_platform( product_db_name, request.BROWSER) # Make sure values are at least empty strings--no Nones. opinion.product = product_db_name or u'' opinion.version = version or u'' opinion.channel = channel or u'' opinion.platform = platform or u'' opinion.save() # If there was an email address, save that separately. if data.get('email_ok') and data.get('email'): e = models.ResponseEmail(email=data['email'], opinion=opinion) e.save() statsd.incr('feedback.emaildata.optin') # If there's browser data, save that separately. if data.get('browser_ok'): # This comes in as a JSON string. Because we're using # JSONObjectField, we need to convert it back to Python and # then save it. This is kind of silly, but it does guarantee # we have valid JSON. try: browser_data = data['browser_data'] browser_data = json.loads(browser_data) except ValueError: # Handles empty string and any non-JSON value. statsd.incr('feedback.browserdata.badvalue') except KeyError: # Handles the case where it's missing from the data # dict. If it's missing, we don't want to do anything # including metrics. pass else: # If browser_data isn't an empty dict, then save it. if browser_data: rti = models.ResponsePI( data=browser_data, opinion=opinion) rti.save() statsd.incr('feedback.browserdata.optin') if get_data: # There was extra context in the query string, so we grab that # with some restrictions and save it separately. slop = {} # We capture at most the first 20 key/val pairs get_data_items = sorted(get_data.items())[:20] for key, val in get_data_items: # Keys can be at most 20 characters long. key = key[:20] if len(val) == 1: val = val[0] # Values can be at most 20 characters long. val = val[:100] slop[key.encode('utf-8')] = val.encode('utf-8') context = models.ResponseContext(data=slop, opinion=opinion) context.save() statsd.incr('feedback.contextdata.optin') if data['happy']: statsd.incr('feedback.happy') else: statsd.incr('feedback.sad') request.session['response_id'] = opinion.id return HttpResponseRedirect(reverse('thanks'))
def _handle_feedback_post(request, locale=None, product=None, version=None, channel=None): if getattr(request, "limited", False): # If we're throttled, then return the thanks page, but don't # add the response to the db. return HttpResponseRedirect(reverse("thanks")) # Get the form and run is_valid() so it goes through the # validation and cleaning machinery. We don't really care if it's # valid, though, since we will take what we got and do the best we # can with it. Error validation is now in JS. form = ResponseForm(request.POST) form.is_valid() data = form.cleaned_data description = data.get("description", u"").strip() if not description: # If there's no description, then there's nothing to do here, # so thank the user and move on. return HttpResponseRedirect(reverse("thanks")) # Do some data validation of product, channel and version # coming from the url. product = models.Product.get_product_map().get(smart_str(product), u"") # FIXME - validate these better channel = smart_str(channel).lower() version = smart_str(version) # src, then source, then utm_source source = request.GET.get("src", u"") if not source: source = request.GET.get("utm_source", u"") campaign = request.GET.get("utm_campaign", u"") # Most platforms aren't different enough between versions to care. # Windows is. platform = request.BROWSER.platform if platform == "Windows": platform += " " + request.BROWSER.platform_version opinion = models.Response( # Data coming from the user happy=data["happy"], url=clean_url(data.get("url", u"")), description=data["description"].strip(), # Inferred data from user agent prodchan=_get_prodchan(request, product, channel), user_agent=request.META.get("HTTP_USER_AGENT", ""), browser=request.BROWSER.browser, browser_version=request.BROWSER.browser_version, platform=platform, # Pulled from the form data or the url locale=data.get("locale", locale), # Data from mobile devices which is probably only # applicable to mobile devices manufacturer=data.get("manufacturer", ""), device=data.get("device", ""), ) if source: opinion.source = source[:100] if campaign: opinion.campaign = campaign[:100] if product: # If we picked up the product from the url, we use url # bits for everything. product = product or u"" version = version or u"" channel = channel or u"" elif opinion.browser != UNKNOWN: # If we didn't pick up a product from the url, then we # infer as much as we can from the user agent. product = data.get("product", models.Response.infer_product(platform)) version = data.get("version", request.BROWSER.browser_version) # Assume everything we don't know about is stable channel. channel = u"stable" else: product = channel = version = u"" opinion.product = product or u"" opinion.version = version or u"" opinion.channel = channel or u"" opinion.save() # If there was an email address, save that separately. if data.get("email_ok") and data.get("email"): e = models.ResponseEmail(email=data["email"], opinion=opinion) e.save() return HttpResponseRedirect(reverse("thanks"))
def _handle_feedback_post(request, locale=None, product=None, version=None, channel=None): """Saves feedback post to db accounting for throttling :arg request: request we're handling the post for :arg locale: locale specified in the url :arg product: validated and sanitized product slug specified in the url :arg version: validated and sanitized version specified in the url :arg channel: validated and sanitized channel specified in the url """ if getattr(request, 'limited', False): # If we're throttled, then return the thanks page, but don't # add the response to the db. return HttpResponseRedirect(reverse('thanks')) # Get the form and run is_valid() so it goes through the # validation and cleaning machinery. We don't really care if it's # valid, though, since we will take what we got and do the best we # can with it. Error validation is now in JS. form = ResponseForm(request.POST) form.is_valid() get_data = request.GET.copy() data = form.cleaned_data description = data.get('description', u'').strip() if not description: # If there's no description, then there's nothing to do here, # so thank the user and move on. return HttpResponseRedirect(reverse('thanks')) # Do some data validation of product, channel and version # coming from the url. if product: # If there was a product in the url, that's a product slug, so # we map it to a db_name which is what we want to save to the # db. product = models.Product.get_product_map()[product] # src, then source, then utm_source source = get_data.pop('src', [u''])[0] if not source: source = get_data.pop('utm_source', [u''])[0] campaign = get_data.pop('utm_campaign', [u''])[0] # If the product came in on the url, then we only want to populate # the platfrom from the user agent data iff the product specified # by the url is the same as the browser product. platform = u'' if product is None or product == request.BROWSER.browser: # Most platforms aren't different enough between versions to care. # Windows is. platform = request.BROWSER.platform if platform == 'Windows': platform += ' ' + request.BROWSER.platform_version product = product or u'' opinion = models.Response( # Data coming from the user happy=data['happy'], url=clean_url(data.get('url', u'')), description=data['description'].strip(), # Inferred data from user agent user_agent=request.META.get('HTTP_USER_AGENT', ''), browser=request.BROWSER.browser, browser_version=request.BROWSER.browser_version, platform=platform, # Pulled from the form data or the url locale=data.get('locale', locale), # Data from mobile devices which is probably only # applicable to mobile devices manufacturer=data.get('manufacturer', ''), device=data.get('device', ''), ) if source: opinion.source = source[:100] if campaign: opinion.campaign = campaign[:100] if product: # If we picked up the product from the url, we use url # bits for everything. product = product or u'' version = version or u'' channel = channel or u'' elif opinion.browser != UNKNOWN: # If we didn't pick up a product from the url, then we # infer as much as we can from the user agent. product = data.get( 'product', models.Response.infer_product(platform)) version = data.get( 'version', request.BROWSER.browser_version) # Assume everything we don't know about is stable channel. channel = u'stable' else: product = channel = version = u'' opinion.product = product or u'' opinion.version = version or u'' opinion.channel = channel or u'' opinion.save() # If there was an email address, save that separately. if data.get('email_ok') and data.get('email'): e = models.ResponseEmail(email=data['email'], opinion=opinion) e.save() if get_data: # There was extra context in the query string, so we grab that # with some restrictions and save it separately. slop = {} # We capture at most the first 20 key/val pairs get_data_items = sorted(get_data.items())[:20] for key, val in get_data_items: # Keys can be at most 20 characters long. key = key[:20] if len(val) == 1: val = val[0] # Values can be at most 20 characters long. val = val[:100] slop[key.encode('utf-8')] = val.encode('utf-8') context = models.ResponseContext(data=slop, opinion=opinion) context.save() if data['happy']: statsd.incr('feedback.happy') else: statsd.incr('feedback.sad') return HttpResponseRedirect(reverse('thanks'))
def _handle_feedback_post(request, locale=None, product=None, version=None, channel=None): """Saves feedback post to db accounting for throttling :arg request: request we're handling the post for :arg locale: locale specified in the url :arg product: None or the Product :arg version: validated and sanitized version specified in the url :arg channel: validated and sanitized channel specified in the url """ if getattr(request, 'limited', False): # If we're throttled, then return the thanks page, but don't # add the response to the db. return HttpResponseRedirect(reverse('thanks')) # Get the form and run is_valid() so it goes through the # validation and cleaning machinery. We don't really care if it's # valid, though, since we will take what we got and do the best we # can with it. Error validation is now in JS. form = ResponseForm(request.POST) form.is_valid() get_data = request.GET.copy() data = form.cleaned_data description = data.get('description', u'').strip() if not description: # If there's no description, then there's nothing to do here, # so thank the user and move on. return HttpResponseRedirect(reverse('thanks')) opinion = models.Response( # Data coming from the user happy=data['happy'], url=clean_url(data.get('url', u'').strip()), description=description, # Pulled from the form data or the url locale=data.get('locale', locale), # Data from mobile devices which is probably only # applicable to mobile devices manufacturer=data.get('manufacturer', ''), device=data.get('device', ''), ) # Add user_agent and inferred data. user_agent = request.META.get('HTTP_USER_AGENT', '') if user_agent: browser = request.BROWSER opinion.user_agent = user_agent opinion.browser = browser.browser opinion.browser_version = browser.browser_version opinion.browser_platform = browser.platform if browser.platform == 'Windows': opinion.browser_platform += ' ' + browser.platform_version # source is src or utm_source source = (get_data.pop('src', [u''])[0] or get_data.pop('utm_source', [u''])[0]) if source: opinion.source = source[:100] campaign = get_data.pop('utm_campaign', [u''])[0] if campaign: opinion.campaign = campaign[:100] platform = u'' if product: # If we have a product at this point, then it came from the # url and it's a Product instance and we need to turn it into # the product.db_name which is a string. product_db_name = product.db_name else: # Check the POST data for the product. product_db_name = data.get('product', '') # For the version, we try the url data, then the POST data. version = version or data.get('version', '') # At this point, we have a bunch of values, but we might be # missing some values, too. We're going to cautiously infer data # from the user agent where we're very confident it's appropriate # to do so. if request.BROWSER != UNKNOWN: # If we don't have a product, try to infer that from the user # agent information. if not product_db_name: product_db_name = models.Response.infer_product(request.BROWSER) # If we have a product and it matches the user agent browser, # then we can infer the version and platform from the user # agent if they're missing. if product_db_name: product = models.Product.objects.get(db_name=product_db_name) if product.browser and product.browser == request.BROWSER.browser: if not version: version = request.BROWSER.browser_version if not platform: platform = models.Response.infer_platform( product_db_name, request.BROWSER) # Make sure values are at least empty strings--no Nones. opinion.product = product_db_name or u'' opinion.version = version or u'' opinion.channel = channel or u'' opinion.platform = platform or u'' opinion.save() # If there was an email address, save that separately. if data.get('email_ok') and data.get('email'): e = models.ResponseEmail(email=data['email'], opinion=opinion) e.save() statsd.incr('feedback.emaildata.optin') # If there's browser data, save that separately. if data.get('browser_ok'): # This comes in as a JSON string. Because we're using # JSONObjectField, we need to convert it back to Python and # then save it. This is kind of silly, but it does guarantee # we have valid JSON. try: browser_data = data['browser_data'] browser_data = json.loads(browser_data) except ValueError: # Handles empty string and any non-JSON value. statsd.incr('feedback.browserdata.badvalue') except KeyError: # Handles the case where it's missing from the data # dict. If it's missing, we don't want to do anything # including metrics. pass else: # If browser_data isn't an empty dict, then save it. if browser_data: rti = models.ResponsePI(data=browser_data, opinion=opinion) rti.save() statsd.incr('feedback.browserdata.optin') if get_data: # There was extra context in the query string, so we grab that # with some restrictions and save it separately. slop = {} # We capture at most the first 20 key/val pairs get_data_items = sorted(get_data.items())[:20] for key, val in get_data_items: # Keys can be at most 20 characters long. key = key[:20] if len(val) == 1: val = val[0] # Values can be at most 20 characters long. val = val[:100] slop[key.encode('utf-8')] = val.encode('utf-8') context = models.ResponseContext(data=slop, opinion=opinion) context.save() statsd.incr('feedback.contextdata.optin') if data['happy']: statsd.incr('feedback.happy') else: statsd.incr('feedback.sad') request.session['opinion_id'] = opinion.id return HttpResponseRedirect(reverse('thanks'))
def _handle_feedback_post(request, locale=None, product=None, version=None, channel=None): """Saves feedback post to db accounting for throttling :arg request: request we're handling the post for :arg locale: locale specified in the url :arg product: validated and sanitized product slug specified in the url :arg version: validated and sanitized version specified in the url :arg channel: validated and sanitized channel specified in the url """ if getattr(request, 'limited', False): # If we're throttled, then return the thanks page, but don't # add the response to the db. return HttpResponseRedirect(reverse('thanks')) # Get the form and run is_valid() so it goes through the # validation and cleaning machinery. We don't really care if it's # valid, though, since we will take what we got and do the best we # can with it. Error validation is now in JS. form = ResponseForm(request.POST) form.is_valid() data = form.cleaned_data description = data.get('description', u'').strip() if not description: # If there's no description, then there's nothing to do here, # so thank the user and move on. return HttpResponseRedirect(reverse('thanks')) # Do some data validation of product, channel and version # coming from the url. if product: # If there was a product in the url, that's a product slug, so # we map it to a db_name which is what we want to save to the # db. product = models.Product.get_product_map()[product] # src, then source, then utm_source source = request.GET.get('src', u'') if not source: source = request.GET.get('utm_source', u'') campaign = request.GET.get('utm_campaign', u'') # If the product came in on the url, then we only want to populate # the platfrom from the user agent data iff the product specified # by the url is the same as the browser product. platform = u'' if product is None or product == request.BROWSER.browser: # Most platforms aren't different enough between versions to care. # Windows is. platform = request.BROWSER.platform if platform == 'Windows': platform += ' ' + request.BROWSER.platform_version product = product or u'' opinion = models.Response( # Data coming from the user happy=data['happy'], url=clean_url(data.get('url', u'')), description=data['description'].strip(), # Inferred data from user agent prodchan=_get_prodchan(request, product, channel), user_agent=request.META.get('HTTP_USER_AGENT', ''), browser=request.BROWSER.browser, browser_version=request.BROWSER.browser_version, platform=platform, # Pulled from the form data or the url locale=data.get('locale', locale), # Data from mobile devices which is probably only # applicable to mobile devices manufacturer=data.get('manufacturer', ''), device=data.get('device', ''), ) if source: opinion.source = source[:100] if campaign: opinion.campaign = campaign[:100] if product: # If we picked up the product from the url, we use url # bits for everything. product = product or u'' version = version or u'' channel = channel or u'' elif opinion.browser != UNKNOWN: # If we didn't pick up a product from the url, then we # infer as much as we can from the user agent. product = data.get( 'product', models.Response.infer_product(platform)) version = data.get( 'version', request.BROWSER.browser_version) # Assume everything we don't know about is stable channel. channel = u'stable' else: product = channel = version = u'' opinion.product = product or u'' opinion.version = version or u'' opinion.channel = channel or u'' opinion.save() # If there was an email address, save that separately. if data.get('email_ok') and data.get('email'): e = models.ResponseEmail(email=data['email'], opinion=opinion) e.save() if data['happy']: statsd.incr('feedback.happy') else: statsd.incr('feedback.sad') return HttpResponseRedirect(reverse('thanks'))
def _handle_feedback_post(request, locale=None, product=None, version=None, channel=None): """Saves feedback post to db accounting for throttling :arg request: request we're handling the post for :arg locale: locale specified in the url :arg product: None or the Product :arg version: validated and sanitized version specified in the url :arg channel: validated and sanitized channel specified in the url """ if getattr(request, 'limited', False): # If we're throttled, then return the thanks page, but don't # add the response to the db. return HttpResponseRedirect(reverse('thanks')) # Get the form and run is_valid() so it goes through the # validation and cleaning machinery. We don't really care if it's # valid, though, since we will take what we got and do the best we # can with it. Error validation is now in JS. form = ResponseForm(request.POST) form.is_valid() get_data = request.GET.copy() data = form.cleaned_data description = data.get('description', u'').strip() if not description: # If there's no description, then there's nothing to do here, # so thank the user and move on. return HttpResponseRedirect(reverse('thanks')) opinion = models.Response( # Data coming from the user happy=data['happy'], url=clean_url(data.get('url', u'')), description=description, # Pulled from the form data or the url locale=data.get('locale', locale), # Data from mobile devices which is probably only # applicable to mobile devices manufacturer=data.get('manufacturer', ''), device=data.get('device', ''), ) # Add user_agent and inferred data. user_agent = request.META.get('HTTP_USER_AGENT', '') if user_agent: browser = request.BROWSER opinion.user_agent = user_agent opinion.browser = browser.browser opinion.browser_version = browser.browser_version opinion.browser_platform = browser.platform if browser.platform == 'Windows': opinion.browser_platform += ' ' + browser.platform_version # source is src or utm_source source = ( get_data.pop('src', [u''])[0] or get_data.pop('utm_source', [u''])[0] ) if source: opinion.source = source[:100] campaign = get_data.pop('utm_campaign', [u''])[0] if campaign: opinion.campaign = campaign[:100] platform = u'' # Figure out product, version, channel and platform. We either get # them from the url, from the user_agent, or we don't set them at # all. if product: # If there was a product in the url, then we get a Product # instance as an argument and we want the db_name from that. product = product.db_name # FIXME: We should be able to "match" the product with the # user agent browser and if they're the same, then set # platform == browser_platform. However, it's tricky since # our "products" are "interesting". elif opinion.browser != UNKNOWN: # If we didn't pick up a product from the url, then we # infer as much as we can from the user agent. product = data.get( 'product', models.Response.infer_product(opinion.browser_platform)) version = data.get( 'version', request.BROWSER.browser_version) # Assume everything we don't know about is stable channel. channel = u'stable' # Try to infer the platform from the product. if product and not platform: platform = models.Response.infer_platform(product, request.BROWSER) opinion.product = product or u'' opinion.version = version or u'' opinion.channel = channel or u'' opinion.platform = platform or u'' opinion.save() # If there was an email address, save that separately. if data.get('email_ok') and data.get('email'): e = models.ResponseEmail(email=data['email'], opinion=opinion) e.save() if get_data: # There was extra context in the query string, so we grab that # with some restrictions and save it separately. slop = {} # We capture at most the first 20 key/val pairs get_data_items = sorted(get_data.items())[:20] for key, val in get_data_items: # Keys can be at most 20 characters long. key = key[:20] if len(val) == 1: val = val[0] # Values can be at most 20 characters long. val = val[:100] slop[key.encode('utf-8')] = val.encode('utf-8') context = models.ResponseContext(data=slop, opinion=opinion) context.save() if data['happy']: statsd.incr('feedback.happy') else: statsd.incr('feedback.sad') return HttpResponseRedirect(reverse('thanks'))