using two templates from one view - django

I am trying to present content from a view in two ways: html and csv download. The only way I was able to do it was to use 2 different views, one for html presentation and one for csv. This duplicates my code and I am looking for a more elegant solution.
Any suggestions?
Here is the sample code:
# views.py
[...]
def member_list(request):
member_list = Member.objects.all()
return render_to_response("member_list.html",
{'member_list':member_list)
def member_csv_list(request):
member_list = Member.objects.all()
csv_list = HttpResponse(content_type='text/csv')
csv_list['Content-Disposition'] = 'attachment; filename="member_list.csv"'
writer = csv.writer(csv_list)
writer.writerow(['Name', 'Member Type', 'Rooms'])
for member in member_list:
fields = [member.name, member.member_type, member.room]
writer.writerow(fields)
return member_list

You can use a parameter in your url and implement a view like
def myview(request) :
type = request.GET.get('type', 'html')
# do processing
if type == 'html':
# return html
else if type == 'csv':
# return csv
If you access a url like http://yourserver/myview?type=csv it will render the csv part of the view. When the url http://yourserver/myview is accessed it will return the html part of the view.

Rohan's answer is absolutely the right paradigm. For an excellent tutorial-style introduction to this topic, cf. Multiple Templates in Django.
Here are a few quotes (all credit goes to Scott Newman).
To serve a printable version of an article, for example, we can add ?printable to the end of the URL.
To make it work, we'll add an extra step in our view to check the URL for this variable. If it exists, we'll load up a printer-friendly template file. If it doesn't exist, we'll load the normal template file.
def detail(request, pid):
'''
Accepts a press release ID and returns the detail page
'''
p = get_object_or_404(PressRelease, id=pid)
if request.GET.has_key('printable'):
template_file = 'press/detail_printable.html'
else:
template_file = 'press/detail.html'
t = loader.get_template(template_file)
c = Context({'press': p})
return HttpResponse(t.render(c))
He continues with template overrides and different templates by domain names. All this is excellent.

Related

Django not displaying correct URL after reverse

There's lots of documentation about Django and the reverse() method. I can't seem to locate my exact problem. Suppose I have two urlconfs like this:
url(r'ParentLists/$', main_page, name = "main_page"),
url(r'ParentLists/(?P<grade_level>.+)/$', foo, name = "foo")
and the two corresponding views like this:
def main_page(request):
if request.method == 'POST':
grade_level = request.POST['grade_picked']
return HttpResponseRedirect(reverse('foo', args = (grade_level,)))
else:
return render(request, 'index.html', context = {'grade_list' : grade_list})
def foo(request, grade_level):
grade_level = request.POST['grade_picked']
parent_list = # get stuff from database
emails = # get stuff from database
return render(request, 'list.html', context = {'grade_list' : grade_list, 'parent_list' : parent_list})
Here, list.html just extends my base template index.html, which contains a drop down box with grade levels. When the user goes to /ParentLists, the main_page view renders index.html with the drop down box as it should.
When the user picks a grade level from the drop down box (say 5th Grade), the template does a form submit, and main_page once again executes - but this time the POST branch runs and the HttpResponseRedirect takes the user to /ParentLists/05. This simply results in an HTML table pertaining to grade 5 being displayed below the drop down box.
The problem is, when the user now selects say 10th Grade, the table updates to show the grade 10 content, but the URL displayed is still /ParentLists/05. I want it to be /ParentLists/10.
Clearly, after the first selection, the main_page view never executes again. Only foo does...and so the HttpResponseRedirect never gets called. How should I reorganize this to get what I'm looking for? Thanks in advance!
As you correctly mentioned you will never redirect to foo() from foo().
So the simple way to fix this is just add similar code as in main_page() view:
def foo(request, grade_level):
if request.method == 'POST':
grade_level = request.POST['grade_picked']
return HttpResponseRedirect(reverse('foo', args = (grade_level,)))
else:
parent_list = # get stuff from database
emails = # get stuff from database
return render(request, 'list.html', context = {'grade_list' : grade_list, 'parent_list' : parent_list})
Please note that I remove grade_level = request.POST['grade_picked'] because as Nagkumar Arkalgud correctly said it is excessively.
Also instead of combination of HttpResponseRedirect and reverse you can use shortcut redirect which probably little easy to code:
from django.shortcuts redirect
...
return redirect('foo', grade_level=grade_level)
I would suggest you to use kwargs instead of args.
The right way to use the view is:
your_url = reverse("<view_name>", kwargs={"<key>": "<value>"})
Ex:
return HttpResponseRedirect(reverse('foo', kwargs={"grade_level": grade_level}))
Also, you are sending "grade_level" to your view foo using the URL and not a POST value. I would remove the line:
grade_level = request.POST['grade_picked']
as you will override the grade_level sent to the method from the url.

Django: restrict access to a view, dependent upon referring url

I'm making a school records webapp. I want staff users to be able to view the user data pages for any pupil by going to the correct url, but without allowing pupils access to each others' pages. However I'm using the same view function for both urls.
I have a working #user_is_staff decorator based on the existence of a user.staff object. Pupil users have a user.pupil object instead. These are discrete, naturally, as no user can have both a .staff and a .pupil entry.
urls.py
(r'^home/(?P<subject>[^/]+)/$', 'myproject.myapp.views.display_pupil')
(r'^admin/user/(?P<user>\d+)/(+P<subject>[^/]+)/$', 'myproject.myapp.views.display_pupil')
views.py
#login_required
def display_pupil(request, subject, pupil=None):
if pupil:
try:
thepupil = get_object_or_404(Pupil, id = pupil, cohort__school = request.user.staff.school)
except Staff.DoesNotExist:
return HttpResponseForbidden()
else:
thepupil = request.user.pupil
thesubject = get_object_or_404(Subject, shortname = subject)
# do lots more stuff here
return render_to_response('pupilpage.html', locals(), context_instance=RequestContext(request))
Doing it this way works, but feels very hacky, particularly as my '#user_is_staff' decorator has a more elegant redirect to a login page than the 403 error here.
What I don't know is how to apply the #user_is_staff decorator to the function only when it has been accessed with the pupil kwarg. There's a lot more code in the real view function, so I don't want to write a second one as that would be severely non-DRY.
Sounds like you want two separate views - one for a specific pupil and one for the current user - and a utility function containing the shared logic.
#login_required:
def display_current_pupil(request, subject):
thepupil = request.user.pupil
return display_pupil_info(request, subject, thepupil)
#user_is_staff
def display_pupil(request, subject, pupil):
thepupil = get_object_or_404(Pupil, id=pupil, cohort__school=request.user.staff.school)
return display_pupil_info(request, subject, thepupil)
def display_pupil_info(request, subject, thepupil):
thesubject = get_object_or_404(Subject, shortname=subject)
# do lots more stuff here
return render_to_response('pupilpage.html', locals(), context_instance=RequestContext(request))

Want to print out a list of items from another view django

I have a view which displays a list items.
def edit_order(request, order_no):
try:
status_list = models.Status.objects.all()
order = models.Order.objects.get(pk = order_no)
if order.is_storage:
items = models.StorageItem.objects.filter(orderstoragelist__order__pk = order.pk)
else:
items = models.StorageItem.objects.filter(orderservicelist__order__pk = order.pk)
except:
return HttpResponseNotFound()
I want to put these list of item in another view. Unfortunately this is proving to be trickier then I thought.
#login_required
def client_items(request, client_id = 0):
client = None
items = None
try:
client = models.Client.objects.get(pk = client_id)
items = client.storageitem_set.all()
item_list = models.StorageItem.objects.filter(orderstoragelist__order__pk = order.pk)
except:
return HttpResponse(reverse(return_clients))
return render_to_response('items.html', {'items':items, 'client':client, 'item_list':item_list}, context_instance = RequestContext(request))
I thought maybe I can just paste the definition of items and just call that item_list but that does not work. Any ideas
items.html
{% for item in item_list %}
{{item.tiptop_id}
{% endfor %}
From your comment:
I get a white screen with the url printed on the screen. /tiptop/client in this case.
Because that's what you've asked for:
except:
return HttpResponse(reverse(return_clients))
This means that if there are any bugs or problems in the above, your view will simply output a response containing just that URL. Maybe you meant to use HttpResponseRedirect, so the browser actually redirects to the URL - but still you should not use a blank except, as it prevents you from seeing what is actually going wrong.
To answer the main question, think about what your edit_order view returns: it gives you a complete HTML response with a rendered template. How could you use that as an element in a query in another view? You need to think logically about this.
One possible solution would be to define a separate function which just returns the data you want - as a plain queryset - and both views can call it. Does that do what you want?

How to write a request filter / preprocessor in Django

I am writing an application in Django, which uses [year]/[month]/[title-text] in the url to identitfy news items. To manage the items I have defined a number of urls, each starting with the above prefix.
urlpatterns = patterns('msite.views',
(r'^(?P<year>[\d]{4})/(?P<month>[\d]{1,2})/(?P<slug>[\w]+)/edit/$', 'edit'),
(r'^(?P<year>[\d]{4})/(?P<month>[\d]{1,2})/(?P<slug>[\w]+)/$', 'show'),
(r'^(?P<year>[\d]{4})/(?P<month>[\d]{1,2})/(?P<slug>[\w]+)/save$', 'save'),
)
I was wondering, if there is a mechanism in Django, which allows me to preprocess a given request to the views edit, show and save. It could parse the parameters e.g. year=2010, month=11, slug='this-is-a-title' and extract a model object out of them.
The benefit would be, that I could define my views as
def show(news_item):
'''does some stuff with the news item, doesn't have to care
about how to extract the item from request data'''
...
instead of
def show(year, month, slug):
'''extract the model instance manually inside this method'''
...
What is the Django way of solving this?
Or in a more generic way, is there some mechanism to implement request filters / preprocessors such as in JavaEE and Ruby on Rails?
You need date based generic views and create/update/delete generic views maybe?
One way of doing this is to write a custom decorator. I tested this in one of my projects and it worked.
First, a custom decorator. This one will have to accept other arguments beside the function, so we declare another decorator to make it so.
decorator_with_arguments = lambda decorator: lambda * args, **kwargs: lambda func: decorator(func, *args, **kwargs)
Now the actual decorator:
#decorator_with_arguments
def parse_args_and_create_instance(function, klass, attr_names):
def _function(request, *args, **kwargs):
model_attributes_and_values = dict()
for name in attr_names:
value = kwargs.get(name, None)
if value: model_attributes_and_values[name] = value
model_instance = klass.objects.get(**model_attributes_and_values)
return function(model_instance)
return _function
This decorator expects two additional arguments besides the function it is decorating. These are respectively the model class for which the instance is to be prepared and injected and the names of the attributes to be used to prepare the instance. In this case the decorator uses the attributes to get the instance from the database.
And now, a "generic" view making use of a show function.
def show(model_instance):
return HttpResponse(model_instance.some_attribute)
show_order = parse_args_and_create_instance(Order, ['order_id'])(show)
And another:
show_customer = parse_args_and_create_instance(Customer, ['id'])(show)
In order for this to work the URL configuration parameters must contain the same key words as the attributes. Of course you can customize this by tweaking the decorator.
# urls.py
...
url(r'^order/(?P<order_id>\d+)/$', 'show_order', {}, name = 'show_order'),
url(r'^customer/(?P<id>\d+)/$', 'show_customer', {}, name = 'show_customer'),
...
Update
As #rebus correctly pointed out you also need to investigate Django's generic views.
Django is python after all, so you can easily do this:
def get_item(*args, **kwargs):
year = kwargs['year']
month = kwargs['month']
slug = kwargs['slug']
# return item based on year, month, slug...
def show(request, *args, **kwargs):
item = get_item(request, *args, **kwargs)
# rest of your logic using item
# return HttpResponse...

passing object data through URL

I know that I can pass object values through a URL pattern and use them in view functions. For instance:
(r'^edit/(?P<id>\w+)/', edit_entry),
can be utilized like:
def edit_entry(request, id):
if request.method == 'POST':
a=Entry.objects.get(pk=id)
form = EntryForm(request.POST, instance=a)
if form.is_valid():
form.save()
return HttpResponseRedirect('/contact/display/%s/' % id)
else:
a=Entry.objects.get(pk=id)
form = EntryForm(instance=a)
return render_to_response('edit_contact.html', {'form': form})
But how do I pass a value from a model field (other than "id") in the url? For instance, I have an abstract base model with a field "job_number" that is shared by child models "OrderForm" and "SpecReport". I want to click on the "job_number" on the order form and call the Spec Report for that same job number. I can create an
href="/../specifications/{{ record.job_number }}
to pass the info to the url, but I already know that this regex syntax is incorrect:
(r'^specifications/(?P<**job_number**>\w+)/', display_specs),
nor can I capture the job_number in the view the same way I could an id:
def display_specs(request, job_number):
records = SpecReport.objects.filter(pk=job_number)
tpl = 'display.html'
return render_to_response(tpl, {'records': records })
Is there an easy approach to this or is it more complicated than I think it is?
the amended code is as follows:
(r'^specdisplay/?agencyID=12/', display_specs),
and:
def display_specs(request, agencyID):
agencyID= request.GET.get('agencyID')
records = ProductionSpecs.objects.filter(pk=id)
tpl = 'display_specs.html'
return render_to_response(tpl, {'records': records })
not sure how to filter. pk is no longer applicable.
Yes, you are making this a little more complicated that it is.
In your urls.py you have:
(r'^edit/(?P<id>\w+)/', edit_entry),
Now you just need to add the almost identical expression for display_specs:
(r'^specifications/(?P<job_number>\w+)/', display_specs),
Parenthesis in the regex identifies a group and the (?P<name>...) defines a named group which will be named name. This name is the parameter to your view.
Thus, your view will now look like:
def display_specs(request, job_number):
...
Finally, even though this will work, when you redirect to the view, instead of using:
HttpResponseRedirect('/path/to/view/%s/' % job_number)
Use the more DRY:
HttpResponseRedirect(
reverse('display_specs', kwargs={'job_number': a.job_number}))
Now if you decide to change your resource paths your redirect won't break.
For this to work you need to start using named urls in your urlconf like this:
url(r'^specifications/(?P<job_number>\w+)/', display_specs, name='display_specs'),
Not knowing what your model structure is like ... why couldn't you just pass the particular job's id and then pick it up with a query?
Afaik every model automatically has an id field that autoincrements and is a unique identifier of a row (an index if you will), so just change the href creation to {{record.id}} and go from there.
Try passing the job_number through the url then, especially if you don't care about pretty url's too much just do this:
url: /foo/bar/?job_number=12
no special markup to catch this btw, the regex is r'^foo/bar/'
And then read it in the view like this:
job_number= request.GET.get('job_number')
I really don't understand your question. What's the difference between passing id and passing job_number in a URL? If you can do one, why can't you do the other? And once the job_number is in the view, why can't you do a normal filter:
records = SpecReport.objects.filter(job_number=job_number)