I am developing a site right now that, once you are logged in, a search bar will always be present on the top of the page. I am wondering what the best way design this paradigm in Django. Currently, I have a separate file called forms.py that sits at settings.py level in my folder hierarchy. In almost every view, I have to add:
from forms.py import SearchForm
and then in every single render call, I have to pass:
form = SearchForm()
return render('somepage.html',{"search_form" : form},c=RequestContext())
I have looked around for a better way of doing this, but I am having trouble finding anything useful. I have a feeling that the current design I am using is not ideal, since I am required to import/pass as parameter in almost every view.
The form is defined in a base.html, so I am using template inheritance, but I still need to pass the form object to every render as far as I can tell.
Use a context processor
Add your search form to the context of all views using RequestContext, which the new render you're using does automatically.
def FormContextProcessor(request):
if request.user.is_authenticated():
return {'form': SearchForm() }
return {}
You said it's used in nearly all views, and this is hardly an expensive operation instantiating a form, so I'd use this solution.
with django < 1.3 you can have a decorator, which can take care of rendering a html:
def search_render(function):
# return a decorated function which will take template from the args
# take output of the inner function (this should be a dictionary e.g. data = ..
# instantiate SearchForm
# add SearchForm instance to the data dictionary
# and return render(template, data, RequestContext(request))
#search_render(tamplate='somepage.html')
def my_other_view(request):
return {'data':'value'}
With django >= 1.3 you can use a class based views, with similiar approach.
Related
I have a table with results from a search. This means there are no models here. The problem is that i'd like to go to a detail page for a clicked item, but I am not sure if I can do that without putting it in the URL.
Right now it is done like this:
In my .html for each item in the table:
view more</td>
In my urls.py
url(r'^track/(?P<title>.+?)/$', detail,
name='detail'),
In my detail view, where it uses the variable:
def detail(request, title):
if request.method == 'GET':
...
Now this might work, but it is not ideal for me. The url contains whitespace and is not urlencoded, because I need the variable like it is. I was wondering if there is some easier, or better way to pass this variable to a different view or template
The right way to do this is to define a get_absolute_url method on the model class for your item model. This is typically done using reverse, so it would look something like this:
def get_absolute_url(self):
from django.core.urlresolvers import reverse
return reverse('detail', kwargs={'title': self.title})
Then in your template you simply do:
view more
And the title will be properly escaped.
That said, you might want to consider using a slug instead of a title to generate your URLs.
I would like almost every View in my Django project to compute, for instance, time_on_mars. This variable needs to be put into every template context, so every template can draw time_on_mars. I'd also like every View to be able to use that variable if it wishes, maybe to compute is_it_nighttime_on_mars or whatever. I'd like to do this using inheritance, or some other mechanism, so when I provision a whole lot more than mars time, I don't repeat myself.
Is this an appropriate use of class-based Views? I guess I'd create a base class view, and in its get and post methods, calculate the current time on mars, then call child class methods 'doGet' or 'doPost', intercept the result, put the mars time into the result, and continue. Seems tedious.
Decorators? Is there a way I can put time_on_mars into the closure context for the view?
What's the pythonic (djangonic?) way to do this?
EDIT: I like the idea of context processors, but I've changed my mind - instead of every view, I'd like most views to get this. Especially if it's expensive to compute the time on mars...
EDIT 2: Problem description not adequate! To clarify:
I have a bunch of views that do this:
def some_view(request):
w,x,y,z = provision_wxyz()
... some biz logic, maybe compute a,b,c using w,x,y,z ...
return render(request, 'some_template.html', { 'a':a, 'b':b, 'c':c, 'w':w, 'x':x, 'y':y, 'z':z })
... and I'd like to abstract out the first step (w,x,y,z=), and the adding of w,x,y,z to the template context. Context processors don't work, because inside the some_view business logic, I want access to w,x,y,z. I don't like doing the mixing strategy for CLA on the TemplateView, because again, it doesn't give me access to w,x,y and z inside some_view (or whatever the equivalent of some_view is), and I want to, well, do a bunch of business logic somewhere, and TemplateView doesn't seem to give me that?
You can definitely use CBV's for this. It is a great use for Mixins which take advantage of pythons multiple inheritance. Here is a quick and dirty example I just wrote out.
class MarsMixin(object):
time_on_mars = 5
def get_time_on_mars(self):
"""
Does what it takes to return a time on mars be it calculation
or returning a property set on the object. Should return a property
from the object if it is a constant. Should calcualte in the method
if it is going to be dynanic
"""
return self.time_on_mars
def get_context_data(self, **kwargs):
context = super(MarsMixin, self).get_context_data(**kwargs)
context['time_on_mars'] = self.get_time_on_mars()
return context
class HomeView(MarsMixin, TemplateView):
template_name = 'home/index.html'
Biggest notes are the mixin inherits from object. The other is you inherit from mixin in the HomeView and it is listed before the TemplateView.
I think you can still use context processors, the tip that can help is to prefix the urls where you want to do your calculation, for example they will start by '/mars/...'. You can also use another kind of prefix.
Like this you can check on the context processor method:
def go_to_mars(request):
if request.path.startswith('/mars/'):
return {'time_on_mars': calculate_time_on_mars()}
return {}
I have a nested loop that I would like to break out of. After searching this site it seems the best practice is to put the nested loop into a function and use return to break out of it. Is it acceptable to have functions inside the views.py file that are not a view? What is the best practice for the location of this function? Here's the example code from inside my views.py
#login_required
def save_bookmark(request):
if request.method == 'POST':
form = BookmarkSaveForm(request.POST)
if form.is_valid():
bookmark_list = Bookmark.objects.all()
for bookmark in bookmark_list:
for link in bookmark.link_set.all():
if link.url == form.cleaned_data['url']:
# Do something.
break
else:
# Do something else.
else:
form = BookmarkSaveForm()
return render_to_response('save_bookmark_form.html', {'form': form})
You shouldn't think of Django views as being in any way special. It's just Python. As such, you can have whatever functions you like in views.py. The only limitation is that views themselves have to take a request object and return a subclass of HttpResponse. Other than that, you can do what you like in that module, including having functions, classes or constants that are used by your views.
If you have a lot of utility functions, you may want to consider extracting them into eg a lib.py in your app directory for the sake of tidiness. But there's no need to do that if you've just got one or two.
Yes. it's fine to have functions in views.py that are not views - (I do this all the time). This is particularly appropriate if the function is only for use within that module (i.e. by views in that views.py), or by just a single view function.
You could always make it a private function if you're worried about exposing it to the outside world. Also, try avoid giving it a parameter called request, I tend to subconsciously parse functions which take a request as view functions when reading code.
Is it possible to use create_object view to create a new object and automatically assign request.user as foreign key?
P.E:
class Post(models.Model):
text = models.TextField()
author = models.ForeignKey(User)
What I want is to use create_object and fill author with request.user.
In many ways, all the solutions to this will be more trouble than they are worth. This one qualifies as a hack. It is possible for a django update to leave you high and dry if they change the way create_update is implemented. For simplicity sake, I'll assume that you are trying to set a default user, not silently force the user to be the logged in user.
Write a context processor:
from django.views.generic.create_update import get_model_and_form_class
def form_user_default(request):
if request.method == 'GET':
model, custom_form = get_model_and_form_class(Post,None)
custom_form.author = request.user
return {'form':custom_form}
else: return {}
What this will do is override the form object that create_update passes to the template. What it's technically doing is re-creating the form after the default view has done it.
Then in your url conf:
url(r'pattern_to_match', 'django.views.generic.create_update.create_object', kwargs={'context_processors':form_user_default})
Again, I had to delve into the source code to figure out how to do this. It might really be best to try writing your own view (but incorporate as many Django custom objects as possible). There's no "simple default" way to do this, because in the django paradigm forms are more closely tied to the model layer than to views, and only views have knowledge of the request object.
You may want to consider a closure.
from django.forms import ModelForm
from django.views.generic.create_update import create_object, update_object
def make_foo_form(request):
class FooForm(ModelForm):
class Meta:
model = Foo
fields = ['foo', 'bar']
def save(self, commit=True):
f = super(FooForm, self).save(commit=False)
if not f.pk: f.user = request.user
if commit: f.save()
return f
return FooForm
def create_foo(request):
FooForm = make_foo_form(request)
return create_object(form_class=FooForm)
There is some inefficiency here, since you need to create the ModelForm object on each request, but it does allow you to inject functionality into the generic view.
You need to decide whether the added complexity for the form creation is worth maintaining simplicity on the view side.
A benefit here, though, is that this also works with the update case with practically no extra effort:
def update_foo(request, object_id):
FooForm = make_foo_form(request)
return update_object(form_class=FooForm, object_id=object_id)
Obviously, you can use this approach for more complex cases as well.
If a user is authenticated, their user object is the request.user object.
I'm not familiar with create_object... I'm still a beginner to django and have only just started my first real project with it.
Note that you should check to make sure a user is logged in before using this. This can be done with request.user.is_authenticated().
There is no good way to hook into the saving of an object when using the current Django generic views. Once they are rewritten as classes, you'll be able to subclass the view and hook in at the proper place without having to rewrite the whole view.
I already use my own class-based generic views for this reason.
I would suggest to make a wrapper for the create_object, as this author suggest
http://www.b-list.org/weblog/2006/nov/16/django-tips-get-most-out-generic-views/
in the view you'll have access to the user info.
Afterwards, you will need to use the extra_context to pass the user to the template. Finally at the template you can add a hidden field with the user info. I haven't tried it, but I have been thinking of it for quite some time. Hope this solution suits you!
;) cheers!
What I would like to achive is:
I go to admin site, apply some filters to the list of objects
I click and object edit, edit, edit, hit 'Save'
Site takes me to the list of objects... unfiltered. I'd like to have the filter from step 1 remembered and applied.
Is there an easy way to do it?
Unfortunately there's no easy way to do this. The filtering does not seem to be saved in any session variable.
Clicking back twice is the normal method, but it can be unweildy and annoying if you've just changed an object so that it should no longer be shown using your filter.
If it's just a one-off, click back twice or go through the filtering again, it's the easiest way.
If you're going to be filtering more often, or you just want to learn about hacking the admin (which is pretty open and easy), you'll want to write a FilterSpec.
Have a look here and here for examples of people writing their own.
A really, really terrible way to do this would be to edit the admin interface so that after you click "Save", you are redirected to you filtered URL. I wouldn't recommend this at all, but it's an option.
Another fairly simple way to do this would be to write a generic view to show your filtered objects, then use Django forms to edit the items from there. I'd have a look at this, you'll be stunned just how little code you have to write to get a simple view/edit page going.
Click 2 times "Back"?
There's a simple hack to do this, but it's not a general solution and requires modifying every ModelAdmin which you want to support this. Maybe there is a general way to do this, but I've not spent the time to solve it on a general level.
The first step is to write a custom FilterSpec for the filter (see Harley's post for links that will help) which saves the chosen filter value in the session (and deletes it when no longer wanted).
# in cust_admin/filterspecs.py
from django.contrib.admin.filterspecs import FilterSpec, ChoicesFilterSpec
class MyFilterSpec(ChoicesFilterSpec):
def __init__(self, f, request, params, model, model_admin):
super(MyFilterSpec, self).__init__(f, request, params, model,
model_admin)
if self.lookup_val is not None:
request.session[self.lookup_kwarg] = self.lookup_val
elif self.lookup_kwarg in request.session:
del(request.session[self.lookup_kwarg])
# Register the filter with a test function which will apply it to any field
# with a my_filter attribute equal to True
FilterSpec.filter_specs.insert(0, (lambda f: getattr(f, 'my_filter', False),
MyFilterSpec))
You must import the module this is in somewhere, for example your urls.py:
# in urls.py
from cust_admin import filterspecs
Set a property on the field you want to apply the filter to:
# in models.py
class MyModel(models.Model):
my_field = Models.IntegerField(choices=MY_CHOICES)
my_field.my_filter = True
In a custom ModelAdmin class, override the change_view method, so that after the user clicks save, they are returned to the list view with their filter field value added to the URL.
class MyModelAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, extra_context=None):
result = super(MyModelAdmin, self).change_view(request, object_id,
extra_context)
if '_save' in request.POST:
if 'my_field__exact' in request.session:
result['Location'] = '/admin/myapp/mymodel/?my_field__exact=%s' \
% request.session['my_field__exact']
return result
Another way to do this is to embed the filter in the queryset.
You can dynamically create a proxy model with a manager that filters the way you want, then call admin.site.register() to create a new model admin. All the links would then be relative to this view.
In my opinion its better to override methods from ModelAdmin changelist_view and change_view:
Like so:
class FakturaAdmin(admin.ModelAdmin):
[...]
def changelist_view(self, request, extra_context=None):
result = super(FakturaAdmin, self).changelist_view(request, extra_context=None)
request.session['qdict'] = request.GET
return result
def change_view(self, request, object_id, extra_context=None):
result = super(FakturaAdmin, self).change_view(request, object_id, extra_context)
try:
result['location'] = result['location']+"?"+request.session['qdict'].urlencode()
except:
pass
return result
As you wish, after save object you go back to list of objects with active filters.
There is a change request at the Django project asking for exactly this functionality.
All it's waiting for to be checked in is some tests and documentation. You could write those, and help the whole project, or you could just take the proposed patch (near the bottom of the page) and try it out.
https://code.djangoproject.com/ticket/6903
This feature has been added to Django as part of the 1.6 release and is enabled now by default. It is described in the release notes:
ModelAdmin now preserves filters on the list view after creating,
editing or deleting an object. It’s possible to restore the previous
behavior of clearing filters by setting the preserve_filters attribute
to False.