From 6e2b74d65e84938fdc05f2de40bcfe15efadc693 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Tue, 4 Oct 2005 19:34:43 +0000 Subject: [PATCH] i18n: merged r722:774 from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/i18n@775 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/admin_media/css/changelists.css | 6 +- django/conf/global_settings.py | 2 +- django/core/defaulttags.py | 7 ++ django/core/management.py | 17 ++-- django/core/rss.py | 7 +- django/views/admin/main.py | 2 +- django/views/generic/create_update.py | 11 ++- django/views/generic/date_based.py | 16 ++-- django/views/generic/list_detail.py | 6 +- docs/db-api.txt | 2 +- docs/model-api.txt | 23 +++++- docs/modpython.txt | 4 +- docs/outputting_pdf.txt | 90 +++++++++++++++++++++ docs/templates.txt | 4 + docs/templates_python.txt | 2 +- docs/tutorial01.txt | 12 +-- docs/tutorial03.txt | 4 +- tests/othertests/templates.py | 4 + 18 files changed, 182 insertions(+), 37 deletions(-) create mode 100644 docs/outputting_pdf.txt diff --git a/django/conf/admin_media/css/changelists.css b/django/conf/admin_media/css/changelists.css index 3c6e125e7d..966ca4a486 100644 --- a/django/conf/admin_media/css/changelists.css +++ b/django/conf/admin_media/css/changelists.css @@ -18,10 +18,10 @@ #changelist {position:relative; width:100%;} #changelist table {width:100%;} -.change-list .filtered table { border-right:1px solid #ddd; } +.change-list .filtered table { border-right:1px solid #ddd, width:100%; } .change-list .filtered {min-height:400px; _height:400px;} .change-list .filtered {background:white url(../img/admin/changelist-bg.gif) top right repeat-y !important;} -.change-list .filtered table, .filtered .paginator, .filtered #toolbar, .filtered div.xfull {margin-right:160px !important; width:auto !important; } +.change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull {margin-right:160px !important; width:auto !important; } .change-list .filtered table tbody th {padding-right:10px;} #changelist .toplinks {border-bottom:1px solid #ccc !important;} #changelist .paginator { color:#666; border-top:1px solid #eee; border-bottom:1px solid #eee; background:white url(../img/admin/nav-bg.gif) 0 180% repeat-x; overflow:hidden;} @@ -57,4 +57,4 @@ .change-list ul.toplinks {display:block; background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; border-top:1px solid white; float:left; padding:0 !important; margin:0 !important; width:100%;} .change-list ul.toplinks li {float: left; width: 9em; padding:3px 6px; font-weight: bold; list-style-type:none;} .change-list ul.toplinks .date-back a {color:#999;} -.change-list ul.toplinks .date-back a:hover {color:#036;} \ No newline at end of file +.change-list ul.toplinks .date-back a:hover {color:#036;} diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 03567b98c4..4204c7a25d 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -120,7 +120,7 @@ IGNORABLE_404_ENDS = ('mail.pl', 'mailform.pl', 'mail.cgi', 'mailform.cgi', 'fav SECRET_KEY = '' # Path to the "jing" executable -- needed to validate XMLFields -JING_PATH = "/usr/bin/jng" +JING_PATH = "/usr/bin/jing" ############## # MIDDLEWARE # diff --git a/django/core/defaulttags.py b/django/core/defaulttags.py index 5898352a10..b6575c8768 100644 --- a/django/core/defaulttags.py +++ b/django/core/defaulttags.py @@ -100,6 +100,9 @@ class ForNode(template.Node): # shortcuts for current loop iteration number 'counter0': i, 'counter': i+1, + # reverse counter iteration numbers + 'revcounter': len_values - i, + 'revcounter0': len_values - i - 1, # boolean values designating first and last times through loop 'first': (i == 0), 'last': (i == len_values - 1), @@ -471,6 +474,10 @@ def do_for(parser, token): ========================== ================================================ ``forloop.counter`` The current iteration of the loop (1-indexed) ``forloop.counter0`` The current iteration of the loop (0-indexed) + ``forloop.revcounter`` The number of iterations from the end of the + loop (1-indexed) + ``forloop.revcounter0`` The number of iterations from the end of the + loop (0-indexed) ``forloop.first`` True if this is the first time through the loop ``forloop.last`` True if this is the last time through the loop ``forloop.parentloop`` For nested loops, this is the loop "above" the diff --git a/django/core/management.py b/django/core/management.py index d494564d6b..f3accd84cc 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -156,18 +156,23 @@ get_sql_reset.args = APP_ARGS def get_sql_initial_data(mod): "Returns a list of the initial INSERT SQL statements for the given module." + from django.core import db output = [] app_label = mod._MODELS[0]._meta.app_label output.append(_get_packages_insert(app_label)) app_dir = os.path.normpath(os.path.join(os.path.dirname(mod.__file__), '../sql')) for klass in mod._MODELS: opts = klass._meta + # Add custom SQL, if it's available. - sql_file_name = os.path.join(app_dir, opts.module_name + '.sql') - if os.path.exists(sql_file_name): - fp = open(sql_file_name, 'r') - output.append(fp.read()) - fp.close() + sql_files = [os.path.join(app_dir, opts.module_name + '.' + db.DATABASE_ENGINE + '.sql'), + os.path.join(app_dir, opts.module_name + '.sql')] + for sql_file in sql_files: + if os.path.exists(sql_file): + fp = open(sql_file) + output.append(fp.read()) + fp.close() + # Content types. output.append(_get_contenttype_insert(opts)) # Permissions. @@ -653,4 +658,4 @@ def createcachetable(tablename): for statement in index_output: curs.execute(statement) db.db.commit() -createcachetable.args = "[tablename]" \ No newline at end of file +createcachetable.args = "[tablename]" diff --git a/django/core/rss.py b/django/core/rss.py index a381a9cd78..cd2bcf4ad1 100644 --- a/django/core/rss.py +++ b/django/core/rss.py @@ -7,7 +7,7 @@ from django.conf.settings import LANGUAGE_CODE, SETTINGS_MODULE class FeedConfiguration: def __init__(self, slug, title_cb, link_cb, description_cb, get_list_func_cb, get_list_kwargs, - param_func=None, param_kwargs_cb=None, get_list_kwargs_cb=None, + param_func=None, param_kwargs_cb=None, get_list_kwargs_cb=None, get_pubdate_cb=None, enc_url=None, enc_length=None, enc_mime_type=None): """ slug -- Normal Python string. Used to register the feed. @@ -28,6 +28,9 @@ class FeedConfiguration: get_list_kwargs_cb -- Function that takes the param and returns a dictionary to use in addition to get_list_kwargs (if applicable). + + get_pubdate_cb -- Function that takes the object and returns a datetime + to use as the publication date in the feed. The three enc_* parameters are strings representing methods or attributes to call on a particular item to get its enclosure @@ -41,6 +44,7 @@ class FeedConfiguration: self.get_list_kwargs = get_list_kwargs self.param_func, self.param_kwargs_cb = param_func, param_kwargs_cb self.get_list_kwargs_cb = get_list_kwargs_cb + self.get_pubdate_cb = get_pubdate_cb assert (None == enc_url == enc_length == enc_mime_type) or (enc_url is not None and enc_length is not None and enc_mime_type is not None) self.enc_url = enc_url self.enc_length = enc_length @@ -95,6 +99,7 @@ class FeedConfiguration: description = description_template.render(Context({'obj': obj, 'site': current_site})).decode('utf-8'), unique_id=link, enclosure=enc, + pubdate = self.get_pubdate_cb and self.get_pubdate_cb(obj) or None, ) return f diff --git a/django/views/admin/main.py b/django/views/admin/main.py index a6109a46cd..9283230b26 100644 --- a/django/views/admin/main.py +++ b/django/views/admin/main.py @@ -523,7 +523,7 @@ def _get_submit_row_template(opts, app_label, add, change, show_delete, ordered_ if not opts.admin.save_as or add: t.append('{%% if not is_popup %%}{%% endif %%}' % \ (ordered_objects and change and 'onclick="submitOrderForm();"' or '')) - t.append('' % \ + t.append('{%% if not is_popup %%}{%% endif %%}' % \ (ordered_objects and change and 'onclick="submitOrderForm();"' or '')) t.append('' % \ (ordered_objects and change and 'onclick="submitOrderForm();"' or '')) diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py index d1775d61e9..f06e1d57e5 100644 --- a/django/views/generic/create_update.py +++ b/django/views/generic/create_update.py @@ -8,7 +8,8 @@ from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect from django.core.exceptions import Http404, ObjectDoesNotExist, ImproperlyConfigured def create_object(request, app_label, module_name, template_name=None, - extra_context={}, post_save_redirect=None, login_required=False): + template_loader=template_loader, extra_context={}, + post_save_redirect=None, login_required=False): """ Generic object-creation function. @@ -65,8 +66,9 @@ def create_object(request, app_label, module_name, template_name=None, return HttpResponse(t.render(c)) def update_object(request, app_label, module_name, object_id=None, slug=None, - slug_field=None, template_name=None, extra_lookup_kwargs={}, - extra_context={}, post_save_redirect=None, login_required=False): + slug_field=None, template_name=None, template_loader=template_loader, + extra_lookup_kwargs={}, extra_context={}, post_save_redirect=None, + login_required=False): """ Generic object-update function. @@ -139,7 +141,8 @@ def update_object(request, app_label, module_name, object_id=None, slug=None, def delete_object(request, app_label, module_name, post_delete_redirect, object_id=None, slug=None, slug_field=None, template_name=None, - extra_lookup_kwargs={}, extra_context={}, login_required=False): + template_loader=template_loader, extra_lookup_kwargs={}, + extra_context={}, login_required=False): """ Generic object-delete function. diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py index 0e726f2a78..5dc9892894 100644 --- a/django/views/generic/date_based.py +++ b/django/views/generic/date_based.py @@ -7,7 +7,8 @@ from django.utils.httpwrappers import HttpResponse import datetime, time def archive_index(request, app_label, module_name, date_field, num_latest=15, - template_name=None, extra_lookup_kwargs={}, extra_context={}): + template_name=None, template_loader=template_loader, + extra_lookup_kwargs={}, extra_context={}): """ Generic top-level archive of date-based objects. @@ -49,7 +50,8 @@ def archive_index(request, app_label, module_name, date_field, num_latest=15, return HttpResponse(t.render(c)) def archive_year(request, year, app_label, module_name, date_field, - template_name=None, extra_lookup_kwargs={}, extra_context={}): + template_name=None, template_loader=template_loader, + extra_lookup_kwargs={}, extra_context={}): """ Generic yearly archive view. @@ -85,8 +87,8 @@ def archive_year(request, year, app_label, module_name, date_field, return HttpResponse(t.render(c)) def archive_month(request, year, month, app_label, module_name, date_field, - month_format='%b', template_name=None, extra_lookup_kwargs={}, - extra_context={}): + month_format='%b', template_name=None, template_loader=template_loader, + extra_lookup_kwargs={}, extra_context={}): """ Generic monthly archive view. @@ -138,7 +140,8 @@ def archive_month(request, year, month, app_label, module_name, date_field, def archive_day(request, year, month, day, app_label, module_name, date_field, month_format='%b', day_format='%d', template_name=None, - extra_lookup_kwargs={}, extra_context={}, allow_empty=False): + template_loader=template_loader, extra_lookup_kwargs={}, + extra_context={}, allow_empty=False): """ Generic daily archive view. @@ -201,7 +204,8 @@ def archive_today(request, **kwargs): def object_detail(request, year, month, day, app_label, module_name, date_field, month_format='%b', day_format='%d', object_id=None, slug=None, slug_field=None, template_name=None, template_name_field=None, - extra_lookup_kwargs={}, extra_context={}): + template_loader=template_loader, extra_lookup_kwargs={}, + extra_context={}): """ Generic detail view from year/month/day/slug or year/month/day/id structure. diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py index 373aef3e18..6328b7097a 100644 --- a/django/views/generic/list_detail.py +++ b/django/views/generic/list_detail.py @@ -7,7 +7,8 @@ from django.core.paginator import ObjectPaginator, InvalidPage from django.core.exceptions import Http404, ObjectDoesNotExist def object_list(request, app_label, module_name, paginate_by=None, allow_empty=False, - template_name=None, extra_lookup_kwargs={}, extra_context={}): + template_name=None, template_loader=template_loader, + extra_lookup_kwargs={}, extra_context={}): """ Generic list of objects. @@ -76,7 +77,8 @@ def object_list(request, app_label, module_name, paginate_by=None, allow_empty=F def object_detail(request, app_label, module_name, object_id=None, slug=None, slug_field=None, template_name=None, template_name_field=None, - extra_lookup_kwargs={}, extra_context={}): + template_loader=template_loader, extra_lookup_kwargs={}, + extra_context={}): """ Generic list of objects. diff --git a/docs/db-api.txt b/docs/db-api.txt index 4c49a2760f..e0885da8f0 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -524,7 +524,7 @@ model with an ``ImageField`` will also get this method. get_FOO_url() ------------- -For every ``FileField``, the object will have a ``get_FOO_filename()`` method, +For every ``FileField``, the object will have a ``get_FOO_url()`` method, where ``FOO`` is the name of the field. This returns the full URL to the file, according to your ``MEDIA_URL`` setting. If the value is blank, this method returns an empty string. diff --git a/docs/model-api.txt b/docs/model-api.txt index 918f4eb5db..4af193ca48 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -248,7 +248,28 @@ Here are all available field types: uploaded files don't fill up the given directory). The admin represents this as an ```` (a file-upload widget). + + Using a `FieldField` or an ``ImageField`` (see below) in a model takes a few + steps: + + 1. In your settings file, you'll need to define ``MEDIA_ROOT``as the + full path to a directory where you'd like Django to store uploaded + files. (For performance, these files are not stored in the database.) + Define ``MEDIA_URL`` as the base public URL of that directory. Make + sure that this directory is writable by the Web server's user + account. + + 2. Add the ``FileField`` or ``ImageField`` to your model, making sure + to define the ``upload_to`` option to tell Django to which + subdirectory of ``MEDIA_ROOT`` it should upload files. + 3. All that will be stored in your database is a path to the file + (relative to ``MEDIA_ROOT``). You'll must likely want to use the + convenience ``get__url`` function provided by Django. For + example, if your ``ImageField`` is called ``mug_shot``, you can get + the absolute URL to your image in a template with ``{{ + object.get_mug_shot_url }}``. + .. _`strftime formatting`: http://docs.python.org/lib/module-time.html#l2h-1941 ``FloatField`` @@ -281,7 +302,7 @@ Here are all available field types: width of the image each time a model instance is saved. Requires the `Python Imaging Library`_. - + .. _Python Imaging Library: http://www.pythonware.com/products/pil/ ``IntegerField`` diff --git a/docs/modpython.txt b/docs/modpython.txt index 72c2d9a073..acac50a6d5 100644 --- a/docs/modpython.txt +++ b/docs/modpython.txt @@ -29,8 +29,8 @@ Then edit your ``httpd.conf`` file and add the following:: PythonDebug On -...and replace ``myproject.settings.main`` with the path to your settings file, -in dotted-package syntax. +...and replace ``myproject.settings.main`` with the Python path to your +settings file. This tells Apache: "Use mod_python for any URL at or under '/mysite/', using the Django mod_python handler." It passes the value of ``DJANGO_SETTINGS_MODULE`` diff --git a/docs/outputting_pdf.txt b/docs/outputting_pdf.txt new file mode 100644 index 0000000000..bd209b2e90 --- /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 +customized 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. diff --git a/docs/templates.txt b/docs/templates.txt index 09431c1dda..a6848a9638 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -376,6 +376,10 @@ Built-in tag reference ========================== ================================================ ``forloop.counter`` The current iteration of the loop (1-indexed) ``forloop.counter0`` The current iteration of the loop (0-indexed) + ``forloop.revcounter`` The number of iterations from the end of the + loop (1-indexed) + ``forloop.revcounter0`` The number of iterations from the end of the + loop (0-indexed) ``forloop.first`` True if this is the first time through the loop ``forloop.last`` True if this is the last time through the loop ``forloop.parentloop`` For nested loops, this is the loop "above" the diff --git a/docs/templates_python.txt b/docs/templates_python.txt index 63b734dc44..39b768429b 100644 --- a/docs/templates_python.txt +++ b/docs/templates_python.txt @@ -267,7 +267,7 @@ every template automatic access to the current time, use something like this:: from django.core.template import Context import datetime - class TimeContext(template.Context): + class TimeContext(Context): def __init__(self, *args, **kwargs): Context.__init__(self, *args, **kwargs) self['current_time'] = datetime.datetime.now() diff --git a/docs/tutorial01.txt b/docs/tutorial01.txt index 629a2ab017..d69afa7392 100644 --- a/docs/tutorial01.txt +++ b/docs/tutorial01.txt @@ -385,23 +385,23 @@ Let's jump back into the Python interactive shell:: # Django provides a rich database lookup API that's entirely driven by # keyword arguments. >>> polls.get_object(id__exact=1) - What's up + What's up? >>> polls.get_object(question__startswith='What') - What's up + What's up? >>> polls.get_object(pub_date__year=2005) - What's up + What's up? >>> polls.get_object(id__exact=2) Traceback (most recent call last): ... PollDoesNotExist: Poll does not exist for {'id__exact': 2} >>> polls.get_list(question__startswith='What') - [What's up] + [What's up?] # Lookup by a primary key is the most common case, so Django provides a # shortcut for primary-key exact lookups. # The following is identical to polls.get_object(id__exact=1). >>> polls.get_object(pk=1) - What's up + What's up? # Make sure our custom method worked. >>> p = polls.get_object(pk=1) @@ -419,7 +419,7 @@ Let's jump back into the Python interactive shell:: # Choice objects have API access to their related Poll objects. >>> c.get_poll() - What's up + What's up? # And vice versa: Poll objects get access to Choice objects. >>> p.get_choice_list() diff --git a/docs/tutorial03.txt b/docs/tutorial03.txt index 2bbd8d7782..84fb64abfe 100644 --- a/docs/tutorial03.txt +++ b/docs/tutorial03.txt @@ -91,8 +91,8 @@ Finally, it calls that ``detail()`` function like so:: detail(request=, poll_id=23) The ``poll_id=23`` part comes from ``(?P\d+)``. Using -``(?pattern)`` "captures" the text matched by ``pattern`` and sends it as -a keyword argument to the view function. +``(?Ppattern)`` "captures" the text matched by ``pattern`` and sends it +as a keyword argument to the view function. Because the URL patterns are regular expressions, there really is no limit on what you can do with them. And there's no need to add URL cruft such as diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py index 9b70230537..e73163aabc 100644 --- a/tests/othertests/templates.py +++ b/tests/othertests/templates.py @@ -108,6 +108,10 @@ TEMPLATE_TESTS = { ### FOR TAG ############################################################### 'for-tag01': ("{% for val in values %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "123"), 'for-tag02': ("{% for val in values reversed %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "321"), + 'for-tag-vars01': ("{% for val in values %}{{ forloop.counter }}{% endfor %}", {"values": [6, 6, 6]}, "123"), + 'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"), + 'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"), + 'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"), ### IFEQUAL TAG ########################################################### 'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),