Django's admin 'change' view not accepting kwargs={'object_id':'blah} - django

I'm a little thrown by this. I have the following code, which works perfectly well:
urlresolvers.reverse('admin:cards_card_change', args=([92]))
To further my understanding, I wanted to try rewriting the line as:
urlresolvers.reverse('admin:cards_card_change', kwargs={'object_id':92})
as seemingly suggested by the documentation on reversing admin views (I'm using Django 1.4).
However, this doesn't seem to match anything at all. Why not? I tried looking in the django source code for answers, but couldn't find the view used for change, so links to the relevant module there would be really helpful as well!

The urlpattern of the change view is in admin/options.py:
url(r'^(.+)/$',
wrap(self.change_view),
name='%s_%s_change' % info),
You could find that it dispatches a request to the change_view method of the ModelAdmin instance. The change_view method also resides in admin/options.py:
def change_view(self, request, object_id, form_url='', extra_context=None):
...
It does accept a parameter object_id.
The reason of the missing match of reverse is that the urlpattern above does not accept named parameter, if you change it to something like
url(r'^(?P<object_id>.+)/$',
wrap(self.change_view),
name='%s_%s_change' % info),
The urlresolvers.reverse('admin:cards_card_change', kwargs={'object_id':92}) should work.
I've no idea whether it was intended to avoid some edge cases or it's just a bug and there's already a ticket fixing this. I'll check it later.

I think this is a python syntax issue.
The thing is that **kwargs cannot be taken as *args.
The function you're trying to call has a first argument (object_id) which is positional and cannot be used as a named argument. (Someone tell me if I'm wrong)
For you, object_id is a positional argument, and thus will be ignored if used in kwargs
If you want your view function to accept object_id also in kwargs, you have to change it with for example
my_id = kwargs.get('object_id', args[0])

Related

Force django querystring in url

Say you have a url like this:
/cats/?filter=kittens
Is it possible to build a django url pattern that forces the use of the querystring?
Currently I have:
url(r'^/cats/$', views.CatsListView.as_view(), name='cats')
Now I want to add the querystring and get a different view, something like this:
url(r'^/cats/?filter=(?P<filter>.+?)$', views.CatsFilteredListView.as_view(), name='cats-filtered')
Is it possible to do something like this and still keep the querystring in the GET parameter of the request?
Remember that this is just a testcase, I, and you should too, know that filtering like probably this isn't the way to go..
Short answer: no, it's not possible. Django url patterns match only on the "path" componant of the url, period.
No, it's not possible to do this. If you really need two separate views, you can write a view that dispatches the correct view.
def cat_list_view(request, *args, **kwargs):
if 'filter' in request.GET:
return cat_list_filter_view(request, *args, **kwargs)
else:
return cat_list_unfiltered_view(request, *args, **kwargs)
However, for your example of CatsListView and CatsFilteredListView there is probably a better way to combine the views. For example you might be able to do the filtering in the get_queryset method.
While it is true that you cannot manipulate the path component like that, you can pass a dictionary in. It's a 3rd unnamed argument.
This approach can be useful if you want to use the same view for multiple resources, and pass data to configure its behaviour in each case (below we supply a different template in each case).
path('url/', views.my_reused_view, {'my_template_name': 'some_path'}, name='aurl'),
path('anotherurl/', views.my_reused_view, {'my_template_name': 'another_path'}, name='anotherurl'),
Note: Both extra options and named captured patterns are passed to the view as named arguments. If you use the same name for both a captured pattern and an extra option then only the captured pattern value will be sent to the view (the value specified in the additional option will be dropped).
Courtesy of https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Generic_views

how show personalized error with get_object_or_404

