Compare commits
15 Commits
main
...
stable/0.9
Author | SHA1 | Date |
---|---|---|
James Bennett | f4afec9893 | |
Jacob Kaplan-Moss | aee48854a1 | |
James Bennett | 156e3653bd | |
James Bennett | 50ce7fb57d | |
Jacob Kaplan-Moss | 412ed22502 | |
James Bennett | 3fc271871f | |
James Bennett | b0ea21299b | |
James Bennett | 4fa6c90110 | |
James Bennett | 3080f4a36f | |
James Bennett | 027afbcb00 | |
James Bennett | 154b64dcf8 | |
James Bennett | e89f0a6558 | |
James Bennett | a132d411c6 | |
James Bennett | 69fb4babc3 | |
Adrian Holovaty | 0b353abe67 |
|
@ -1 +1 @@
|
|||
VERSION = (0, 95, None)
|
||||
VERSION = (0, 95.4, None)
|
||||
|
|
|
@ -19,7 +19,17 @@ def compile_messages():
|
|||
if f.endswith('.po'):
|
||||
sys.stderr.write('processing file %s in %s\n' % (f, dirpath))
|
||||
pf = os.path.splitext(os.path.join(dirpath, f))[0]
|
||||
cmd = 'msgfmt -o "%s.mo" "%s.po"' % (pf, pf)
|
||||
# Store the names of the .mo and .po files in an environment
|
||||
# variable, rather than doing a string replacement into the
|
||||
# command, so that we can take advantage of shell quoting, to
|
||||
# quote any malicious characters/escaping.
|
||||
# See http://cyberelk.net/tim/articles/cmdline/ar01s02.html
|
||||
os.environ['djangocompilemo'] = pf + '.mo'
|
||||
os.environ['djangocompilepo'] = pf + '.po'
|
||||
if sys.platform == 'win32': # Different shell-variable syntax
|
||||
cmd = 'msgfmt -o "%djangocompilemo%" "%djangocompilepo%"'
|
||||
else:
|
||||
cmd = 'msgfmt -o "$djangocompilemo" "$djangocompilepo"'
|
||||
os.system(cmd)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
<div class="form-row">
|
||||
<label for="id_password">{% trans 'Password:' %}</label> <input type="password" name="password" id="id_password" />
|
||||
<input type="hidden" name="this_is_the_login_form" value="1" />
|
||||
<input type="hidden" name="post_data" value="{{ post_data }}" /> {% comment %}<span class="help">{% trans 'Have you <a href="/password_reset/">forgotten your password</a>?' %}</span>{% endcomment %}
|
||||
</div>
|
||||
<div class="submit-row">
|
||||
<label> </label><input type="submit" value="{% trans 'Log in' %}" />
|
||||
|
|
|
@ -3,43 +3,21 @@ from django.conf import settings
|
|||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.shortcuts import render_to_response
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import gettext_lazy
|
||||
import base64, datetime, md5
|
||||
import cPickle as pickle
|
||||
import base64, datetime
|
||||
|
||||
ERROR_MESSAGE = gettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.")
|
||||
LOGIN_FORM_KEY = 'this_is_the_login_form'
|
||||
|
||||
def _display_login_form(request, error_message=''):
|
||||
request.session.set_test_cookie()
|
||||
if request.POST and request.POST.has_key('post_data'):
|
||||
# User has failed login BUT has previously saved post data.
|
||||
post_data = request.POST['post_data']
|
||||
elif request.POST:
|
||||
# User's session must have expired; save their post data.
|
||||
post_data = _encode_post_data(request.POST)
|
||||
else:
|
||||
post_data = _encode_post_data({})
|
||||
return render_to_response('admin/login.html', {
|
||||
'title': _('Log in'),
|
||||
'app_path': request.path,
|
||||
'post_data': post_data,
|
||||
'app_path': escape(request.path),
|
||||
'error_message': error_message
|
||||
}, context_instance=template.RequestContext(request))
|
||||
|
||||
def _encode_post_data(post_data):
|
||||
pickled = pickle.dumps(post_data)
|
||||
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
|
||||
return base64.encodestring(pickled + pickled_md5)
|
||||
|
||||
def _decode_post_data(encoded_data):
|
||||
encoded_data = base64.decodestring(encoded_data)
|
||||
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
|
||||
if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
raise SuspiciousOperation, "User may have tampered with session cookie."
|
||||
return pickle.loads(pickled)
|
||||
|
||||
def staff_member_required(view_func):
|
||||
"""
|
||||
Decorator for views that checks that the user is logged in and is a staff
|
||||
|
@ -48,10 +26,6 @@ def staff_member_required(view_func):
|
|||
def _checklogin(request, *args, **kwargs):
|
||||
if request.user.is_authenticated() and request.user.is_staff:
|
||||
# The user is valid. Continue to the admin page.
|
||||
if request.POST.has_key('post_data'):
|
||||
# User must have re-authenticated through a different window
|
||||
# or tab.
|
||||
request.POST = _decode_post_data(request.POST['post_data'])
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
|
||||
|
@ -59,7 +33,7 @@ def staff_member_required(view_func):
|
|||
# If this isn't already the login page, display it.
|
||||
if not request.POST.has_key(LOGIN_FORM_KEY):
|
||||
if request.POST:
|
||||
message = _("Please log in again, because your session has expired. Don't worry: Your submission has been saved.")
|
||||
message = _("Please log in again, because your session has expired.")
|
||||
else:
|
||||
message = ""
|
||||
return _display_login_form(request, message)
|
||||
|
@ -92,16 +66,7 @@ def staff_member_required(view_func):
|
|||
# TODO: set last_login with an event.
|
||||
user.last_login = datetime.datetime.now()
|
||||
user.save()
|
||||
if request.POST.has_key('post_data'):
|
||||
post_data = _decode_post_data(request.POST['post_data'])
|
||||
if post_data and not post_data.has_key(LOGIN_FORM_KEY):
|
||||
# overwrite request.POST with the saved post_data, and continue
|
||||
request.POST = post_data
|
||||
request.user = user
|
||||
return view_func(request, *args, **kwargs)
|
||||
else:
|
||||
request.session.delete_test_cookie()
|
||||
return http.HttpResponseRedirect(request.path)
|
||||
return http.HttpResponseRedirect(request.path)
|
||||
else:
|
||||
return _display_login_form(request, ERROR_MESSAGE)
|
||||
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
class LazyUser(object):
|
||||
def __init__(self):
|
||||
self._user = None
|
||||
|
||||
def __get__(self, request, obj_type=None):
|
||||
if self._user is None:
|
||||
if not hasattr(request, '_cached_user'):
|
||||
from django.contrib.auth import get_user
|
||||
self._user = get_user(request)
|
||||
return self._user
|
||||
request._cached_user = get_user(request)
|
||||
return request._cached_user
|
||||
|
||||
class AuthenticationMiddleware(object):
|
||||
def process_request(self, request):
|
||||
|
|
|
@ -108,7 +108,9 @@ def runfastcgi(argset):
|
|||
wsgi_opts = {}
|
||||
else:
|
||||
return fastcgi_help("ERROR: Implementation must be one of prefork or thread.")
|
||||
|
||||
|
||||
wsgi_opts['debug'] = False # Turn off flup tracebacks
|
||||
|
||||
# Prep up and go
|
||||
from django.core.handlers.wsgi import WSGIHandler
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.db import backend, connection, transaction
|
||||
from django.db.models.fields import DateField, FieldDoesNotExist
|
||||
from django.db.models.fields.generic import GenericRelation
|
||||
from django.db.models import signals
|
||||
from django.dispatch import dispatcher
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
@ -925,18 +926,26 @@ def delete_objects(seen_objs):
|
|||
|
||||
pk_list = [pk for pk,instance in seen_objs[cls]]
|
||||
for related in cls._meta.get_all_related_many_to_many_objects():
|
||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
|
||||
(qn(related.field.m2m_db_table()),
|
||||
qn(related.field.m2m_reverse_name()),
|
||||
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
|
||||
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
|
||||
if not isinstance(related.field, GenericRelation):
|
||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
|
||||
(qn(related.field.m2m_db_table()),
|
||||
qn(related.field.m2m_reverse_name()),
|
||||
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
|
||||
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
|
||||
for f in cls._meta.many_to_many:
|
||||
if isinstance(f, GenericRelation):
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column
|
||||
args_extra = [ContentType.objects.get_for_model(cls).id]
|
||||
else:
|
||||
query_extra = ''
|
||||
args_extra = []
|
||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
|
||||
(qn(f.m2m_db_table()), qn(f.m2m_column_name()),
|
||||
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
|
||||
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
|
||||
(qn(f.m2m_db_table()), qn(f.m2m_column_name()),
|
||||
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])) + query_extra,
|
||||
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE] + args_extra)
|
||||
for field in cls._meta.fields:
|
||||
if field.rel and field.null and field.rel.to in seen_objs:
|
||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
"Translation helper functions"
|
||||
|
||||
import os, re, sys
|
||||
import locale
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import gettext as gettext_module
|
||||
from cStringIO import StringIO
|
||||
from django.utils.functional import lazy
|
||||
|
@ -25,15 +28,25 @@ _active = {}
|
|||
# The default translation is based on the settings file.
|
||||
_default = None
|
||||
|
||||
# This is a cache for accept-header to translation object mappings to prevent
|
||||
# the accept parser to run multiple times for one user.
|
||||
# This is a cache for normalised accept-header languages to prevent multiple
|
||||
# file lookups when checking the same locale on repeated requests.
|
||||
_accepted = {}
|
||||
|
||||
def to_locale(language):
|
||||
# Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9.
|
||||
accept_language_re = re.compile(r'''
|
||||
([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*) # "en", "en-au", "x-y-z", "*"
|
||||
(?:;q=(0(?:\.\d{,3})?|1(?:.0{,3})?))? # Optional "q=1.00", "q=0.8"
|
||||
(?:\s*,\s*|$) # Multiple accepts per header.
|
||||
''', re.VERBOSE)
|
||||
|
||||
def to_locale(language, to_lower=False):
|
||||
"Turns a language name (en-us) into a locale name (en_US)."
|
||||
p = language.find('-')
|
||||
if p >= 0:
|
||||
return language[:p].lower()+'_'+language[p+1:].upper()
|
||||
if to_lower:
|
||||
return language[:p].lower()+'_'+language[p+1:].lower()
|
||||
else:
|
||||
return language[:p].lower()+'_'+language[p+1:].upper()
|
||||
else:
|
||||
return language.lower()
|
||||
|
||||
|
@ -309,46 +322,40 @@ def get_language_from_request(request):
|
|||
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
|
||||
return lang_code
|
||||
|
||||
lang_code = request.COOKIES.get('django_language', None)
|
||||
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
|
||||
lang_code = request.COOKIES.get('django_language')
|
||||
if lang_code and lang_code in supported and check_for_language(lang_code):
|
||||
return lang_code
|
||||
|
||||
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', None)
|
||||
if accept is not None:
|
||||
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
|
||||
for lang, unused in parse_accept_lang_header(accept):
|
||||
if lang == '*':
|
||||
break
|
||||
|
||||
t = _accepted.get(accept, None)
|
||||
if t is not None:
|
||||
return t
|
||||
# We have a very restricted form for our language files (no encoding
|
||||
# specifier, since they all must be UTF-8 and only one possible
|
||||
# language each time. So we avoid the overhead of gettext.find() and
|
||||
# look up the MO file manually.
|
||||
|
||||
def _parsed(el):
|
||||
p = el.find(';q=')
|
||||
if p >= 0:
|
||||
lang = el[:p].strip()
|
||||
order = int(float(el[p+3:].strip())*100)
|
||||
else:
|
||||
lang = el
|
||||
order = 100
|
||||
p = lang.find('-')
|
||||
if p >= 0:
|
||||
mainlang = lang[:p]
|
||||
else:
|
||||
mainlang = lang
|
||||
return (lang, mainlang, order)
|
||||
normalized = locale.locale_alias.get(to_locale(lang, True))
|
||||
if not normalized:
|
||||
continue
|
||||
|
||||
langs = [_parsed(el) for el in accept.split(',')]
|
||||
langs.sort(lambda a,b: -1*cmp(a[2], b[2]))
|
||||
# Remove the default encoding from locale_alias
|
||||
normalized = normalized.split('.')[0]
|
||||
|
||||
for lang, mainlang, order in langs:
|
||||
if lang in supported or mainlang in supported:
|
||||
langfile = gettext_module.find('django', globalpath, [to_locale(lang)])
|
||||
if langfile:
|
||||
# reconstruct the actual language from the language
|
||||
# filename, because otherwise we might incorrectly
|
||||
# report de_DE if we only have de available, but
|
||||
# did find de_DE because of language normalization
|
||||
lang = langfile[len(globalpath):].split(os.path.sep)[1]
|
||||
_accepted[accept] = lang
|
||||
return lang
|
||||
if normalized in _accepted:
|
||||
# We've seen this locale before and have an MO file for it, so no
|
||||
# need to check again.
|
||||
return _accepted[normalized]
|
||||
|
||||
for lang in (normalized, normalized.split('_')[0]):
|
||||
if lang not in supported:
|
||||
continue
|
||||
langfile = os.path.join(globalpath, lang, 'LC_MESSAGES',
|
||||
'django.mo')
|
||||
if os.path.exists(langfile):
|
||||
_accepted[normalized] = lang
|
||||
return lang
|
||||
|
||||
return settings.LANGUAGE_CODE
|
||||
|
||||
|
@ -494,3 +501,24 @@ def string_concat(*strings):
|
|||
return ''.join([str(el) for el in strings])
|
||||
|
||||
string_concat = lazy(string_concat, str)
|
||||
|
||||
def parse_accept_lang_header(lang_string):
|
||||
"""
|
||||
Parses the lang_string, which is the body of an HTTP Accept-Language
|
||||
header, and returns a list of (lang, q-value), ordered by 'q' values.
|
||||
|
||||
Any format errors in lang_string results in an empty list being returned.
|
||||
"""
|
||||
result = []
|
||||
pieces = accept_language_re.split(lang_string)
|
||||
if pieces[-1]:
|
||||
return []
|
||||
for i in range(0, len(pieces) - 1, 3):
|
||||
first, lang, priority = pieces[i : i + 3]
|
||||
if first:
|
||||
return []
|
||||
priority = priority and float(priority) or 1.0
|
||||
result.append((lang, priority))
|
||||
result.sort(lambda x, y: -cmp(x[1], y[1]))
|
||||
return result
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
=================================
|
||||
Django version 0.95 release notes
|
||||
=================================
|
||||
===================================
|
||||
Django version 0.95.2 release notes
|
||||
===================================
|
||||
|
||||
|
||||
Welcome to the Django 0.95 release.
|
||||
Welcome to the Django 0.95.2 release.
|
||||
|
||||
This represents a significant advance in Django development since the 0.91
|
||||
release in January 2006. The details of every change in this release would be
|
||||
|
@ -91,6 +90,31 @@ easy checklist_ for reference when undertaking the porting operation.
|
|||
.. _Removing The Magic: http://code.djangoproject.com/wiki/RemovingTheMagic
|
||||
.. _checklist: http://code.djangoproject.com/wiki/MagicRemovalCheatSheet1
|
||||
|
||||
Changes since the 0.95 release
|
||||
==============================
|
||||
|
||||
This release contains fixes for several bugs discovered after the
|
||||
initial release of Django 0.95; these include:
|
||||
|
||||
* A patch for a small security vulnerability in the script
|
||||
Django's internationalization system uses to compile translation
|
||||
files.
|
||||
|
||||
* A fix for a bug in Django's authentication middleware which
|
||||
could cause apparent "caching" of a logged-in user.
|
||||
|
||||
* A patch which disables debugging mode in the flup FastCGI
|
||||
package Django uses to launch its FastCGI server, which prevents
|
||||
tracebacks from bubbling up during production use.
|
||||
|
||||
* A security fix to the i18n framework which could allow an
|
||||
attacker to send extremely large strings in the Accept-Language
|
||||
header and cause a denial of service by filling available memory.
|
||||
|
||||
Because these problems weren't discovered and fixed until after the
|
||||
0.95 release, it's recommended that you use this release rather than
|
||||
the original 0.95.
|
||||
|
||||
Problem reports and getting help
|
||||
================================
|
||||
|
||||
|
@ -118,5 +142,5 @@ available at any hour of the day -- to help, or just to chat.
|
|||
Thanks for using Django!
|
||||
|
||||
The Django Team
|
||||
July 2006
|
||||
January 2007
|
||||
|
||||
|
|
2
setup.py
2
setup.py
|
@ -5,7 +5,7 @@ from setuptools import setup, find_packages
|
|||
|
||||
setup(
|
||||
name = "Django",
|
||||
version = "0.95",
|
||||
version = "0.95.4",
|
||||
url = 'http://www.djangoproject.com/',
|
||||
author = 'Lawrence Journal-World',
|
||||
author_email = 'holovaty@gmail.com',
|
||||
|
|
|
@ -65,14 +65,14 @@ API_TESTS = """
|
|||
|
||||
# Objects with declared GenericRelations can be tagged directly -- the API
|
||||
# mimics the many-to-many API.
|
||||
>>> lion.tags.create(tag="yellow")
|
||||
<TaggedItem: yellow>
|
||||
>>> lion.tags.create(tag="hairy")
|
||||
<TaggedItem: hairy>
|
||||
>>> bacon.tags.create(tag="fatty")
|
||||
<TaggedItem: fatty>
|
||||
>>> bacon.tags.create(tag="salty")
|
||||
<TaggedItem: salty>
|
||||
>>> lion.tags.create(tag="yellow")
|
||||
<TaggedItem: yellow>
|
||||
>>> lion.tags.create(tag="hairy")
|
||||
<TaggedItem: hairy>
|
||||
|
||||
>>> lion.tags.all()
|
||||
[<TaggedItem: hairy>, <TaggedItem: yellow>]
|
||||
|
@ -105,4 +105,31 @@ API_TESTS = """
|
|||
[<TaggedItem: shiny>]
|
||||
>>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id)
|
||||
[<TaggedItem: clearish>]
|
||||
|
||||
|
||||
# If you delete an object with an explicit Generic relation, the related
|
||||
# objects are deleted when the source object is deleted.
|
||||
# Original list of tags:
|
||||
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
||||
[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('hairy', <ContentType: animal>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2), ('yellow', <ContentType: animal>, 1)]
|
||||
|
||||
>>> lion.delete()
|
||||
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
||||
[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
|
||||
|
||||
# If Generic Relation is not explicitly defined, any related objects
|
||||
# remain after deletion of the source object.
|
||||
>>> quartz.delete()
|
||||
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
||||
[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
|
||||
|
||||
# If you delete a tag, the objects using the tag are unaffected
|
||||
# (other than losing a tag)
|
||||
>>> tag = TaggedItem.objects.get(id=1)
|
||||
>>> tag.delete()
|
||||
>>> bacon.tags.all()
|
||||
[<TaggedItem: salty>]
|
||||
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
||||
[('clearish', <ContentType: mineral>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
|
||||
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue