From cfe2e95529253eb23145aeb7621e2fa1b140e320 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Sun, 2 Oct 2005 17:55:03 +0000 Subject: [PATCH] Merge to r764. Fix 684, also allow overloading indivual fields templates in the admin. git-svn-id: http://code.djangoproject.com/svn/django/branches/new-admin@765 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/defaulttags.py | 8 ++- django/core/meta/__init__.py | 9 +-- django/templatetags/admin_modify.py | 52 +++++++++-------- docs/outputting_pdf.txt | 90 +++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 32 deletions(-) create mode 100644 docs/outputting_pdf.txt 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 = ['
'] - -# if not is_popup: -# if has_delete_permission and (change or show_delete): -# t.append('

Delete

') -# if change and save_as: -# t.append('' % onclick_attrib) -# if (not save_as or add): -# t.append('' % onclick_attrib) -# t.append('' % onclick_attrib ) -# t.append('' % onclick_attrib) -# t.append('
\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.