1
0
mirror of https://github.com/django/django.git synced 2025-07-04 01:39:20 +00:00

[soc2009/http-wsgi-improvements] Merged up to 11009 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/http-wsgi-improvements@11013 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Chris Cahoon 2009-06-16 16:27:55 +00:00
parent 3d4f9ec1e6
commit c435676176
17 changed files with 302 additions and 114 deletions

View File

@ -317,7 +317,7 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
def get_queryset(self): def get_queryset(self):
# Avoid a circular import. # Avoid a circular import.
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
if self.instance is None: if self.instance is None or self.instance.pk is None:
return self.model._default_manager.none() return self.model._default_manager.none()
return self.model._default_manager.filter(**{ return self.model._default_manager.filter(**{
self.ct_field.name: ContentType.objects.get_for_model(self.instance), self.ct_field.name: ContentType.objects.get_for_model(self.instance),

View File

@ -14,7 +14,7 @@ if lib_path:
lib_names = None lib_names = None
elif os.name == 'nt': elif os.name == 'nt':
# Windows NT shared library # Windows NT shared library
lib_names = ['gdal15'] lib_names = ['gdal16', 'gdal15']
elif os.name == 'posix': elif os.name == 'posix':
# *NIX library names. # *NIX library names.
lib_names = ['gdal', 'GDAL', 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0'] lib_names = ['gdal', 'GDAL', 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0']

View File

@ -84,16 +84,15 @@ class GeoIPTest(unittest.TestCase):
self.assertEqual('USA', d['country_code3']) self.assertEqual('USA', d['country_code3'])
self.assertEqual('Houston', d['city']) self.assertEqual('Houston', d['city'])
self.assertEqual('TX', d['region']) self.assertEqual('TX', d['region'])
self.assertEqual('77002', d['postal_code'])
self.assertEqual(713, d['area_code']) self.assertEqual(713, d['area_code'])
geom = g.geos(query) geom = g.geos(query)
self.failIf(not isinstance(geom, GEOSGeometry)) self.failIf(not isinstance(geom, GEOSGeometry))
lon, lat = (-95.366996765, 29.752300262) lon, lat = (-95.4152, 29.7755)
lat_lon = g.lat_lon(query) lat_lon = g.lat_lon(query)
lat_lon = (lat_lon[1], lat_lon[0]) lat_lon = (lat_lon[1], lat_lon[0])
for tup in (geom.tuple, g.coords(query), g.lon_lat(query), lat_lon): for tup in (geom.tuple, g.coords(query), g.lon_lat(query), lat_lon):
self.assertAlmostEqual(lon, tup[0], 9) self.assertAlmostEqual(lon, tup[0], 4)
self.assertAlmostEqual(lat, tup[1], 9) self.assertAlmostEqual(lat, tup[1], 4)
def suite(): def suite():
s = unittest.TestSuite() s = unittest.TestSuite()

View File

@ -83,8 +83,17 @@ class GeoIPRecord(Structure):
('postal_code', c_char_p), ('postal_code', c_char_p),
('latitude', c_float), ('latitude', c_float),
('longitude', c_float), ('longitude', c_float),
# TODO: In 1.4.6 this changed from `int dma_code;` to
# `union {int metro_code; int dma_code;};`. Change
# to a `ctypes.Union` in to accomodate in future when
# pre-1.4.6 versions are no longer distributed.
('dma_code', c_int), ('dma_code', c_int),
('area_code', c_int), ('area_code', c_int),
# TODO: The following structure fields were added in 1.4.3 --
# uncomment these fields when sure previous versions are no
# longer distributed by package maintainers.
#('charset', c_int),
#('continent_code', c_char_p),
] ]
class GeoIPTag(Structure): pass class GeoIPTag(Structure): pass
@ -99,9 +108,12 @@ def record_output(func):
rec_by_addr = record_output(lgeoip.GeoIP_record_by_addr) rec_by_addr = record_output(lgeoip.GeoIP_record_by_addr)
rec_by_name = record_output(lgeoip.GeoIP_record_by_name) rec_by_name = record_output(lgeoip.GeoIP_record_by_name)
# For opening up GeoIP databases. # For opening & closing GeoIP database files.
geoip_open = lgeoip.GeoIP_open geoip_open = lgeoip.GeoIP_open
geoip_open.restype = DBTYPE geoip_open.restype = DBTYPE
geoip_close = lgeoip.GeoIP_delete
geoip_close.argtypes = [DBTYPE]
geoip_close.restype = None
# String output routines. # String output routines.
def string_output(func): def string_output(func):
@ -136,6 +148,12 @@ class GeoIP(object):
GEOIP_CHECK_CACHE = 2 GEOIP_CHECK_CACHE = 2
GEOIP_INDEX_CACHE = 4 GEOIP_INDEX_CACHE = 4
cache_options = dict((opt, None) for opt in (0, 1, 2, 4)) cache_options = dict((opt, None) for opt in (0, 1, 2, 4))
_city_file = ''
_country_file = ''
# Initially, pointers to GeoIP file references are NULL.
_city = None
_country = None
def __init__(self, path=None, cache=0, country=None, city=None): def __init__(self, path=None, cache=0, country=None, city=None):
""" """
@ -174,13 +192,19 @@ class GeoIP(object):
if not isinstance(path, basestring): if not isinstance(path, basestring):
raise TypeError('Invalid path type: %s' % type(path).__name__) raise TypeError('Invalid path type: %s' % type(path).__name__)
cntry_ptr, city_ptr = (None, None)
if os.path.isdir(path): if os.path.isdir(path):
# Getting the country and city files using the settings # Constructing the GeoIP database filenames using the settings
# dictionary. If no settings are provided, default names # dictionary. If the database files for the GeoLite country
# are assigned. # and/or city datasets exist, then try and open them.
country = os.path.join(path, country or GEOIP_SETTINGS.get('GEOIP_COUNTRY', 'GeoIP.dat')) country_db = os.path.join(path, country or GEOIP_SETTINGS.get('GEOIP_COUNTRY', 'GeoIP.dat'))
city = os.path.join(path, city or GEOIP_SETTINGS.get('GEOIP_CITY', 'GeoLiteCity.dat')) if os.path.isfile(country_db):
self._country = geoip_open(country_db, cache)
self._country_file = country_db
city_db = os.path.join(path, city or GEOIP_SETTINGS.get('GEOIP_CITY', 'GeoLiteCity.dat'))
if os.path.isfile(city_db):
self._city = geoip_open(city_db, cache)
self._city_file = city_db
elif os.path.isfile(path): elif os.path.isfile(path):
# Otherwise, some detective work will be needed to figure # Otherwise, some detective work will be needed to figure
# out whether the given database path is for the GeoIP country # out whether the given database path is for the GeoIP country
@ -188,29 +212,22 @@ class GeoIP(object):
ptr = geoip_open(path, cache) ptr = geoip_open(path, cache)
info = geoip_dbinfo(ptr) info = geoip_dbinfo(ptr)
if lite_regex.match(info): if lite_regex.match(info):
# GeoLite City database. # GeoLite City database detected.
city, city_ptr = path, ptr self._city = ptr
self._city_file = path
elif free_regex.match(info): elif free_regex.match(info):
# GeoIP Country database. # GeoIP Country database detected.
country, cntry_ptr = path, ptr self._country = ptr
self._country_file = path
else: else:
raise GeoIPException('Unable to recognize database edition: %s' % info) raise GeoIPException('Unable to recognize database edition: %s' % info)
else: else:
raise GeoIPException('GeoIP path must be a valid file or directory.') raise GeoIPException('GeoIP path must be a valid file or directory.')
# `_init_db` does the dirty work. def __del__(self):
self._init_db(country, cache, '_country', cntry_ptr) # Cleaning any GeoIP file handles lying around.
self._init_db(city, cache, '_city', city_ptr) if self._country: geoip_close(self._country)
if self._city: geoip_close(self._city)
def _init_db(self, db_file, cache, attname, ptr=None):
"Helper routine for setting GeoIP ctypes database properties."
if ptr:
# Pointer already retrieved.
pass
elif os.path.isfile(db_file or ''):
ptr = geoip_open(db_file, cache)
setattr(self, attname, ptr)
setattr(self, '%s_file' % attname, db_file)
def _check_query(self, query, country=False, city=False, city_or_country=False): def _check_query(self, query, country=False, city=False, city_or_country=False):
"Helper routine for checking the query and database availability." "Helper routine for checking the query and database availability."
@ -219,11 +236,11 @@ class GeoIP(object):
raise TypeError('GeoIP query must be a string, not type %s' % type(query).__name__) raise TypeError('GeoIP query must be a string, not type %s' % type(query).__name__)
# Extra checks for the existence of country and city databases. # Extra checks for the existence of country and city databases.
if city_or_country and self._country is None and self._city is None: if city_or_country and not (self._country or self._city):
raise GeoIPException('Invalid GeoIP country and city data files.') raise GeoIPException('Invalid GeoIP country and city data files.')
elif country and self._country is None: elif country and not self._country:
raise GeoIPException('Invalid GeoIP country data file: %s' % self._country_file) raise GeoIPException('Invalid GeoIP country data file: %s' % self._country_file)
elif city and self._city is None: elif city and not self._city:
raise GeoIPException('Invalid GeoIP city data file: %s' % self._city_file) raise GeoIPException('Invalid GeoIP city data file: %s' % self._city_file)
def city(self, query): def city(self, query):

View File

@ -195,7 +195,7 @@ class EmailMessage(object):
A container for email information. A container for email information.
""" """
content_subtype = 'plain' content_subtype = 'plain'
multipart_subtype = 'mixed' mixed_subtype = 'mixed'
encoding = None # None => use settings default encoding = None # None => use settings default
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None, def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
@ -234,16 +234,7 @@ class EmailMessage(object):
encoding = self.encoding or settings.DEFAULT_CHARSET encoding = self.encoding or settings.DEFAULT_CHARSET
msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET), msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
self.content_subtype, encoding) self.content_subtype, encoding)
if self.attachments: msg = self._create_message(msg)
body_msg = msg
msg = SafeMIMEMultipart(_subtype=self.multipart_subtype)
if self.body:
msg.attach(body_msg)
for attachment in self.attachments:
if isinstance(attachment, MIMEBase):
msg.attach(attachment)
else:
msg.attach(self._create_attachment(*attachment))
msg['Subject'] = self.subject msg['Subject'] = self.subject
msg['From'] = self.extra_headers.pop('From', self.from_email) msg['From'] = self.extra_headers.pop('From', self.from_email)
msg['To'] = ', '.join(self.to) msg['To'] = ', '.join(self.to)
@ -277,8 +268,7 @@ class EmailMessage(object):
def attach(self, filename=None, content=None, mimetype=None): def attach(self, filename=None, content=None, mimetype=None):
""" """
Attaches a file with the given filename and content. The filename can Attaches a file with the given filename and content. The filename can
be omitted (useful for multipart/alternative messages) and the mimetype be omitted and the mimetype is guessed, if not provided.
is guessed, if not provided.
If the first parameter is a MIMEBase subclass it is inserted directly If the first parameter is a MIMEBase subclass it is inserted directly
into the resulting message attachments. into the resulting message attachments.
@ -296,15 +286,26 @@ class EmailMessage(object):
content = open(path, 'rb').read() content = open(path, 'rb').read()
self.attach(filename, content, mimetype) self.attach(filename, content, mimetype)
def _create_attachment(self, filename, content, mimetype=None): def _create_message(self, msg):
return self._create_attachments(msg)
def _create_attachments(self, msg):
if self.attachments:
body_msg = msg
msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
if self.body:
msg.attach(body_msg)
for attachment in self.attachments:
if isinstance(attachment, MIMEBase):
msg.attach(attachment)
else:
msg.attach(self._create_attachment(*attachment))
return msg
def _create_mime_attachment(self, content, mimetype):
""" """
Converts the filename, content, mimetype triple into a MIME attachment Converts the content, mimetype pair into a MIME attachment object.
object.
""" """
if mimetype is None:
mimetype, _ = mimetypes.guess_type(filename)
if mimetype is None:
mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
basetype, subtype = mimetype.split('/', 1) basetype, subtype = mimetype.split('/', 1)
if basetype == 'text': if basetype == 'text':
attachment = SafeMIMEText(smart_str(content, attachment = SafeMIMEText(smart_str(content,
@ -314,6 +315,18 @@ class EmailMessage(object):
attachment = MIMEBase(basetype, subtype) attachment = MIMEBase(basetype, subtype)
attachment.set_payload(content) attachment.set_payload(content)
Encoders.encode_base64(attachment) Encoders.encode_base64(attachment)
return attachment
def _create_attachment(self, filename, content, mimetype=None):
"""
Converts the filename, content, mimetype triple into a MIME attachment
object.
"""
if mimetype is None:
mimetype, _ = mimetypes.guess_type(filename)
if mimetype is None:
mimetype = DEFAULT_ATTACHMENT_MIME_TYPE
attachment = self._create_mime_attachment(content, mimetype)
if filename: if filename:
attachment.add_header('Content-Disposition', 'attachment', attachment.add_header('Content-Disposition', 'attachment',
filename=filename) filename=filename)
@ -325,11 +338,39 @@ class EmailMultiAlternatives(EmailMessage):
messages. For example, including text and HTML versions of the text is messages. For example, including text and HTML versions of the text is
made easier. made easier.
""" """
multipart_subtype = 'alternative' alternative_subtype = 'alternative'
def attach_alternative(self, content, mimetype=None): def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
connection=None, attachments=None, headers=None, alternatives=None):
"""
Initialize a single email message (which can be sent to multiple
recipients).
All strings used to create the message can be unicode strings (or UTF-8
bytestrings). The SafeMIMEText class will handle any necessary encoding
conversions.
"""
super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
self.alternatives=alternatives or []
def attach_alternative(self, content, mimetype):
"""Attach an alternative content representation.""" """Attach an alternative content representation."""
self.attach(content=content, mimetype=mimetype) assert content is not None
assert mimetype is not None
self.alternatives.append((content, mimetype))
def _create_message(self, msg):
return self._create_attachments(self._create_alternatives(msg))
def _create_alternatives(self, msg):
if self.alternatives:
body_msg = msg
msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
if self.body:
msg.attach(body_msg)
for alternative in self.alternatives:
msg.attach(self._create_mime_attachment(*alternative))
return msg
def send_mail(subject, message, from_email, recipient_list, def send_mail(subject, message, from_email, recipient_list,
fail_silently=False, auth_user=None, auth_password=None): fail_silently=False, auth_user=None, auth_password=None):

View File

@ -73,7 +73,7 @@ class Command(BaseCommand):
model_list = get_models(app) model_list = get_models(app)
for model in model_list: for model in model_list:
objects.extend(model.objects.all()) objects.extend(model._default_manager.all())
try: try:
return serializers.serialize(format, objects, indent=indent) return serializers.serialize(format, objects, indent=indent)

View File

@ -26,8 +26,11 @@ class BaseDatabaseCreation(object):
self.connection = connection self.connection = connection
def _digest(self, *args): def _digest(self, *args):
"Generate a 32 bit digest of a set of arguments that can be used to shorten identifying names" """
return '%x' % (abs(hash(args)) % (1<<32)) Generates a 32-bit digest of a set of arguments that can be used to
shorten identifying names.
"""
return '%x' % (abs(hash(args)) % 4294967296L) # 2**32
def sql_create_model(self, model, style, known_models=set()): def sql_create_model(self, model, style, known_models=set()):
""" """

View File

@ -112,9 +112,9 @@ class RelatedField(object):
def do_related_class(self, other, cls): def do_related_class(self, other, cls):
self.set_attributes_from_rel() self.set_attributes_from_rel()
related = RelatedObject(other, cls, self) self.related = RelatedObject(other, cls, self)
if not cls._meta.abstract: if not cls._meta.abstract:
self.contribute_to_related_class(other, related) self.contribute_to_related_class(other, self.related)
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value):
# If we are doing a lookup on a Related Field, we must be # If we are doing a lookup on a Related Field, we must be
@ -132,8 +132,8 @@ class RelatedField(object):
v, field = getattr(v, v._meta.pk.name), v._meta.pk v, field = getattr(v, v._meta.pk.name), v._meta.pk
except AttributeError: except AttributeError:
pass pass
if not field:
field = self.rel.get_related_field() if field:
if lookup_type in ('range', 'in'): if lookup_type in ('range', 'in'):
v = [v] v = [v]
v = field.get_db_prep_lookup(lookup_type, v) v = field.get_db_prep_lookup(lookup_type, v)
@ -184,7 +184,6 @@ class SingleRelatedObjectDescriptor(object):
def __get__(self, instance, instance_type=None): def __get__(self, instance, instance_type=None):
if instance is None: if instance is None:
return self return self
try: try:
return getattr(instance, self.cache_name) return getattr(instance, self.cache_name)
except AttributeError: except AttributeError:
@ -232,6 +231,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
def __get__(self, instance, instance_type=None): def __get__(self, instance, instance_type=None):
if instance is None: if instance is None:
return self return self
cache_name = self.field.get_cache_name() cache_name = self.field.get_cache_name()
try: try:
return getattr(instance, cache_name) return getattr(instance, cache_name)
@ -272,6 +272,29 @@ class ReverseSingleRelatedObjectDescriptor(object):
(value, instance._meta.object_name, (value, instance._meta.object_name,
self.field.name, self.field.rel.to._meta.object_name)) self.field.name, self.field.rel.to._meta.object_name))
# If we're setting the value of a OneToOneField to None, we need to clear
# out the cache on any old related object. Otherwise, deleting the
# previously-related object will also cause this object to be deleted,
# which is wrong.
if value is None:
# Look up the previously-related object, which may still be available
# since we've not yet cleared out the related field.
# Use the cache directly, instead of the accessor; if we haven't
# populated the cache, then we don't care - we're only accessing
# the object to invalidate the accessor cache, so there's no
# need to populate the cache just to expire it again.
related = getattr(instance, self.field.get_cache_name(), None)
# If we've got an old related object, we need to clear out its
# cache. This cache also might not exist if the related object
# hasn't been accessed yet.
if related:
cache_name = '_%s_cache' % self.field.related.get_accessor_name()
try:
delattr(related, cache_name)
except AttributeError:
pass
# Set the value of the related field # Set the value of the related field
try: try:
val = getattr(value, self.field.rel.get_related_field().attname) val = getattr(value, self.field.rel.get_related_field().attname)

