Skip to content

Commit

Permalink
Support django 3
Browse files Browse the repository at this point in the history
  • Loading branch information
Lubos Matl committed Jan 11, 2021
1 parent f271eeb commit 8d080d4
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 82 deletions.
9 changes: 6 additions & 3 deletions .travis.yml
@@ -1,11 +1,14 @@
language: python
python:
- "3.5"
- "3.6"
- "3.7"
- "3.8"
- "3.9"

env:
- DJANGO_VERSION=1.11
- DJANGO_VERSION=2.0
- DJANGO_VERSION=2.2
- DJANGO_VERSION=3.0
- DJANGO_VERSION=3.1

# command to install dependencies
install:
Expand Down
13 changes: 6 additions & 7 deletions chamber/importers/__init__.py
@@ -1,17 +1,13 @@
import csv
import io

from itertools import zip_longest

from django.conf import settings

import pyprind


try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest


def simple_count(file):
lines = 0
for _ in file:
Expand Down Expand Up @@ -127,13 +123,16 @@ def import_rows(self, reader, row_count=0):
self._post_batch_create(self.get_batch_size(), row_count)
del batch[:]
if any(row): # Skip blank lines
batch.append(self.model_class(**self.get_fields_dict(row)))
batch.append(self.create_instance(row))
created += self.create_batch(batch)
self._post_batch_create(len(batch), row_count)
self._post_import_rows(created)

return created

def create_instance(self, row):
return self.model_class(**self.get_fields_dict(row))

def get_delete_existing_objects(self):
return self.delete_existing_objects

Expand Down
104 changes: 64 additions & 40 deletions chamber/models/__init__.py
@@ -1,7 +1,5 @@
import collections

from itertools import chain

from distutils.version import StrictVersion

import django
Expand Down Expand Up @@ -60,11 +58,24 @@ def __bool__(self):
Unknown = UnknownSingleton()


@singleton
class DeferredSingleton:

def __repr__(self):
return 'deferred'

def __bool__(self):
return False


Deferred = DeferredSingleton()


def unknown_model_fields_to_dict(instance, fields=None, exclude=None):

return {
field.name: Unknown
for field in chain(instance._meta.concrete_fields, instance._meta.many_to_many) # pylint: disable=W0212
for field in instance._meta.concrete_fields # pylint: disable=W0212
if not should_exclude_field(field, fields, exclude)
}

Expand All @@ -73,10 +84,9 @@ def model_to_dict(instance, fields=None, exclude=None):
"""
The same implementation as django model_to_dict but editable fields are allowed
"""

return {
field.name: field_to_dict(field, instance)
for field in chain(instance._meta.concrete_fields, instance._meta.many_to_many) # pylint: disable=W0212
for field in instance._meta.concrete_fields # pylint: disable=W0212
if not should_exclude_field(field, fields, exclude)
}

Expand All @@ -94,7 +104,7 @@ def __init__(self, initial_dict):

@property
def initial_values(self):
return self._initial_dict
return self._initial_dict.copy()

@property
def current_values(self):
Expand Down Expand Up @@ -134,9 +144,6 @@ def has_key(self, k):
def has_any_key(self, *keys):
return bool(set(self.keys()) & set(keys))

def update(self, *args, **kwargs):
raise AttributeError('Object is readonly')

def keys(self):
return self.diff.keys()

Expand Down Expand Up @@ -169,27 +176,41 @@ class DynamicChangedFields(ChangedFields):

def __init__(self, instance):
super().__init__(
self._get_unknown_dict(instance) if instance.is_adding else self._get_instance_dict(instance)
self._get_unknown_dict(instance)
)
self.instance = instance

def _get_unknown_dict(self, instance):
return unknown_model_fields_to_dict(
instance, fields=(field.name for field in instance._meta.fields)
)

def _get_instance_dict(self, instance):
return model_to_dict(
instance, fields=(field.name for field in instance._meta.fields)
)
return unknown_model_fields_to_dict(instance)

@property
def current_values(self):
return self._get_instance_dict(self.instance)
deferred_values = {
field_name: value for field_name, value in self._initial_dict.items()
if field_name in self.instance.get_deferred_fields()
}
current_values = model_to_dict(
self.instance,
exclude=set(deferred_values.keys())
)
current_values.update(deferred_values)
return current_values

def get_static_changes(self):
return StaticChangedFields(self.initial_values, self.current_values)

def from_db(self, fields=None):
if fields is None:
fields = {field_name for field_name, value in self._initial_dict.items() if value is not Deferred}

self._initial_dict.update(
model_to_dict(self.instance, fields=set(fields))
)

for field_name, value in self._initial_dict.items():
if value is Unknown:
self._initial_dict[field_name] = Deferred


