diff --git a/django/core/defaulttags.py b/django/core/defaulttags.py
index 46905c50a6..d5bfec7cf0 100644
--- a/django/core/defaulttags.py
+++ b/django/core/defaulttags.py
@@ -228,15 +228,17 @@ class SsiNode(template.Node):
return output
class IncludeNode(template.Node):
- def __init__(self, template_path):
+ def __init__(self, template_path_var):
self.template_path_var = template_path_var
def render(self, context):
try:
- template_path = template.resolve(self.template_path_var, context)
+ template_path = template.resolve_variable(self.template_path_var, context)
+ print "IncludeNode rendering %s" % template_path
t = template_loader.get_template(template_path)
return t.render(context)
- except:
+ except Exception, e:
+ print e
return '' # Fail silently for invalid included templates.
diff --git a/django/core/meta/__init__.py b/django/core/meta/__init__.py
index 4c89841b3b..1df2032f97 100644
--- a/django/core/meta/__init__.py
+++ b/django/core/meta/__init__.py
@@ -1035,9 +1035,10 @@ def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):
m2m_table = rel_field.get_m2m_db_table(rel_opts)
this_id = getattr(self, self._meta.pk.column)
cursor = db.db.cursor()
- cursor.execute("DELETE FROM %s WHERE %s_id = %%s" % (m2m_table, rel.object_name.lower()), [this_id])
- sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % (m2m_table, rel.object_name.lower(), rel_opts.object_name.lower())
- cursor.executemany(sql, [(this_id, i) for i in id_list])
+ delete_stmt = "DELETE FROM %s WHERE %s_id = %%s" % (m2m_table, rel.object_name.lower())
+ cursor.execute(delete_stmt, [this_id])
+ insert_stmt = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % (m2m_table, rel.pk.column, rel_opts.pk.column)
+ cursor.executemany(insert_stmt, [(this_id, i) for i in id_list])
db.db.commit()
# ORDERING METHODS #########################
@@ -1488,7 +1489,7 @@ def get_manipulator(opts, klass, extra_methods, add=False, change=False):
setattr(man, k, v)
return man
-def manipulator_init(opts, add, change, self, obj_key=None):
+def manipulator_init(opts, add, change, self, obj_key=None, follow=None):
if change:
assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter."
self.obj_key = obj_key
diff --git a/django/templatetags/admin_modify.py b/django/templatetags/admin_modify.py
index 1ce938b9a9..c52505e044 100644
--- a/django/templatetags/admin_modify.py
+++ b/django/templatetags/admin_modify.py
@@ -8,6 +8,12 @@ from django.utils.functional import curry
from django.views.admin.main import AdminBoundField
import re
+word_re = re.compile('[A-Z][a-z]+')
+
+def class_name_to_underscored(name):
+ return '_'.join([ s.lower() for s in word_re.findall(name)[:-1] ])
+
+
class IncludeAdminScriptNode(template.Node):
def __init__(self, var):
self.var = var
@@ -43,23 +49,6 @@ class SubmitRowNode(template.Node):
}, context);
context.pop()
return output;
-# t = ['
\n')
-
-# return ''.join(t)
-
-
-
class AdminFieldBoundNode(template.Node):
def __init__(self, argument):
@@ -103,19 +92,32 @@ class FieldWidgetNode(template.Node):
self.bound_field_var = bound_field_var
def render(self, context):
+
bound_field = template.resolve_variable(self.bound_field_var, context)
add = context['add']
change = context['change']
context.push()
context['bound_field'] = bound_field
- t = template_loader.get_template("admin_field_widget")
- output = t.render(context)
- context.pop()
-
- return output
-
+ klass = bound_field.field.__class__
+ t = None
+ while klass:
+ try:
+ field_class_name = klass.__name__
+ template_name = "widget/%s" % \
+ class_name_to_underscored(field_class_name)
+
+ t = template_loader.get_template(template_name)
+ break
+ except template.TemplateDoesNotExist:
+ klass = (len(klass.__bases__) > 0) and klass.__bases__[0] or None
+ if t == None:
+ t = template_loader.get_template("widget/default")
+
+ output = t.render(context)
+ context.pop()
+ return output
class FieldWrapper(object):
def __init__(self, field ):
@@ -259,9 +261,9 @@ one_arg_tag_nodes = [
FilterInterfaceScriptMaybeNode,
]
-word = re.compile('[A-Z][a-z]+')
+
def register_one_arg_tag(node):
- tag_name = '_'.join([ s.lower() for s in word.findall(node.__name__)[:-1] ])
+ tag_name = class_name_to_underscored(node.__name__)
parse_func = curry(do_one_arg_tag, node)
template.register_tag(tag_name, parse_func)
diff --git a/docs/outputting_pdf.txt b/docs/outputting_pdf.txt
new file mode 100644
index 0000000000..68504599bd
--- /dev/null
+++ b/docs/outputting_pdf.txt
@@ -0,0 +1,90 @@
+===========================
+Outputting PDFs with Django
+===========================
+
+This document explains how to output PDF files dynamically using Django views.
+This is made possible by the excellent, open-source ReportLab_ Python PDF
+library.
+
+The advantage of generating PDF files dynamically is that you can create
+customzed PDFs for different purposes -- say, for different users or different
+pieces of content.
+
+For example, Django was used at kusports.com to generate customized,
+printer-friendly NCAA tournament brackets, as PDF files, for people
+participating in a March Madness contest.
+
+.. _ReportLab: http://www.reportlab.org/rl_toolkit.html
+
+Install ReportLab
+=================
+
+Download and install the ReportLab library from http://www.reportlab.org/downloads.html.
+The `user guide`_ (not coincidentally, a PDF file) explains how to install it.
+
+Test your installation by importing it in the Python interactive interpreter::
+
+ >>> import reportlab
+
+If that command doesn't raise any errors, the installation worked.
+
+.. _user guide: http://www.reportlab.org/rsrc/userguide.pdf
+
+Write your view
+===============
+
+The key to generating PDFs dynamically with Django is that the ReportLab API
+acts on file-like objects, and Django's ``HttpResponse`` objects are file-like
+objects.
+
+.. admonition:: Note
+
+ For more information on ``HttpResponse`` objects, see
+ `Request and response objects`_.
+
+ .. _Request and response objects: http://www.djangoproject.com/documentation/request_response/
+
+Here's a "Hello World" example::
+
+ from reportlab.pdfgen import canvas
+ from django.utils.httpwrappers import HttpResponse
+
+ def some_view(request):
+ # Create the HttpResponse object with the appropriate PDF headers.
+ response = HttpResponse(mimetype='application/pdf')
+ response['Content-Disposition'] = 'attachment; filename=somefilename.pdf'
+
+ # Create the PDF object, using the response object as its "file."
+ p = canvas.Canvas(response)
+
+ # Draw things on the PDF. Here's where the PDF generation happens.
+ # See the ReportLab documentation for the full list of functionality.
+ p.drawString(100, 100, "Hello world.")
+
+ # Close the PDF object cleanly, and we're done.
+ p.showPage()
+ p.save()
+ return response
+
+The code and comments should be self-explanatory, but a few things deserve a
+mention:
+
+ * The response gets a special mimetype, ``application/pdf``. This tells
+ browsers that the document is a PDF file, rather than an HTML file. If
+ you leave this off, browsers will probably interpret the output as HTML,
+ which would result in ugly, scary gobbledygook in the browser window.
+
+ * The response gets an additional ``Content-Disposition`` header, which
+ contains the name of the PDF file. This filename is arbitrary: Call it
+ whatever you want. It'll be used by browsers in the "Save as..."
+ dialogue, etc.
+
+ * Hooking into the ReportLab API is easy: Just pass ``response`` as the
+ first argument to ``canvas.Canvas``. The ``Canvas`` class expects a
+ file-like object, and ``HttpResponse`` objects fit the bill.
+
+ * Note that all subsequent PDF-generation methods are called on the PDF
+ object (in this case, ``p``) -- not on ``response``.
+
+ * Finally, it's important to call ``showPage()`` and ``save()`` on the PDF
+ file.