mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	newforms-admin: Merged to [4502]
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@4503 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		| @@ -7,6 +7,7 @@ a list of all possible variables. | ||||
| """ | ||||
|  | ||||
| import os | ||||
| import time     # Needed for Windows | ||||
| from django.conf import global_settings | ||||
|  | ||||
| ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" | ||||
| @@ -105,8 +106,10 @@ class Settings(object): | ||||
|                 new_installed_apps.append(app) | ||||
|         self.INSTALLED_APPS = new_installed_apps | ||||
|  | ||||
|         # move the time zone info into os.environ | ||||
|         os.environ['TZ'] = self.TIME_ZONE | ||||
|         if hasattr(time, 'tzset'): | ||||
|             # Move the time zone info into os.environ. See ticket #2315 for why | ||||
|             # we don't do this unconditionally (breaks Windows). | ||||
|             os.environ['TZ'] = self.TIME_ZONE | ||||
|  | ||||
|     def get_all_members(self): | ||||
|         return dir(self) | ||||
|   | ||||
| @@ -18,6 +18,8 @@ DATABASE_PORT = ''             # Set to empty string for default. Not used with | ||||
|  | ||||
| # Local time zone for this installation. All choices can be found here: | ||||
| # http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE | ||||
| # If running in a Windows environment this must be set to the same as your | ||||
| # system time zone. | ||||
| TIME_ZONE = 'America/Chicago' | ||||
|  | ||||
| # Language code for this installation. All choices can be found here: | ||||
|   | ||||
| @@ -4,6 +4,7 @@ from django.contrib.sites.models import Site | ||||
| from django.template import Context, loader | ||||
| from django.core import validators | ||||
| from django import oldforms | ||||
| from django.utils.translation import gettext as _ | ||||
|  | ||||
| class UserCreationForm(oldforms.Manipulator): | ||||
|     "A form that creates a user, with no privileges, from the given username and password." | ||||
|   | ||||
| @@ -78,6 +78,7 @@ class Feed(object): | ||||
|             author_link = self.__get_dynamic_attr('author_link', obj), | ||||
|             author_email = self.__get_dynamic_attr('author_email', obj), | ||||
|             categories = self.__get_dynamic_attr('categories', obj), | ||||
|             feed_copyright = self.__get_dynamic_attr('feed_copyright', obj), | ||||
|         ) | ||||
|  | ||||
|         try: | ||||
| @@ -116,5 +117,6 @@ class Feed(object): | ||||
|                 author_email = author_email, | ||||
|                 author_link = author_link, | ||||
|                 categories = self.__get_dynamic_attr('item_categories', item), | ||||
|                 item_copyright = self.__get_dynamic_attr('item_copyright', item), | ||||
|             ) | ||||
|         return feed | ||||
|   | ||||
| @@ -50,4 +50,9 @@ class LazyDate(object): | ||||
|         return (datetime.datetime.now() + self.delta).date() | ||||
|  | ||||
|     def __getattr__(self, attr): | ||||
|         if attr == 'delta': | ||||
|             # To fix ticket #3377. Note that normal accesses to LazyDate.delta | ||||
|             # (after construction) will still work, because they don't go | ||||
|             # through __getattr__). This is mainly needed for unpickling. | ||||
|             raise AttributeError | ||||
|         return getattr(self.__get_value__(), attr) | ||||
|   | ||||
| @@ -167,17 +167,16 @@ class QuerySet(object): | ||||
|  | ||||
|     def iterator(self): | ||||
|         "Performs the SELECT database lookup of this QuerySet." | ||||
|         try: | ||||
|             select, sql, params = self._get_sql_clause() | ||||
|         except EmptyResultSet: | ||||
|             raise StopIteration | ||||
|  | ||||
|         # self._select is a dictionary, and dictionaries' key order is | ||||
|         # undefined, so we convert it to a list of tuples. | ||||
|         extra_select = self._select.items() | ||||
|  | ||||
|         cursor = connection.cursor() | ||||
|          | ||||
|         try: | ||||
|             select, sql, params = self._get_sql_clause() | ||||
|         except EmptyResultSet: | ||||
|             raise StopIteration | ||||
|              | ||||
|         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) | ||||
|         fill_cache = self._select_related | ||||
|         index_end = len(self.model._meta.fields) | ||||
| @@ -198,9 +197,12 @@ class QuerySet(object): | ||||
|         "Performs a SELECT COUNT() and returns the number of records as an integer." | ||||
|         counter = self._clone() | ||||
|         counter._order_by = () | ||||
|         counter._select_related = False | ||||
|          | ||||
|         offset = counter._offset | ||||
|         limit = counter._limit | ||||
|         counter._offset = None | ||||
|         counter._limit = None | ||||
|         counter._select_related = False | ||||
|          | ||||
|         try: | ||||
|             select, sql, params = counter._get_sql_clause() | ||||
| @@ -214,7 +216,16 @@ class QuerySet(object): | ||||
|             cursor.execute("SELECT COUNT(DISTINCT(%s))" % id_col + sql, params) | ||||
|         else: | ||||
|             cursor.execute("SELECT COUNT(*)" + sql, params) | ||||
|         return cursor.fetchone()[0] | ||||
|         count = cursor.fetchone()[0] | ||||
|  | ||||
|         # Apply any offset and limit constraints manually, since using LIMIT or | ||||
|         # OFFSET in SQL doesn't change the output of COUNT. | ||||
|         if offset: | ||||
|             count = max(0, count - offset) | ||||
|         if limit: | ||||
|             count = min(limit, count) | ||||
|  | ||||
|         return count | ||||
|  | ||||
|     def get(self, *args, **kwargs): | ||||
|         "Performs the SELECT and returns a single object matching the given keyword arguments." | ||||
| @@ -523,11 +534,18 @@ class QuerySet(object): | ||||
|         return select, " ".join(sql), params | ||||
|  | ||||
| class ValuesQuerySet(QuerySet): | ||||
|     def iterator(self): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(ValuesQuerySet, self).__init__(*args, **kwargs) | ||||
|         # select_related and select aren't supported in values(). | ||||
|         self._select_related = False | ||||
|         self._select = {} | ||||
|  | ||||
|     def iterator(self): | ||||
|         try: | ||||
|             select, sql, params = self._get_sql_clause() | ||||
|         except EmptyResultSet: | ||||
|             raise StopIteration | ||||
|  | ||||
|         # self._fields is a list of field names to fetch. | ||||
|         if self._fields: | ||||
|             columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields] | ||||
| @@ -535,15 +553,9 @@ class ValuesQuerySet(QuerySet): | ||||
|         else: # Default to all fields. | ||||
|             columns = [f.column for f in self.model._meta.fields] | ||||
|             field_names = [f.attname for f in self.model._meta.fields] | ||||
|  | ||||
|         cursor = connection.cursor() | ||||
|          | ||||
|         try: | ||||
|             select, sql, params = self._get_sql_clause() | ||||
|         except EmptyResultSet: | ||||
|             raise StopIteration | ||||
|          | ||||
|         select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] | ||||
|         cursor = connection.cursor() | ||||
|         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) | ||||
|         while 1: | ||||
|             rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) | ||||
| @@ -592,9 +604,6 @@ class EmptyQuerySet(QuerySet): | ||||
|         super(EmptyQuerySet, self).__init__(model) | ||||
|         self._result_cache = [] | ||||
|          | ||||
|     def iterator(self): | ||||
|         raise StopIteration | ||||
|          | ||||
|     def count(self): | ||||
|         return 0 | ||||
|          | ||||
| @@ -606,6 +615,9 @@ class EmptyQuerySet(QuerySet): | ||||
|         c._result_cache = [] | ||||
|         return c | ||||
|  | ||||
|     def _get_sql_clause(self): | ||||
|         raise EmptyResultSet | ||||
|  | ||||
| class QOperator(object): | ||||
|     "Base class for QAnd and QOr" | ||||
|     def __init__(self, *args): | ||||
| @@ -881,8 +893,14 @@ def lookup_inner(path, lookup_type, value, opts, table, column): | ||||
|                 new_opts = field.rel.to._meta | ||||
|                 new_column = new_opts.pk.column | ||||
|                 join_column = field.column | ||||
|  | ||||
|             raise FieldFound | ||||
|                 raise FieldFound | ||||
|             elif path: | ||||
|                 # For regular fields, if there are still items on the path, | ||||
|                 # an error has been made. We munge "name" so that the error | ||||
|                 # properly identifies the cause of the problem. | ||||
|                 name += LOOKUP_SEPARATOR + path[0] | ||||
|             else: | ||||
|                 raise FieldFound | ||||
|  | ||||
|     except FieldFound: # Match found, loop has been shortcut. | ||||
|         pass | ||||
|   | ||||
| @@ -68,7 +68,10 @@ class RelatedObject(object): | ||||
|                 # object | ||||
|                 return [attr] | ||||
|         else: | ||||
|             return [None] * self.field.rel.num_in_admin | ||||
|             if self.field.rel.min_num_in_admin: | ||||
|                 return [None] * max(self.field.rel.num_in_admin, self.field.rel.min_num_in_admin) | ||||
|             else: | ||||
|                 return [None] * self.field.rel.num_in_admin | ||||
|  | ||||
|     def get_db_prep_lookup(self, lookup_type, value): | ||||
|         # Defer to the actual field definition for db prep | ||||
|   | ||||
| @@ -160,7 +160,7 @@ class HttpResponse(object): | ||||
|         self._charset = settings.DEFAULT_CHARSET | ||||
|         if not mimetype: | ||||
|             mimetype = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, settings.DEFAULT_CHARSET) | ||||
|         if hasattr(content, '__iter__'): | ||||
|         if not isinstance(content, basestring) and hasattr(content, '__iter__'): | ||||
|             self._container = content | ||||
|             self._is_string = False | ||||
|         else: | ||||
|   | ||||
| @@ -356,7 +356,7 @@ class ChoiceField(Field): | ||||
|             return value | ||||
|         valid_values = set([str(k) for k, v in self.choices]) | ||||
|         if value not in valid_values: | ||||
|             raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % value) | ||||
|             raise ValidationError(gettext(u'Select a valid choice. That choice is not one of the available choices.')) | ||||
|         return value | ||||
|  | ||||
| class MultipleChoiceField(ChoiceField): | ||||
|   | ||||
| @@ -117,8 +117,14 @@ class TemplateDoesNotExist(Exception): | ||||
|     pass | ||||
|  | ||||
| class VariableDoesNotExist(Exception): | ||||
|     pass | ||||
|  | ||||
|     def __init__(self, msg, params=()): | ||||
|         self.msg = msg | ||||
|         self.params = params | ||||
|      | ||||
|     def __str__(self): | ||||
|         return self.msg % self.params | ||||
|      | ||||
| class InvalidTemplateLibrary(Exception): | ||||
|     pass | ||||
|  | ||||
| @@ -660,7 +666,7 @@ def resolve_variable(path, context): | ||||
|                     try: # list-index lookup | ||||
|                         current = current[int(bits[0])] | ||||
|                     except (IndexError, ValueError, KeyError): | ||||
|                         raise VariableDoesNotExist, "Failed lookup for key [%s] in %r" % (bits[0], current) # missing attribute | ||||
|                         raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bits[0], current)) # missing attribute | ||||
|                 except Exception, e: | ||||
|                     if getattr(e, 'silent_variable_failure', False): | ||||
|                         current = settings.TEMPLATE_STRING_IF_INVALID | ||||
|   | ||||
| @@ -49,6 +49,9 @@ class Context(object): | ||||
|                 return True | ||||
|         return False | ||||
|  | ||||
|     def __contains__(self, key): | ||||
|         return self.has_key(key) | ||||
|  | ||||
|     def get(self, key, otherwise=None): | ||||
|         for d in self.dicts: | ||||
|             if d.has_key(key): | ||||
|   | ||||
| @@ -119,6 +119,21 @@ def truncatewords(value, arg): | ||||
|         value = str(value) | ||||
|     return truncate_words(value, length) | ||||
|  | ||||
| def truncatewords_html(value, arg): | ||||
|     """ | ||||
|     Truncates HTML after a certain number of words | ||||
|  | ||||
|     Argument: Number of words to truncate after | ||||
|     """ | ||||
|     from django.utils.text import truncate_html_words | ||||
|     try: | ||||
|         length = int(arg) | ||||
|     except ValueError: # invalid literal for int() | ||||
|         return value # Fail silently. | ||||
|     if not isinstance(value, basestring): | ||||
|         value = str(value) | ||||
|     return truncate_html_words(value, length) | ||||
|  | ||||
| def upper(value): | ||||
|     "Converts a string into all uppercase" | ||||
|     return value.upper() | ||||
| @@ -126,6 +141,8 @@ def upper(value): | ||||
| def urlencode(value): | ||||
|     "Escapes a value for use in a URL" | ||||
|     import urllib | ||||
|     if not isinstance(value, basestring): | ||||
|         value = str(value) | ||||
|     return urllib.quote(value) | ||||
|  | ||||
| def urlize(value): | ||||
| @@ -534,6 +551,7 @@ register.filter(timesince) | ||||
| register.filter(timeuntil) | ||||
| register.filter(title) | ||||
| register.filter(truncatewords) | ||||
| register.filter(truncatewords_html) | ||||
| register.filter(unordered_list) | ||||
| register.filter(upper) | ||||
| register.filter(urlencode) | ||||
|   | ||||
| @@ -315,6 +315,25 @@ class TemplateTagNode(Node): | ||||
|     def render(self, context): | ||||
|         return self.mapping.get(self.tagtype, '') | ||||
|  | ||||
| class URLNode(Node): | ||||
|     def __init__(self, view_name, args, kwargs): | ||||
|         self.view_name = view_name | ||||
|         self.args = args | ||||
|         self.kwargs = kwargs | ||||
|        | ||||
|     def render(self, context): | ||||
|         from django.core.urlresolvers import reverse, NoReverseMatch | ||||
|         args = [arg.resolve(context) for arg in self.args] | ||||
|         kwargs = dict([(k, v.resolve(context)) for k, v in self.kwargs.items()]) | ||||
|         try: | ||||
|             return reverse(self.view_name, args=args, kwargs=kwargs) | ||||
|         except NoReverseMatch: | ||||
|             try: | ||||
|                 project_name = settings.SETTINGS_MODULE.split('.')[0] | ||||
|                 return reverse(project_name + '.' + self.view_name, args=args, kwargs=kwargs) | ||||
|             except NoReverseMatch: | ||||
|                 return '' | ||||
|  | ||||
| class WidthRatioNode(Node): | ||||
|     def __init__(self, val_expr, max_expr, max_width): | ||||
|         self.val_expr = val_expr | ||||
| @@ -868,6 +887,50 @@ def templatetag(parser, token): | ||||
|     return TemplateTagNode(tag) | ||||
| templatetag = register.tag(templatetag) | ||||
|  | ||||
| def url(parser, token): | ||||
|     """ | ||||
|     Returns an absolute URL matching given view with its parameters. This is a | ||||
|     way to define links that aren't tied to a particular url configuration: | ||||
|      | ||||
|         {% url path.to.some_view arg1,arg2,name1=value1 %} | ||||
|      | ||||
|     The first argument is a path to a view. It can be an absolute python path | ||||
|     or just ``app_name.view_name`` without the project name if the view is | ||||
|     located inside the project.  Other arguments are comma-separated values | ||||
|     that will be filled in place of positional and keyword arguments in the | ||||
|     URL. All arguments for the URL should be present. | ||||
|  | ||||
|     For example if you have a view ``app_name.client`` taking client's id and | ||||
|     the corresponding line in a urlconf looks like this: | ||||
|      | ||||
|         ('^client/(\d+)/$', 'app_name.client') | ||||
|      | ||||
|     and this app's urlconf is included into the project's urlconf under some | ||||
|     path: | ||||
|      | ||||
|         ('^clients/', include('project_name.app_name.urls')) | ||||
|      | ||||
|     then in a template you can create a link for a certain client like this: | ||||
|      | ||||
|         {% url app_name.client client.id %} | ||||
|      | ||||
|     The URL will look like ``/clients/client/123/``. | ||||
|     """ | ||||
|     bits = token.contents.split(' ', 2) | ||||
|     if len(bits) < 2: | ||||
|         raise TemplateSyntaxError, "'%s' takes at least one argument (path to a view)" % bits[0] | ||||
|     args = [] | ||||
|     kwargs = {} | ||||
|     if len(bits) > 2: | ||||
|         for arg in bits[2].split(','): | ||||
|             if '=' in arg: | ||||
|                 k, v = arg.split('=', 1) | ||||
|                 kwargs[k] = parser.compile_filter(v) | ||||
|             else: | ||||
|                 args.append(parser.compile_filter(arg)) | ||||
|     return URLNode(bits[1], args, kwargs) | ||||
| url = register.tag(url) | ||||
|  | ||||
| #@register.tag | ||||
| def widthratio(parser, token): | ||||
|     """ | ||||
|   | ||||
| @@ -129,7 +129,7 @@ def do_block(parser, token): | ||||
|         parser.__loaded_blocks.append(block_name) | ||||
|     except AttributeError: # parser.__loaded_blocks isn't a list yet | ||||
|         parser.__loaded_blocks = [block_name] | ||||
|     nodelist = parser.parse(('endblock',)) | ||||
|     nodelist = parser.parse(('endblock', 'endblock %s' % block_name)) | ||||
|     parser.delete_first_token() | ||||
|     return BlockNode(block_name, nodelist) | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| import sys | ||||
| from cStringIO import StringIO | ||||
| from django.conf import settings | ||||
| from django.core.handlers.base import BaseHandler | ||||
| from django.core.handlers.wsgi import WSGIRequest | ||||
| from django.core.signals import got_request_exception | ||||
| from django.dispatch import dispatcher | ||||
| from django.http import urlencode, SimpleCookie | ||||
| from django.test import signals | ||||
| @@ -97,7 +100,16 @@ class Client: | ||||
|     def __init__(self, **defaults): | ||||
|         self.handler = ClientHandler() | ||||
|         self.defaults = defaults | ||||
|         self.cookie = SimpleCookie() | ||||
|         self.cookies = SimpleCookie() | ||||
|         self.session = {} | ||||
|         self.exc_info = None | ||||
|          | ||||
|     def store_exc_info(self, *args, **kwargs): | ||||
|         """ | ||||
|         Utility method that can be used to store exceptions when they are | ||||
|         generated by a view. | ||||
|         """ | ||||
|         self.exc_info = sys.exc_info() | ||||
|  | ||||
|     def request(self, **request): | ||||
|         """ | ||||
| @@ -108,7 +120,7 @@ class Client: | ||||
|         """ | ||||
|  | ||||
|         environ = { | ||||
|             'HTTP_COOKIE':      self.cookie, | ||||
|             'HTTP_COOKIE':      self.cookies, | ||||
|             'PATH_INFO':         '/', | ||||
|             'QUERY_STRING':      '', | ||||
|             'REQUEST_METHOD':    'GET', | ||||
| @@ -126,6 +138,9 @@ class Client: | ||||
|         on_template_render = curry(store_rendered_templates, data) | ||||
|         dispatcher.connect(on_template_render, signal=signals.template_rendered) | ||||
|  | ||||
|         # Capture exceptions created by the handler | ||||
|         dispatcher.connect(self.store_exc_info, signal=got_request_exception) | ||||
|  | ||||
|         response = self.handler(environ) | ||||
|  | ||||
|         # Add any rendered template detail to the response | ||||
| @@ -140,9 +155,20 @@ class Client: | ||||
|             else: | ||||
|                 setattr(response, detail, None) | ||||
|  | ||||
|         # Look for a signalled exception and reraise it | ||||
|         if self.exc_info: | ||||
|             raise self.exc_info[1], None, self.exc_info[2] | ||||
|          | ||||
|         # Update persistent cookie and session data | ||||
|         if response.cookies: | ||||
|             self.cookie.update(response.cookies) | ||||
|             self.cookies.update(response.cookies) | ||||
|  | ||||
|         if 'django.contrib.sessions' in settings.INSTALLED_APPS: | ||||
|             from django.contrib.sessions.middleware import SessionWrapper | ||||
|             cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None) | ||||
|             if cookie: | ||||
|                 self.session = SessionWrapper(cookie.value) | ||||
|              | ||||
|         return response | ||||
|  | ||||
|     def get(self, path, data={}, **extra): | ||||
|   | ||||
| @@ -40,7 +40,7 @@ class SyndicationFeed(object): | ||||
|     "Base class for all syndication feeds. Subclasses should provide write()" | ||||
|     def __init__(self, title, link, description, language=None, author_email=None, | ||||
|             author_name=None, author_link=None, subtitle=None, categories=None, | ||||
|             feed_url=None): | ||||
|             feed_url=None, feed_copyright=None): | ||||
|         self.feed = { | ||||
|             'title': title, | ||||
|             'link': link, | ||||
| @@ -52,12 +52,13 @@ class SyndicationFeed(object): | ||||
|             'subtitle': subtitle, | ||||
|             'categories': categories or (), | ||||
|             'feed_url': feed_url, | ||||
|             'feed_copyright': feed_copyright, | ||||
|         } | ||||
|         self.items = [] | ||||
|  | ||||
|     def add_item(self, title, link, description, author_email=None, | ||||
|         author_name=None, author_link=None, pubdate=None, comments=None, | ||||
|         unique_id=None, enclosure=None, categories=()): | ||||
|         unique_id=None, enclosure=None, categories=(), item_copyright=None): | ||||
|         """ | ||||
|         Adds an item to the feed. All args are expected to be Python Unicode | ||||
|         objects except pubdate, which is a datetime.datetime object, and | ||||
| @@ -75,6 +76,7 @@ class SyndicationFeed(object): | ||||
|             'unique_id': unique_id, | ||||
|             'enclosure': enclosure, | ||||
|             'categories': categories or (), | ||||
|             'item_copyright': item_copyright, | ||||
|         }) | ||||
|  | ||||
|     def num_items(self): | ||||
| @@ -128,6 +130,8 @@ class RssFeed(SyndicationFeed): | ||||
|             handler.addQuickElement(u"language", self.feed['language']) | ||||
|         for cat in self.feed['categories']: | ||||
|             handler.addQuickElement(u"category", cat) | ||||
|         if self.feed['feed_copyright'] is not None: | ||||
|             handler.addQuickElement(u"copyright", self.feed['feed_copyright']) | ||||
|         self.write_items(handler) | ||||
|         self.endChannelElement(handler) | ||||
|         handler.endElement(u"rss") | ||||
| @@ -212,6 +216,8 @@ class Atom1Feed(SyndicationFeed): | ||||
|             handler.addQuickElement(u"subtitle", self.feed['subtitle']) | ||||
|         for cat in self.feed['categories']: | ||||
|             handler.addQuickElement(u"category", "", {u"term": cat}) | ||||
|         if self.feed['feed_copyright'] is not None: | ||||
|             handler.addQuickElement(u"rights", self.feed['feed_copyright']) | ||||
|         self.write_items(handler) | ||||
|         handler.endElement(u"feed") | ||||
|  | ||||
| @@ -252,10 +258,14 @@ class Atom1Feed(SyndicationFeed): | ||||
|                      u"length": item['enclosure'].length, | ||||
|                      u"type": item['enclosure'].mime_type}) | ||||
|  | ||||
|             # Categories: | ||||
|             # Categories. | ||||
|             for cat in item['categories']: | ||||
|                 handler.addQuickElement(u"category", u"", {u"term": cat}) | ||||
|  | ||||
|             # Rights. | ||||
|             if item['item_copyright'] is not None: | ||||
|                 handler.addQuickElement(u"rights", item['item_copyright']) | ||||
|  | ||||
|             handler.endElement(u"entry") | ||||
|  | ||||
| # This isolates the decision of what the system default is, so calling code can | ||||
|   | ||||
| @@ -41,6 +41,66 @@ def truncate_words(s, num): | ||||
|             words.append('...') | ||||
|     return ' '.join(words) | ||||
|  | ||||
| def truncate_html_words(s, num): | ||||
|     """ | ||||
|     Truncates html to a certain number of words (not counting tags and comments). | ||||
|     Closes opened tags if they were correctly closed in the given html. | ||||
|     """ | ||||
|     length = int(num) | ||||
|     if length <= 0: | ||||
|         return '' | ||||
|     html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input') | ||||
|     # Set up regular expressions | ||||
|     re_words = re.compile(r'&.*?;|<.*?>|([A-Za-z0-9][\w-]*)') | ||||
|     re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>') | ||||
|     # Count non-HTML words and keep note of open tags | ||||
|     pos = 0 | ||||
|     ellipsis_pos = 0 | ||||
|     words = 0 | ||||
|     open_tags = [] | ||||
|     while words <= length: | ||||
|         m = re_words.search(s, pos) | ||||
|         if not m: | ||||
|             # Checked through whole string | ||||
|             break | ||||
|         pos = m.end(0) | ||||
|         if m.group(1): | ||||
|             # It's an actual non-HTML word | ||||
|             words += 1 | ||||
|             if words == length: | ||||
|                 ellipsis_pos = pos | ||||
|             continue | ||||
|         # Check for tag | ||||
|         tag = re_tag.match(m.group(0)) | ||||
|         if not tag or ellipsis_pos: | ||||
|             # Don't worry about non tags or tags after our truncate point | ||||
|             continue | ||||
|         closing_tag, tagname, self_closing = tag.groups() | ||||
|         tagname = tagname.lower()  # Element names are always case-insensitive | ||||
|         if self_closing or tagname in html4_singlets: | ||||
|             pass | ||||
|         elif closing_tag: | ||||
|             # Check for match in open tags list | ||||
|             try: | ||||
|                 i = open_tags.index(tagname) | ||||
|             except ValueError: | ||||
|                 pass | ||||
|             else: | ||||
|                 # SGML: An end tag closes, back to the matching start tag, all unclosed intervening start tags with omitted end tags | ||||
|                 open_tags = open_tags[i+1:] | ||||
|         else: | ||||
|             # Add it to the start of the open tags list | ||||
|             open_tags.insert(0, tagname) | ||||
|     if words <= length: | ||||
|         # Don't try to close tags if we don't need to truncate | ||||
|         return s | ||||
|     out = s[:ellipsis_pos] + ' ...' | ||||
|     # Close any tags still open | ||||
|     for tag in open_tags: | ||||
|         out += '</%s>' % tag | ||||
|     # Return string | ||||
|     return out | ||||
|  | ||||
| def get_valid_filename(s): | ||||
|     """ | ||||
|     Returns the given string converted to a string that can be used for a clean | ||||
|   | ||||
		Reference in New Issue
	
	Block a user