Commit 42736ac8 authored by Anubhav Joshi's avatar Anubhav Joshi Committed by Tim Graham
Browse files

Fixed #21430 -- Added a RuntimeWarning when unpickling Models and QuerySets...

Fixed #21430 -- Added a RuntimeWarning when unpickling Models and QuerySets from a different Django version.

Thanks FunkyBob for the suggestion, prasoon2211 for the initial patch,
and akaariai, loic, and charettes for helping in shaping the patch.
parent e163a3d1
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
from django.core import signals
from django.db.utils import (DEFAULT_DB_ALIAS, DataError, OperationalError,
    IntegrityError, InternalError, ProgrammingError, NotSupportedError,
    DatabaseError, InterfaceError, Error, ConnectionHandler, ConnectionRouter)
from django.db.utils import (DEFAULT_DB_ALIAS, DJANGO_VERSION_PICKLE_KEY,
    DataError, OperationalError, IntegrityError, InternalError, ProgrammingError,
    NotSupportedError, DatabaseError, InterfaceError, Error, ConnectionHandler,
    ConnectionRouter)


__all__ = [
    'backend', 'connection', 'connections', 'router', 'DatabaseError',
    'IntegrityError', 'InternalError', 'ProgrammingError', 'DataError',
    'NotSupportedError', 'Error', 'InterfaceError', 'OperationalError',
    'DEFAULT_DB_ALIAS'
    'DEFAULT_DB_ALIAS', 'DJANGO_VERSION_PICKLE_KEY'
]

connections = ConnectionHandler()
+20 −1
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@ from django.core import checks
from django.core.exceptions import (ObjectDoesNotExist,
    MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS)
from django.db import (router, transaction, DatabaseError,
    DEFAULT_DB_ALIAS)
    DEFAULT_DB_ALIAS, DJANGO_VERSION_PICKLE_KEY)
from django.db.models.deletion import Collector
from django.db.models.fields import AutoField, FieldDoesNotExist
from django.db.models.fields.related import (ForeignObjectRel, ManyToOneRel,
@@ -30,6 +30,7 @@ from django.utils.functional import curry
from django.utils.six.moves import zip
from django.utils.text import get_text_list, capfirst
from django.utils.translation import ugettext_lazy as _
from django.utils.version import get_version


def subclass_exception(name, parents, module, attached_to=None):
@@ -495,6 +496,7 @@ class Model(six.with_metaclass(ModelBase)):
        only module-level classes can be pickled by the default path.
        """
        data = self.__dict__
        data[DJANGO_VERSION_PICKLE_KEY] = get_version()
        if not self._deferred:
            class_id = self._meta.app_label, self._meta.object_name
            return model_unpickle, (class_id, [], simple_class_factory), data
@@ -507,6 +509,23 @@ class Model(six.with_metaclass(ModelBase)):
        class_id = model._meta.app_label, model._meta.object_name
        return (model_unpickle, (class_id, defers, deferred_class_factory), data)

    def __setstate__(self, state):
        msg = None
        pickled_version = state.get(DJANGO_VERSION_PICKLE_KEY)
        if pickled_version:
            current_version = get_version()
            if current_version != pickled_version:
                msg = ("Pickled model instance's Django version %s does"
                    " not match the current version %s."
                    % (pickled_version, current_version))
        else:
            msg = "Pickled model instance's Django version is not specified."

        if msg:
            warnings.warn(msg, RuntimeWarning, stacklevel=2)

        self.__dict__.update(state)

    def _get_pk_val(self, meta=None):
        if not meta:
            meta = self._meta
+22 −1
Original line number Diff line number Diff line
@@ -5,10 +5,12 @@ The main QuerySet implementation. This provides the public API for the ORM.
from collections import deque
import copy
import sys
import warnings

from django.conf import settings
from django.core import exceptions
from django.db import connections, router, transaction, IntegrityError
from django.db import (connections, router, transaction, IntegrityError,
    DJANGO_VERSION_PICKLE_KEY)
from django.db.models.constants import LOOKUP_SEP
from django.db.models.fields import AutoField, Empty
from django.db.models.query_utils import (Q, select_related_descend,
@@ -19,6 +21,7 @@ from django.db.models import sql
from django.utils.functional import partition
from django.utils import six
from django.utils import timezone
from django.utils.version import get_version

# The maximum number (one less than the max to be precise) of results to fetch
# in a get() query
@@ -90,8 +93,26 @@ class QuerySet(object):
        # Force the cache to be fully populated.
        self._fetch_all()
        obj_dict = self.__dict__.copy()
        obj_dict[DJANGO_VERSION_PICKLE_KEY] = get_version()
        return obj_dict

    def __setstate__(self, state):
        msg = None
        pickled_version = state.get(DJANGO_VERSION_PICKLE_KEY)
        if pickled_version:
            current_version = get_version()
            if current_version != pickled_version:
                msg = ("Pickled queryset instance's Django version %s does"
                    " not match the current version %s."
                    % (pickled_version, current_version))
        else:
            msg = "Pickled queryset instance's Django version is not specified."

        if msg:
            warnings.warn(msg, RuntimeWarning, stacklevel=2)

        self.__dict__.update(state)

    def __reduce__(self):
        """
        Used by pickle to deal with the types that we create dynamically when
+1 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ from django.utils import six


DEFAULT_DB_ALIAS = 'default'
DJANGO_VERSION_PICKLE_KEY = '_django_version'


class Error(Exception if six.PY3 else StandardError):
+25 −9
Original line number Diff line number Diff line
@@ -7,19 +7,14 @@ import subprocess

def get_version(version=None):
    "Returns a PEP 386-compliant version number from VERSION."
    if version is None:
        from django import VERSION as version
    else:
        assert len(version) == 5
        assert version[3] in ('alpha', 'beta', 'rc', 'final')
    version = get_complete_version(version)

    # Now build the two parts of the version number:
    # main = X.Y[.Z]
    # major = X.Y[.Z]
    # sub = .devN - for pre-alpha releases
    #     | {a|b|c}N - for alpha, beta and rc releases

    parts = 2 if version[2] == 0 else 3
    main = '.'.join(str(x) for x in version[:parts])
    major = get_major_version(version)

    sub = ''
    if version[3] == 'alpha' and version[4] == 0:
@@ -31,7 +26,28 @@ def get_version(version=None):
        mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'}
        sub = mapping[version[3]] + str(version[4])

    return str(main + sub)
    return str(major + sub)


def get_major_version(version=None):
    "Returns major version from VERSION."
    version = get_complete_version(version)
    parts = 2 if version[2] == 0 else 3
    major = '.'.join(str(x) for x in version[:parts])
    return major


def get_complete_version(version=None):
    """Returns a tuple of the django version. If version argument is non-empy,
    then checks for correctness of the tuple provided.
    """
    if version is None:
        from django import VERSION as version
    else:
        assert len(version) == 5
        assert version[3] in ('alpha', 'beta', 'rc', 'final')

    return version


def get_git_changeset():
Loading