View File

@ -20,7 +20,7 @@ tutorial, so that the template contains an HTML ``<form>`` element:
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="vote/" method="post"> <form action="/polls/{{ poll.id }}/vote/" method="post">
{% for choice in poll.choice_set.all %} {% for choice in poll.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br /> <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
@ -36,12 +36,12 @@ A quick rundown:
selects one of the radio buttons and submits the form, it'll send the selects one of the radio buttons and submits the form, it'll send the
POST data ``choice=3``. This is HTML Forms 101. POST data ``choice=3``. This is HTML Forms 101.
* We set the form's ``action`` to ``vote/``, and we set ``method="post"``. * We set the form's ``action`` to ``/polls/{{ poll.id }}/vote/``, and we
Using ``method="post"`` (as opposed to ``method="get"``) is very set ``method="post"``. Using ``method="post"`` (as opposed to
important, because the act of submitting this form will alter data ``method="get"``) is very important, because the act of submitting this
server-side. Whenever you create a form that alters data server-side, use form will alter data server-side. Whenever you create a form that alters
``method="post"``. This tip isn't specific to Django; it's just good Web data server-side, use ``method="post"``. This tip isn't specific to
development practice. Django; it's just good Web development practice.
* ``forloop.counter`` indicates how many times the :ttag:`for` tag has gone * ``forloop.counter`` indicates how many times the :ttag:`for` tag has gone
through its loop through its loop

View File

@ -43,8 +43,8 @@ modify the filename as necessary to get a unique name. The actual name of the
stored file will be returned. stored file will be returned.
The ``content`` argument must be an instance of The ``content`` argument must be an instance of
:class:`django.db.files.File` or of a subclass of :class:`django.core.files.File` or of a subclass of
:class:`~django.db.files.File`. :class:`~django.core.files.File`.
``Storage.delete(name)`` ``Storage.delete(name)``
~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -800,21 +800,22 @@ you can use the name of the model, rather than the model object itself::
class Manufacturer(models.Model): class Manufacturer(models.Model):
# ... # ...
Note, however, that this only refers to models in the same ``models.py`` file -- .. versionadded:: 1.0
you cannot use a string to reference a model defined in another application or
imported from elsewhere.
.. versionchanged:: 1.0 To refer to models defined in another application, you can explicitly specify
Refering models in other applications must include the application label. a model with the full application label. For example, if the ``Manufacturer``
model above is defined in another application called ``production``, you'd
To refer to models defined in another need to use::
application, you must instead explicitly specify the application label. For
example, if the ``Manufacturer`` model above is defined in another application
called ``production``, you'd need to use::
class Car(models.Model): class Car(models.Model):
manufacturer = models.ForeignKey('production.Manufacturer') manufacturer = models.ForeignKey('production.Manufacturer')
This sort of reference can be useful when resolving circular import
dependencies between two applications.
Database Representation
~~~~~~~~~~~~~~~~~~~~~~~
Behind the scenes, Django appends ``"_id"`` to the field name to create its Behind the scenes, Django appends ``"_id"`` to the field name to create its
database column name. In the above example, the database table for the ``Car`` database column name. In the above example, the database table for the ``Car``
model will have a ``manufacturer_id`` column. (You can change this explicitly by model will have a ``manufacturer_id`` column. (You can change this explicitly by
@ -824,6 +825,9 @@ deal with the field names of your model object.
.. _foreign-key-arguments: .. _foreign-key-arguments:
Arguments
~~~~~~~~~
:class:`ForeignKey` accepts an extra set of arguments -- all optional -- that :class:`ForeignKey` accepts an extra set of arguments -- all optional -- that
define the details of how the relation works. define the details of how the relation works.
@ -871,6 +875,9 @@ the model is related. This works exactly the same as it does for
:class:`ForeignKey`, including all the options regarding :ref:`recursive :class:`ForeignKey`, including all the options regarding :ref:`recursive
<recursive-relationships>` and :ref:`lazy <lazy-relationships>` relationships. <recursive-relationships>` and :ref:`lazy <lazy-relationships>` relationships.
Database Representation
~~~~~~~~~~~~~~~~~~~~~~~
Behind the scenes, Django creates an intermediary join table to represent the Behind the scenes, Django creates an intermediary join table to represent the
many-to-many relationship. By default, this table name is generated using the many-to-many relationship. By default, this table name is generated using the
names of the two tables being joined. Since some databases don't support table names of the two tables being joined. Since some databases don't support table
@ -882,6 +889,9 @@ You can manually provide the name of the join table using the
.. _manytomany-arguments: .. _manytomany-arguments:
Arguments
~~~~~~~~~
:class:`ManyToManyField` accepts an extra set of arguments -- all optional -- :class:`ManyToManyField` accepts an extra set of arguments -- all optional --
that control how the relationship functions. that control how the relationship functions.

View File

@ -323,16 +323,19 @@ Since the Author model has only 3 fields, 'name', 'title', and
to be empty, and does not provide a default value for the missing fields, to be empty, and does not provide a default value for the missing fields,
any attempt to ``save()`` a ``ModelForm`` with missing fields will fail. any attempt to ``save()`` a ``ModelForm`` with missing fields will fail.
To avoid this failure, you must instantiate your model with initial values To avoid this failure, you must instantiate your model with initial values
for the missing, but required fields, or use ``save(commit=False)`` and for the missing, but required fields::
manually set any extra required fields::
instance = Instance(required_field='value') author = Author(title='Mr')
form = InstanceForm(request.POST, instance=instance) form = PartialAuthorForm(request.POST, instance=author)
new_instance = form.save() form.save()
instance = form.save(commit=False) Alternatively, you can use ``save(commit=False)`` and manually set
instance.required_field = 'new value' any extra required fields::
new_instance = instance.save()
form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = 'Mr'
author.save()
See the `section on saving forms`_ for more details on using See the `section on saving forms`_ for more details on using
``save(commit=False)``. ``save(commit=False)``.

View File

@ -136,11 +136,14 @@ Pass
# Regression for #10785 -- Custom fields can be used for primary keys. # Regression for #10785 -- Custom fields can be used for primary keys.
>>> new_bar = Bar.objects.create() >>> new_bar = Bar.objects.create()
>>> new_foo = Foo.objects.create(bar=new_bar) >>> new_foo = Foo.objects.create(bar=new_bar)
>>> f = Foo.objects.get(bar=new_bar.pk)
>>> f == new_foo # FIXME: This still doesn't work, but will require some changes in
True # get_db_prep_lookup to fix it.
>>> f.bar == new_bar # >>> f = Foo.objects.get(bar=new_bar.pk)
True # >>> f == new_foo
# True
# >>> f.bar == new_bar
# True
>>> f = Foo.objects.get(bar=new_bar) >>> f = Foo.objects.get(bar=new_bar)
>>> f == new_foo >>> f == new_foo

View File

@ -9,6 +9,9 @@ class Animal(models.Model):
count = models.IntegerField() count = models.IntegerField()
weight = models.FloatField() weight = models.FloatField()
# use a non-default name for the default manager
specimens = models.Manager()
def __unicode__(self): def __unicode__(self):
return self.common_name return self.common_name
@ -161,4 +164,10 @@ Weight = 1.2 (<type 'float'>)
>>> models.signals.pre_save.disconnect(animal_pre_save_check) >>> models.signals.pre_save.disconnect(animal_pre_save_check)
###############################################
# Regression for #11286 -- Ensure that dumpdata honors the default manager
# Dump the current contents of the database as a JSON fixture
>>> management.call_command('dumpdata', 'fixtures_regress.animal', format='json')
[{"pk": 1, "model": "fixtures_regress.animal", "fields": {"count": 3, "weight": 1.2, "name": "Lion", "latin_name": "Panthera leo"}}, {"pk": 2, "model": "fixtures_regress.animal", "fields": {"count": 2, "weight": 2.29..., "name": "Platypus", "latin_name": "Ornithorhynchus anatinus"}}, {"pk": 10, "model": "fixtures_regress.animal", "fields": {"count": 42, "weight": 1.2, "name": "Emu", "latin_name": "Dromaius novaehollandiae"}}]
"""} """}

View File

@ -33,6 +33,14 @@ class SelfReferChild(SelfRefer):
class SelfReferChildSibling(SelfRefer): class SelfReferChildSibling(SelfRefer):
pass pass
# Many-to-Many relation between models, where one of the PK's isn't an Autofield
class Line(models.Model):
name = models.CharField(max_length=100)
class Worksheet(models.Model):
id = models.CharField(primary_key=True, max_length=100)
lines = models.ManyToManyField(Line, blank=True, null=True)
__test__ = {"regressions": """ __test__ = {"regressions": """
# Multiple m2m references to the same model or a different model must be # Multiple m2m references to the same model or a different model must be
# distinguished when accessing the relations through an instance attribute. # distinguished when accessing the relations through an instance attribute.
@ -79,5 +87,11 @@ FieldError: Cannot resolve keyword 'porcupine' into field. Choices are: id, name
>>> sr_sibling.related.all() >>> sr_sibling.related.all()
[<SelfRefer: Hanna>] [<SelfRefer: Hanna>]
# Regression for #11311 - The primary key for models in a m2m relation
# doesn't have to be an AutoField
>>> w = Worksheet(id='abc')
>>> w.save()
>>> w.delete()
""" """
} }

View File

@ -4,7 +4,7 @@ r"""
>>> from django.conf import settings >>> from django.conf import settings
>>> from django.core import mail >>> from django.core import mail
>>> from django.core.mail import EmailMessage, mail_admins, mail_managers >>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives
>>> from django.utils.translation import ugettext_lazy >>> from django.utils.translation import ugettext_lazy
# Test normal ascii character case: # Test normal ascii character case:
@ -95,4 +95,48 @@ BadHeaderError: Header values can't contain newlines (got u'Subject\nInjection T
>>> message['From'] >>> message['From']
'from@example.com' 'from@example.com'
# Handle attachments within an multipart/alternative mail correctly (#9367)
# (test is not as precise/clear as it could be w.r.t. email tree structure,
# but it's good enough.)
>>> headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
>>> subject, from_email, to = 'hello', 'from@example.com', 'to@example.com'
>>> text_content = 'This is an important message.'
>>> html_content = '<p>This is an <strong>important</strong> message.</p>'
>>> msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers=headers)
>>> msg.attach_alternative(html_content, "text/html")
>>> msg.attach("an attachment.pdf", "%PDF-1.4.%...", mimetype="application/pdf")
>>> print msg.message().as_string()
Content-Type: multipart/mixed; boundary="..."
MIME-Version: 1.0
Subject: hello
From: from@example.com
To: to@example.com
Date: Fri, 09 Nov 2001 01:08:47 -0000
Message-ID: foo
...
Content-Type: multipart/alternative; boundary="..."
MIME-Version: 1.0
...
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
...
This is an important message.
...
Content-Type: text/html; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
...
<p>This is an <strong>important</strong> message.</p>
...
...
Content-Type: application/pdf
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="an attachment.pdf"
...
JVBERi0xLjQuJS4uLg==
...
""" """

View File

@ -0,0 +1,22 @@
from django.test import TestCase
from regressiontests.one_to_one_regress.models import Place, UndergroundBar
class OneToOneDeletionTests(TestCase):
def test_reverse_relationship_cache_cascade(self):
"""
Regression test for #9023: accessing the reverse relationship shouldn't
result in a cascading delete().
"""
place = Place.objects.create(name="Dempsey's", address="623 Vermont St")
bar = UndergroundBar.objects.create(place=place, serves_cocktails=False)
# The bug in #9023: if you access the one-to-one relation *before*
# setting to None and deleting, the cascade happens anyway.
place.undergroundbar
bar.place.name='foo'
bar.place = None
bar.save()
place.delete()
self.assertEqual(Place.objects.all().count(), 0)
self.assertEqual(UndergroundBar.objects.all().count(), 1)