I would like to know how to show personalized errors with the get_object_or_404 method. I don't want the normal Http404 pages, but I want to display a custom message with the message: the result is none.
Thanks :)
The get_object_or_404() is essentially a simple 5-line function. Unless you have some specific reason for using it, just do:
try:
instance = YourModel.objects.get(pk=something)
except YourModel.DoesNotExist:
return render_to_response('a_template_with_your_error_message.html')
If for whatever reason you have to use get_object_or_404(), you can try putting it in a try: ... except Http404: ... block, but I honestly can't think of a plausible reason for that.
As stated by michael, when using get_object_or_404 you cannot customize the message given on http 404. The message provided in DEBUG does offer information about the exception however: "No MyModel matches the given query."
Check out the doc on this. There are three arguments: Model, *args, and **kwargs. The last two are used to build an argument for either get() or filter() on the Model.
The reason I wrote, however, is to address the question of why we would want to use a helper function such as get_object_or_404() instead of, for example, catching it with an exception like Model.DoesNotExist.
The later solution couples the view layer to the model layer. In an effort to relax this coupling we can take advantage of the controlled coupling offered in the django.shortcuts module[1].
And why exactly aren't you using your server's capeability to do just that?
get_object_or_404() is redirecting to the default 404 page right?
If you are on debug mode you won't see it but when deployed django will just refer to the server's 404 html page.
That can't be done with that shortcut. It will only raise a Http404 exception. Your best bet is a try catch if you want full control. Eg.
try:
obj = Model.objects.get(pk = foo)
except:
return HttpResponseRedirect('/no/foo/for/you')
#or
return render_to_response ...

Django's list_details views saving queryset to memory (not updating)?