class StaticChangedFields(ChangedFields):
"""
Expand All @@ -202,7 +223,7 @@ def __init__(self, initial_dict, current_dict):

@property
def current_values(self):
return self._current_dict
return self._current_dict.copy()


class ComparableModelMixin:
Expand Down Expand Up @@ -315,7 +336,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.is_adding = True
self.is_changing = False
self.changed_fields = DynamicChangedFields(self)
self._changed_fields = DynamicChangedFields(self)
self.post_save = Signal(self)

class Meta:
Expand All @@ -329,16 +350,24 @@ def from_db(cls, db, field_names, values):
new = super().from_db(db, field_names, values)
new.is_adding = False
new.is_changing = True
new.changed_fields = DynamicChangedFields(new)
updating_fields = [
f.name for f in cls._meta.concrete_fields
if len(values) == len(cls._meta.concrete_fields) or f.attname in field_names
]
new._changed_fields.from_db(fields=updating_fields)
return new

@property
def has_changed(self):
return bool(self.changed_fields)
return bool(self._changed_fields)

@property
def changed_fields(self):
return self._changed_fields.get_static_changes()

@property
def initial_values(self):
return self.changed_fields.initial_values
return self._changed_fields.initial_values

def full_clean(self, exclude=None, *args, **kwargs):
errors = {}
Expand Down Expand Up @@ -420,24 +449,24 @@ def _save(self, update_only_changed_fields=False, is_cleaned_pre_save=None, is_c
kwargs.update(self._get_save_extra_kwargs())

self._call_pre_save(
changed=self.is_changing, changed_fields=self.changed_fields.get_static_changes(), *args, **kwargs
changed=self.is_changing, changed_fields=self.changed_fields, *args, **kwargs
)
if is_cleaned_pre_save:
self._clean_pre_save(*args, **kwargs)
dispatcher_pre_save.send(
sender=origin, instance=self, changed=self.is_changing,
changed_fields=self.changed_fields.get_static_changes(),
changed_fields=self.changed_fields,
*args, **kwargs
)

if not update_fields and update_only_changed_fields:
update_fields = list(self.changed_fields.keys()) + ['changed_at']
update_fields = list(self._changed_fields.keys()) + ['changed_at']
# remove primary key from updating fields
if self._meta.pk.name in update_fields:
update_fields.remove(self._meta.pk.name)

# Changed fields must be cached before save, for post_save and signal purposes
post_save_changed_fields = self.changed_fields.get_static_changes()
post_save_changed_fields = self.changed_fields
post_save_is_changing = self.is_changing

self.save_simple(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
Expand Down Expand Up @@ -468,7 +497,7 @@ def save_simple(self, *args, **kwargs):
super().save(*args, **kwargs)
self.is_adding = False
self.is_changing = True
self.changed_fields = DynamicChangedFields(self)
self._changed_fields.from_db()

def save(self, update_only_changed_fields=False, *args, **kwargs):
if self._smart_meta.is_save_atomic:
Expand Down Expand Up @@ -510,22 +539,17 @@ def delete(self, *args, **kwargs):
else:
self._delete(*args, **kwargs)

def refresh_from_db(self, *args, **kwargs):
super().refresh_from_db(*args, **kwargs)
def refresh_from_db(self, using=None, fields=None):
super().refresh_from_db(using=using, fields=fields)
for key, value in self.__class__.__dict__.items():
if isinstance(value, cached_property):
self.__dict__.pop(key, None)
self.is_adding = False
self.is_changing = True
self.changed_fields = DynamicChangedFields(self)

if StrictVersion(get_main_version()) < StrictVersion('2.0'):
for field in [f for f in self._meta.get_fields() if f.is_relation]:
# For Generic relation related model is None
# https://docs.djangoproject.com/en/2.1/ref/models/meta/#migrating-from-the-old-api
cache_key = field.get_cache_name() if field.related_model else field.cache_attr
if cache_key in self.__dict__:
del self.__dict__[cache_key]

self._changed_fields.from_db(fields={
f.name for f in self._meta.concrete_fields if not fields or f.attname in fields or f.name in fields
})

return self

Expand Down
9 changes: 2 additions & 7 deletions chamber/multidomains/domain.py
@@ -1,5 +1,6 @@
from urllib.parse import urlparse

from django.apps import apps
from django.core.exceptions import ImproperlyConfigured


Expand Down Expand Up @@ -38,13 +39,7 @@ def url(self):

@property
def user_class(self):
try:
from django.apps import apps
get_model = apps.get_model
except ImportError:
from django.db.models.loading import get_model

return get_model(*self.user_model.split('.', 1))
return apps.get_model(*self.user_model.split('.', 1))


def get_domain(site_id):
Expand Down
6 changes: 1 addition & 5 deletions chamber/multidomains/urlresolvers.py
@@ -1,11 +1,7 @@
from urllib.parse import urlencode

from django.conf import settings

try:
from django.core.urlresolvers import reverse as django_reverse
except ImportError:
from django.urls import reverse as django_reverse
from django.urls import reverse as django_reverse



Expand Down
21 changes: 14 additions & 7 deletions chamber/utils/migrations/fixtures.py
Expand Up @@ -2,8 +2,11 @@

from io import StringIO


from django.db import DEFAULT_DB_ALIAS
from django.core.management import call_command
from django.core.serializers import base, python
from django.core.serializers import python, base
from django.core.management.commands import loaddata


class MigrationLoadFixture:
Expand All @@ -26,9 +29,13 @@ def _get_model(model_identifier):
raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier)

get_model_tmp = python._get_model # pylint: disable=W0212
python._get_model = _get_model
file = os.path.join(self.fixture_dir, self.fixture_filename)
if not os.path.isfile(file):
raise IOError('File "%s" does not exists' % file)
call_command('loaddata', file, stdout=StringIO())
python._get_model = get_model_tmp # pylint: disable=W0212
try:
python._get_model = _get_model
file = os.path.join(self.fixture_dir, self.fixture_filename)
if not os.path.isfile(file):
raise IOError('File "%s" does not exists' % file)
loaddata.Command().handle(
file, ignore=True, database=DEFAULT_DB_ALIAS, app_label=None, verbosity=0, exclude=[], format='json'
)
finally:
python._get_model = get_model_tmp # pylint: disable=W0212
2 changes: 1 addition & 1 deletion example/dj/apps/test_chamber/tests/importers.py
@@ -1,4 +1,4 @@
from six import StringIO
from io import StringIO

from django.core.management import call_command
from django.test import TestCase
Expand Down

0 comments on commit 8d080d4

Please sign in to comment.