1
0
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:
Adrian Holovaty
2007-02-13 16:15:10 +00:00
parent af908e2e8d
commit 649423e81c
42 changed files with 671 additions and 66 deletions

View File

@@ -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)

View File

@@ -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:

View File

@@ -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."

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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):

View File

@@ -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

View File

@@ -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):

View File

@@ -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)

View File

@@ -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):
"""

View File

@@ -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)

View File

@@ -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):

View File

@@ -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

View File

@@ -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