I have a custom model manager that looks like this:
class MyManager(models.Manager)
def get_query_set(self):
'''Only get items that are 'approved' and have a `pub_date` that is in
the past. Ignore the rest.'''
queryset = super(MyManager, self).get_query_set()
queryset = queryset.filter(status__in=('a',))
return queryset.filter(pub_date__lte=datetime.utcnow())
And this works well enough; however, I have a problem using Django's generic.list_detail views object_detail and object_list: the queryset seems to be only loading once and, because of this, it isn't fetching the items it should be because, I assume, the utcnow() time has been called only once (when it first loaded).
I assume this is intentional and meant as a performance boost - however, it means that video's show up elsewhere on the site (in places I am not in a object_detail view) before they are available in an object_detail view (see urls.py below). This is leading to 404s ...
Any ideas ? Or do I have to write my own custom views to avoid this ?
Thanks!
urls.py
url(r'^video/(?P<object_id>\d+)$',
list_detail.object_detail,
{ 'queryset': Video.objects.all(), },
name='video_detail',
),
It is not a problem of cache: as you do it now, the queryset definition is evaluated once, while parsing urls, and then, it is never evaluated again.
Solution is actually pretty simple and described in the online documentation: Complex filtering with wrapper functions: just create a small custom view, that will simply call the generic view.
I am actually using a similar solution quite a lot and I feel it quite comfortable.
By the way, a small side note, for this case I would suggest not using a custom manager, and go back instead on a normal filtering.
Try correcting urls.py to:
url(r'^video/(?P<object_id>\d+)$',
list_detail.object_detail,
{ 'queryset': Video.objects.all, }, # here's the difference
name='video_detail',
)
Edit:
If this fail, try apply similar technique(passing callable instead of calling it) to filter():
return queryset.filter(pub_date__lte=datetime.utcnow)
I have an almost identical model Manager to thornomad, and the same problem with generic views.
I have to point out that neither of the suggestions above work:
doing Video.objects.all without parentheses gives an error
doing queryset.filter(pub_date__lte=datetime.utcnow), again without the parentheses, does not give an error but does not fix the problem
I have also tried another way, which is to use a lambda to return the queryset, eg:
qs = lambda *x: Video.objects.all()
url(r'^video/(?P<object_id>\d+)$',
list_detail.object_detail,
{ 'queryset': qs(), },
name='video_detail',
),
...it didn't work either and I can see now I must have been desperate to think it would :)
lazy_qs = lambda *x: lazy(Post.live_objects.all, QuerySet)
blog_posts = {
'queryset': lazy_qs(),
...doesn't work either (gives an error) as utils.functional.lazy doesn't know how to convert the result to a QuerySet properly, as best I can tell.
I think Roberto's answer of wrapping the generic view is the only one that will help.
The django docs should be amended to point out the limitations of the queryset used by generic views (currently the docs have a special note to tell you everything will be okay!)

Reverse Not Found: Sending Request Context in from templates

N.B This question has been significantly edited before the first answer was given.
Hi,
I'm fairly new to django, so apologies if I'm missing something obvious.
I've got a urls.py file that looks like this:
urlpatterns = patterns(
'',
(r'^$', 'faros.lantern.views.home_page'),
(r'^login/$', 'django.contrib.auth.views.login'),
(r'^logout/$', 'django.contrib.auth.views.logout'),
(r'^about/$', 'faros.lantern.views.about_page_index', {}, 'about_page_index'),
(r'^about/(?P<page_id>([a-z0-9]+/)?)$', 'faros.lantern.views.about_page', {}, 'about_page'),
)
Views that looks like this:
def about_page_index(request):
try:
return render_to_response('lantern/about/index.html', context_instance=RequestContext(request))
except TemplateDoesNotExist:
raise Http404
def about_page(request, page_id):
page_id = page_id.strip('/ ')
try:
return render_to_response('lantern/about/' + page_id + '.html', context_instance=RequestContext(request))
except TemplateDoesNotExist:
raise Http404
And a template that includes this:
Contact
Contact
I'm getting this error message:
Caught an exception while rendering: Reverse for '<function about_page at 0x015EE730>' with arguments '()' and keyword arguments '{'page_id': u'contact'}' not found. The first reverse works fine (about_page_index), generating the correct URL without error messages.
I think this is because the request argument to the about_page view (request) is used, so I need to pass it in when I generate the URL in my template. Problem is, I don't know how to get to it, and searching around isn't getting me anywhere. Any ideas?
Thanks,
Dom
p.s. As an aside, does that method of handling static "about" type pages in an app look horrific or reasonable? I'm essentially taking URLs and assuming the path to the template is whatever comes after the about/ bit. This means I can make the static pages look like part of the app, so the user can jump into the about section and then right back to where they came from. Comments/Feedback on whether this is djangoic or stupid appreciated!
If I guess correctly from the signature of your view function (def about_page(request, page_id = None):), you likely have another URL configuration that points to the same view but that does not take a page_id parameter. If so, the django reverse function will see only one of these, and it's probably seeing the one without the named page_id regex pattern. This is a pretty common gotcha with reverse! :-)
To get around this, assign a name to each of the url patterns (see Syntax of the urlpatterns variable). In the case of your example, you'd do:
(r'^about/(?P<page_id>([a-z]+/)?)$', 'faros.lantern.views.about_page',
{}, 'about_with_page_id')
and then in the template:
Contact
Edit
Thanks for posting the updated urls.py. In the url template tag, using the unqualified pattern name should do the trick (note that I'm deleting the lantern.views part:
Contact
Contact
Edit2
I'm sorry I didn't twig to this earlier. Your pattern is expressed in a way that django can't reverse, and this is what causes the mismatch. Instead of:
r'^about/(?P<page_id>([a-z]+/)?)$'
use:
r'^about/(?P<page_id>[a-z0-9]+)/$'
I created a dummy project on my system that matched yours, reproduced the error, and inserted this correction to success. If this doesn't solve your problem, I'm going to eat my hat! :-)

Any thoughts on A/B testing in Django based project? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
We just now started doing the A/B testing for our Django based project. Can I get some information on best practices or useful insights about this A/B testing.
Ideally each new testing page will be differentiated with a single parameter(just like Gmail). mysite.com/?ui=2 should give a different page. So for every view I need to write a decorator to load different templates based on the 'ui' parameter value. And I dont want to hard code any template names in decorators. So how would urls.py url pattern will be?
It's useful to take a step back and abstract what A/B testing is trying to do before diving into the code. What exactly will we need to conduct a test?
A Goal that has a Condition
At least two distinct Paths to meet the Goal's Condition
A system for sending viewers down one of the Paths
A system for recording the Results of the test
With this in mind let's think about implementation.
The Goal
When we think about a Goal on the web usually we mean that a user reaches a certain page or that they complete a specific action, for example successfully registering as a user or getting to the checkout page.
In Django we could model that in a couple of ways - perhaps naively inside a view, calling a function whenever a Goal has been reached:
def checkout(request):
a_b_goal_complete(request)
...
But that doesn't help because we'll have to add that code everywhere we need it - plus if we're using any pluggable apps we'd prefer not to edit their code to add our A/B test.
How can we introduce A/B Goals without directly editing view code? What about a Middleware?
class ABMiddleware:
def process_request(self, request):
if a_b_goal_conditions_met(request):
a_b_goal_complete(request)
That would allow us to track A/B Goals anywhere on the site.
How do we know that a Goal's conditions has been met? For ease of implementation I'll suggest that we know a Goal has had it's conditions met when a user reaches a specific URL path. As a bonus we can measure this without getting our hands dirty inside a view. To go back to our example of registering a user we could say that this goal has been met when the user reaches the URL path:
/registration/complete
So we define a_b_goal_conditions_met:
a_b_goal_conditions_met(request):
return request.path == "/registration/complete":
Paths
When thinking about Paths in Django it's natural to jump to the idea of using different templates. Whether there is another way remains to be explored. In A/B testing you make small differences between two pages and measure the results. Therefore it should be a best practice to define a single base Path template from which all Paths to the Goal should extend.
How should render these templates? A decorator is probably a good start - it's a best practice in Django to include a parameter template_name to your views a decorator could alter this parameter at runtime.
#a_b
def registration(request, extra_context=None, template_name="reg/reg.html"):
...
You could see this decorator either introspecting the wrapped function and modifying the template_name argument or looking up the correct templates from somewhere (like a Model). If we didn't want to add the decorator to every function we could implement this as part of our ABMiddleware:
class ABMiddleware:
...
def process_view(self, request, view_func, view_args, view_kwargs):
if should_do_a_b_test(...) and "template_name" in view_kwargs:
# Modify the template name to one of our Path templates
view_kwargs["template_name"] = get_a_b_path_for_view(view_func)
response = view_func(view_args, view_kwargs)
return response
We'd need also need to add some way to keep track of which views have A/B tests running etc.
A system for sending viewers down a Path
In theory this is easy but there are lot of different implementations so it's not clear which one is best. We know a good system should divide users evenly down the path - Some hash method must be used - Maybe you could use the modulus of memcache counter divided by the number of Paths - maybe there is a better way.
A system for recording the Results of the Test
We need to record how many users went down what Path - we'll also need access to this information when the user reaches the goal (we need to be able to say what Path they came down to met the Condition of the Goal) - we'll use some kind of Model(s) to record the data and either Django Sessions or Cookies to persist the Path information until the user meets the Goal condition.
Closing Thoughts
I've given a lot of pseudo code for implementing A/B testing in Django - the above is by no means a complete solution but a good start towards creating a reusable framework for A/B testing in Django.
For reference you may want to look at Paul Mar's Seven Minute A/Bs on GitHub - it's the ROR version of the above!
http://github.com/paulmars/seven_minute_abs/tree/master
Update
On further reflection and investigation of Google Website Optimizer it's apparent that there are gaping holes in the above logic. By using different templates to represent Paths you break all caching on the view (or if the view is cached it will always serve the same path!). Instead, of using Paths, I would instead steal GWO terminology and use the idea of Combinations - that is one specific part of a template changing - for instance, changing the <h1> tag of a site.
The solution would involve template tags which would render down to JavaScript. When the page is loaded in the browser the JavaScript makes a request to your server which fetches one of the possible Combinations.
This way you can test multiple combinations per page while preserving caching!
Update
There still is room for template switching - say for example you introduce an entirely new homepage and want to test it's performance against the old homepage - you'd still want to use the template switching technique. The thing to keep in mind is your going to have to figure out some way to switch between X number of cached versions of the page. To do this you'd need to override the standard cached middleware to see if their is a A/B test running on the requested URL. Then it could choose the correct cached version to show!!!
Update
Using the ideas described above I've implemented a pluggable app for basic A/B testing Django. You can get it off Github:
http://github.com/johnboxall/django-ab/tree/master
If you use the GET parameters like you suggsted (?ui=2), then you shouldn't have to touch urls.py at all. Your decorator can inspect request.GET['ui'] and find what it needs.
To avoid hardcoding template names, maybe you could wrap the return value from the view function? Instead of returning the output of render_to_response, you could return a tuple of (template_name, context) and let the decorator mangle the template name. How about something like this? WARNING: I haven't tested this code
def ab_test(view):
def wrapped_view(request, *args, **kwargs):
template_name, context = view(request, *args, **kwargs)
if 'ui' in request.GET:
template_name = '%s_%s' % (template_name, request.GET['ui'])
# ie, 'folder/template.html' becomes 'folder/template.html_2'
return render_to_response(template_name, context)
return wrapped_view
This is a really basic example, but I hope it gets the idea across. You could modify several other things about the response, such as adding information to the template context. You could use those context variables to integrate with your site analytics, like Google Analytics, for example.
As a bonus, you could refactor this decorator in the future if you decide to stop using GET parameters and move to something based on cookies, etc.
Update If you already have a lot of views written, and you don't want to modify them all, you could write your own version of render_to_response.
def render_to_response(template_list, dictionary, context_instance, mimetype):
return (template_list, dictionary, context_instance, mimetype)
def ab_test(view):
from django.shortcuts import render_to_response as old_render_to_response
def wrapped_view(request, *args, **kwargs):
template_name, context, context_instance, mimetype = view(request, *args, **kwargs)
if 'ui' in request.GET:
template_name = '%s_%s' % (template_name, request.GET['ui'])
# ie, 'folder/template.html' becomes 'folder/template.html_2'
return old_render_to_response(template_name, context, context_instance=context_instance, mimetype=mimetype)
return wrapped_view
#ab_test
def my_legacy_view(request, param):
return render_to_response('mytemplate.html', {'param': param})
Justin's response is right... I recommend you vote for that one, as he was first. His approach is particularly useful if you have multiple views that need this A/B adjustment.
Note, however, that you don't need a decorator, or alterations to urls.py, if you have just a handful of views. If you left your urls.py file as is...
(r'^foo/', my.view.here),
... you can use request.GET to determine the view variant requested:
def here(request):
variant = request.GET.get('ui', some_default)
If you want to avoid hardcoding template names for the individual A/B/C/etc views, just make them a convention in your template naming scheme (as Justin's approach also recommends):
def here(request):
variant = request.GET.get('ui', some_default)
template_name = 'heretemplates/page%s.html' % variant
try:
return render_to_response(template_name)
except TemplateDoesNotExist:
return render_to_response('oops.html')
A code based on the one by Justin Voss:
def ab_test(force = None):
def _ab_test(view):
def wrapped_view(request, *args, **kwargs):
request, template_name, cont = view(request, *args, **kwargs)
if 'ui' in request.GET:
request.session['ui'] = request.GET['ui']
if 'ui' in request.session:
cont['ui'] = request.session['ui']
else:
if force is None:
cont['ui'] = '0'
else:
return redirect_to(request, force)
return direct_to_template(request, template_name, extra_context = cont)
return wrapped_view
return _ab_test
example function using the code:
#ab_test()
def index1(request):
return (request,'website/index.html', locals())
#ab_test('?ui=33')
def index2(request):
return (request,'website/index.html', locals())
What happens here:
1. The passed UI parameter is stored in the session variable
2. The same template loads every time, but a context variable {{ui}} stores the UI id (you can use it to modify the template)
3. If user enters the page without ?ui=xx then in case of index2 he's redirected to '?ui=33', in case of index1 the UI variable is set to 0.
I use 3 to redirect from the main page to Google Website Optimizer which in turn redirects back to the main page with a proper ?ui parameter.
You can also A/B test using Google Optimize. To do so you'll have to add Google Analytics to your site and then when you create a Google Optimize experiment each user will get a cookie with a different experiment variant (according to the weight for each variant). You can then extract the variant from the cookie and display various versions of your application. You can use the following snippet to extract the variant:
ga_exp = self.request.COOKIES.get("_gaexp")
parts = ga_exp.split(".")
experiments_part = ".".join(parts[2:])
experiments = experiments_part.split("!")
for experiment_str in experiments:
experiment_parts = experiment_str.split(".")
experiment_id = experiment_parts[0]
variation_id = int(experiment_parts[2])
experiment_variations[experiment_id] = variation_id
However there is a django package that integrates well with Google Optimize: https://github.com/adinhodovic/django-google-optimize/.
And here is a blog post on how to use the package and how Google Optimize works: https://hodovi.cc/blog/django-b-testing-google-optimize/.