morango 0.6.11__py2.py3-none-any.whl → 0.8.6__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. morango/__init__.py +1 -6
  2. morango/api/serializers.py +3 -0
  3. morango/api/viewsets.py +38 -23
  4. morango/apps.py +1 -2
  5. morango/constants/settings.py +3 -0
  6. morango/constants/transfer_stages.py +1 -1
  7. morango/constants/transfer_statuses.py +1 -1
  8. morango/errors.py +4 -0
  9. morango/management/commands/cleanupsyncs.py +64 -14
  10. morango/migrations/0001_initial.py +0 -2
  11. morango/migrations/0001_squashed_0024_auto_20240129_1757.py +583 -0
  12. morango/migrations/0002_auto_20170511_0400.py +0 -2
  13. morango/migrations/0002_store_idx_morango_deserialize.py +21 -0
  14. morango/migrations/0003_auto_20170519_0543.py +0 -2
  15. morango/migrations/0004_auto_20170520_2112.py +0 -2
  16. morango/migrations/0005_auto_20170629_2139.py +0 -2
  17. morango/migrations/0006_instanceidmodel_system_id.py +0 -2
  18. morango/migrations/0007_auto_20171018_1615.py +0 -2
  19. morango/migrations/0008_auto_20171114_2217.py +0 -2
  20. morango/migrations/0009_auto_20171205_0252.py +0 -2
  21. morango/migrations/0010_auto_20171206_1615.py +0 -2
  22. morango/migrations/0011_sharedkey.py +0 -2
  23. morango/migrations/0012_auto_20180927_1658.py +0 -2
  24. morango/migrations/0013_auto_20190627_1513.py +0 -2
  25. morango/migrations/0014_syncsession_extra_fields.py +0 -2
  26. morango/migrations/0015_auto_20200508_2104.py +2 -3
  27. morango/migrations/0016_store_deserialization_error.py +2 -3
  28. morango/migrations/0017_store_last_transfer_session_id.py +1 -2
  29. morango/migrations/0018_auto_20210714_2216.py +2 -3
  30. morango/migrations/0019_auto_20220113_1807.py +2 -3
  31. morango/migrations/0020_postgres_fix_nullable.py +0 -2
  32. morango/migrations/0021_store_partition_index_create.py +0 -2
  33. morango/migrations/0022_rename_instance_fields.py +23 -0
  34. morango/migrations/0023_add_instance_id_fields.py +24 -0
  35. morango/migrations/0024_auto_20240129_1757.py +28 -0
  36. morango/models/__init__.py +0 -6
  37. morango/models/certificates.py +137 -28
  38. morango/models/core.py +48 -46
  39. morango/models/fields/crypto.py +20 -6
  40. morango/models/fields/uuids.py +2 -1
  41. morango/models/utils.py +5 -6
  42. morango/proquint.py +2 -3
  43. morango/registry.py +28 -49
  44. morango/sync/backends/base.py +34 -0
  45. morango/sync/backends/postgres.py +129 -0
  46. morango/sync/backends/utils.py +10 -11
  47. morango/sync/context.py +198 -13
  48. morango/sync/controller.py +33 -11
  49. morango/sync/operations.py +324 -251
  50. morango/sync/session.py +11 -0
  51. morango/sync/syncsession.py +78 -85
  52. morango/sync/utils.py +18 -0
  53. morango/urls.py +3 -3
  54. morango/utils.py +2 -3
  55. {morango-0.6.11.dist-info → morango-0.8.6.dist-info}/METADATA +29 -14
  56. morango-0.8.6.dist-info/RECORD +79 -0
  57. {morango-0.6.11.dist-info → morango-0.8.6.dist-info}/WHEEL +1 -1
  58. morango/models/morango_mptt.py +0 -33
  59. morango/settings.py +0 -115
  60. morango/wsgi.py +0 -33
  61. morango-0.6.11.dist-info/RECORD +0 -77
  62. {morango-0.6.11.dist-info → morango-0.8.6.dist-info/licenses}/AUTHORS.md +0 -0
  63. {morango-0.6.11.dist-info → morango-0.8.6.dist-info/licenses}/LICENSE +0 -0
  64. {morango-0.6.11.dist-info → morango-0.8.6.dist-info}/top_level.txt +0 -0
morango/models/core.py CHANGED
@@ -1,5 +1,3 @@
1
- from __future__ import unicode_literals
2
-
3
1
  import functools
4
2
  import json
5
3
  import logging
@@ -17,13 +15,13 @@ from django.db.models import F
17
15
  from django.db.models import Func
18
16
  from django.db.models import Max
19
17
  from django.db.models import Q
18
+ from django.db.models import signals
20
19
  from django.db.models import TextField
21
20
  from django.db.models import Value
22
21
  from django.db.models.deletion import Collector
23
22
  from django.db.models.expressions import CombinedExpression
24
23
  from django.db.models.fields.related import ForeignKey
25
24
  from django.db.models.functions import Cast
26
- from django.utils import six
27
25
  from django.utils import timezone
28
26
  from django.utils.functional import cached_property
29
27
 
@@ -38,7 +36,6 @@ from morango.models.fields.uuids import UUIDField
38
36
  from morango.models.fields.uuids import UUIDModelMixin
39
37
  from morango.models.fsic_utils import remove_redundant_instance_counters
40
38
  from morango.models.manager import SyncableModelManager
41
- from morango.models.morango_mptt import MorangoMPTTModel
42
39
  from morango.models.utils import get_0_4_system_parameters
43
40
  from morango.models.utils import get_0_5_mac_address
44
41
  from morango.models.utils import get_0_5_system_id
@@ -122,7 +119,7 @@ class InstanceIDModel(models.Model):
122
119
  hostname = models.TextField()
123
120
  sysversion = models.TextField()
124
121
  node_id = models.CharField(max_length=20, blank=True)
125
- database = models.ForeignKey(DatabaseIDModel)
122
+ database = models.ForeignKey(DatabaseIDModel, on_delete=models.CASCADE)
126
123
  counter = models.IntegerField(default=0)
127
124
  current = models.BooleanField(default=True)
128
125
  db_path = models.CharField(max_length=1000)
@@ -219,10 +216,10 @@ class SyncSession(models.Model):
219
216
 
220
217
  # track the certificates being used by each side for this session
221
218
  client_certificate = models.ForeignKey(
222
- Certificate, blank=True, null=True, related_name="syncsessions_client"
219
+ Certificate, blank=True, null=True, related_name="syncsessions_client", on_delete=models.CASCADE
223
220
  )
224
221
  server_certificate = models.ForeignKey(
225
- Certificate, blank=True, null=True, related_name="syncsessions_server"
222
+ Certificate, blank=True, null=True, related_name="syncsessions_server", on_delete=models.CASCADE
226
223
  )
227
224
 
228
225
  # track the morango profile this sync session is happening for
@@ -240,9 +237,12 @@ class SyncSession(models.Model):
240
237
  client_ip = models.CharField(max_length=100, blank=True)
241
238
  server_ip = models.CharField(max_length=100, blank=True)
242
239
 
243
- # serialized copies of the client and server instance model fields, for debugging/tracking purposes
244
- client_instance = models.TextField(default="{}")
245
- server_instance = models.TextField(default="{}")
240
+ # track the instance IDs for convenient querying, as well as serialized copies of
241
+ # the client and server instance model fields for debugging/tracking purposes
242
+ client_instance_id = models.UUIDField(null=True, blank=True)
243
+ client_instance_json = models.TextField(default="{}")
244
+ server_instance_id = models.UUIDField(null=True, blank=True)
245
+ server_instance_json = models.TextField(default="{}")
246
246
 
247
247
  # used to store other data we may need to know about this sync session
248
248
  extra_fields = models.TextField(default="{}")
@@ -252,11 +252,11 @@ class SyncSession(models.Model):
252
252
 
253
253
  @cached_property
254
254
  def client_instance_data(self):
255
- return json.loads(self.client_instance)
255
+ return json.loads(self.client_instance_json)
256
256
 
257
257
  @cached_property
258
258
  def server_instance_data(self):
259
- return json.loads(self.server_instance)
259
+ return json.loads(self.server_instance_json)
260
260
 
261
261
 
262
262
  class TransferSession(models.Model):
@@ -280,7 +280,7 @@ class TransferSession(models.Model):
280
280
  bytes_sent = models.BigIntegerField(default=0, null=True, blank=True)
281
281
  bytes_received = models.BigIntegerField(default=0, null=True, blank=True)
282
282
 
283
- sync_session = models.ForeignKey(SyncSession)
283
+ sync_session = models.ForeignKey(SyncSession, on_delete=models.CASCADE)
284
284
 
285
285
  # track when the transfer session started and the last time there was activity on it
286
286
  start_timestamp = models.DateTimeField(default=timezone.now)
@@ -351,10 +351,10 @@ class TransferSession(models.Model):
351
351
 
352
352
  def get_touched_record_ids_for_model(self, model):
353
353
  if isinstance(model, SyncableModel) or (
354
- isinstance(model, six.class_types) and issubclass(model, SyncableModel)
354
+ isinstance(model, type) and issubclass(model, SyncableModel)
355
355
  ):
356
356
  model = model.morango_model_name
357
- _assert(isinstance(model, six.string_types), "Model must resolve to string")
357
+ _assert(isinstance(model, str), "Model must resolve to string")
358
358
  return Store.objects.filter(
359
359
  model_name=model, last_transfer_session_id=self.id
360
360
  ).values_list("id", flat=True)
@@ -418,7 +418,7 @@ class StoreQueryset(models.QuerySet):
418
418
  self.annotate(id_cast=Cast("id", TextField()))
419
419
  # remove dashes from char uuid
420
420
  .annotate(
421
- fixed_id=Func(F("id_cast"), Value("-"), Value(""), function="replace")
421
+ fixed_id=Func(F("id_cast"), Value("-"), Value(""), function="replace", output_field=TextField())
422
422
  )
423
423
  # return as list
424
424
  .values_list("fixed_id", flat=True)
@@ -449,7 +449,8 @@ class Store(AbstractStore):
449
449
 
450
450
  class Meta:
451
451
  indexes = [
452
- models.Index(fields=['partition'], name='idx_morango_store_partition'),
452
+ models.Index(fields=["partition"], name="idx_morango_store_partition"),
453
+ models.Index(fields=["profile", "model_name", "partition", "dirty_bit"], condition=models.Q(dirty_bit=True), name="idx_morango_deserialize"),
453
454
  ]
454
455
 
455
456
  def _deserialize_store_model(self, fk_cache, defer_fks=False): # noqa: C901
@@ -465,14 +466,14 @@ class Store(AbstractStore):
465
466
  klass_model = syncable_models.get_model(self.profile, self.model_name)
466
467
  # if store model marked as deleted, attempt to delete in app layer
467
468
  if self.deleted:
468
- # if hard deleted, propagate to related models
469
- if self.hard_deleted:
470
- try:
471
- klass_model.objects.get(id=self.id).delete(hard_delete=True)
472
- except klass_model.DoesNotExist:
473
- pass
474
- else:
475
- klass_model.objects.filter(id=self.id).delete()
469
+ # Don't differentiate between deletion and hard deletion here,
470
+ # as we don't want to add additional tracking for models in either case,
471
+ # just to actually delete them.
472
+ # Import here to avoid circular import, as the utils module
473
+ # imports core models.
474
+ from morango.sync.utils import mute_signals
475
+ with mute_signals(signals.post_delete):
476
+ klass_model.syncing_objects.filter(id=self.id).delete()
476
477
  return None, deferred_fks
477
478
  else:
478
479
  # load model into memory
@@ -527,7 +528,7 @@ class Buffer(AbstractStore):
527
528
  dequeuing into the local store.
528
529
  """
529
530
 
530
- transfer_session = models.ForeignKey(TransferSession)
531
+ transfer_session = models.ForeignKey(TransferSession, on_delete=models.CASCADE)
531
532
  model_uuid = UUIDField()
532
533
 
533
534
  class Meta:
@@ -608,7 +609,7 @@ class DatabaseMaxCounter(AbstractCounter):
608
609
  )
609
610
  else:
610
611
  updated_fsic = {}
611
- for key, value in six.iteritems(fsics):
612
+ for key, value in fsics.items():
612
613
  if key in internal_fsic:
613
614
  # if same instance id, update fsic with larger value
614
615
  if fsics[key] > internal_fsic[key]:
@@ -618,7 +619,7 @@ class DatabaseMaxCounter(AbstractCounter):
618
619
  updated_fsic[key] = fsics[key]
619
620
 
620
621
  # load database max counters
621
- for (key, value) in six.iteritems(updated_fsic):
622
+ for (key, value) in updated_fsic.items():
622
623
  for f in sync_filter:
623
624
  DatabaseMaxCounter.objects.update_or_create(
624
625
  instance_id=key, partition=f, defaults={"counter": value}
@@ -761,7 +762,7 @@ class RecordMaxCounter(AbstractCounter):
761
762
  during the sync process.
762
763
  """
763
764
 
764
- store_model = models.ForeignKey(Store)
765
+ store_model = models.ForeignKey(Store, on_delete=models.CASCADE)
765
766
 
766
767
  class Meta:
767
768
  unique_together = ("store_model", "instance_id")
@@ -773,11 +774,13 @@ class RecordMaxCounterBuffer(AbstractCounter):
773
774
  until they are sent or received by another morango instance.
774
775
  """
775
776
 
776
- transfer_session = models.ForeignKey(TransferSession)
777
+ transfer_session = models.ForeignKey(TransferSession, on_delete=models.CASCADE)
777
778
  model_uuid = UUIDField(db_index=True)
778
779
 
779
780
 
780
- ForeignKeyReference = namedtuple("ForeignKeyReference", ["from_field", "from_pk", "to_pk"])
781
+ ForeignKeyReference = namedtuple(
782
+ "ForeignKeyReference", ["from_field", "from_pk", "to_pk"]
783
+ )
781
784
 
782
785
 
783
786
  class SyncableModel(UUIDModelMixin):
@@ -803,6 +806,11 @@ class SyncableModel(UUIDModelMixin):
803
806
 
804
807
  objects = SyncableModelManager()
805
808
 
809
+ # Add a special syncing_objects queryset to every SyncableModel for use in syncing operations.
810
+ # This means that we still deal with the entire set of objects when syncing, even if the default
811
+ # model manager has been overridden to filter the queryset.
812
+ syncing_objects = SyncableModelManager()
813
+
806
814
  class Meta:
807
815
  abstract = True
808
816
 
@@ -839,10 +847,8 @@ class SyncableModel(UUIDModelMixin):
839
847
  with transaction.atomic():
840
848
  if hard_delete:
841
849
  # set hard deletion for all related models
842
- for model, instances in six.iteritems(collector.data):
843
- if issubclass(model, SyncableModel) or issubclass(
844
- model, MorangoMPTTModel
845
- ):
850
+ for model, instances in collector.data.items():
851
+ if issubclass(model, SyncableModel):
846
852
  for obj in instances:
847
853
  obj._update_hard_deleted_models()
848
854
  return collector.delete()
@@ -908,7 +914,7 @@ class SyncableModel(UUIDModelMixin):
908
914
  ForeignKeyReference(
909
915
  from_field=field.attname,
910
916
  from_pk=self.pk,
911
- to_pk=getattr(self, field.attname)
917
+ to_pk=getattr(self, field.attname),
912
918
  )
913
919
  )
914
920
 
@@ -926,15 +932,8 @@ class SyncableModel(UUIDModelMixin):
926
932
  continue
927
933
  if f.attname in self._morango_internal_fields_not_to_serialize:
928
934
  continue
929
- # case if model is morango mptt
930
- if f.attname in getattr(
931
- self,
932
- "_internal_mptt_fields_not_to_serialize",
933
- "_internal_fields_not_to_serialize",
934
- ):
935
- continue
936
- if hasattr(f, "value_from_object_json_compatible"):
937
- data[f.attname] = f.value_from_object_json_compatible(self)
935
+ if getattr(f, "morango_serialize_to_string", False):
936
+ data[f.attname] = f.value_to_string(self)
938
937
  else:
939
938
  data[f.attname] = f.value_from_object(self)
940
939
  return data
@@ -945,7 +944,10 @@ class SyncableModel(UUIDModelMixin):
945
944
  kwargs = {}
946
945
  for f in cls._meta.concrete_fields:
947
946
  if f.attname in dict_model:
948
- kwargs[f.attname] = dict_model[f.attname]
947
+ if getattr(f, "morango_serialize_to_string", False):
948
+ kwargs[f.attname] = f.to_python(dict_model[f.attname])
949
+ else:
950
+ kwargs[f.attname] = dict_model[f.attname]
949
951
  return cls(**kwargs)
950
952
 
951
953
  @classmethod
@@ -21,6 +21,16 @@ except ImportError:
21
21
  M2CRYPTO_EXISTS = False
22
22
 
23
23
  try:
24
+ # Pre-empt the PanicException that importing cryptography can cause
25
+ # when we are using a non-compatible version of cffi on Python 3.13
26
+ # this happens because of static depdendency bundling in Kolibri
27
+ import cffi
28
+
29
+ if sys.version_info > (3, 13):
30
+ if hasattr(cffi, "__version_info__"):
31
+ if cffi.__version_info__ < (1, 17, 1):
32
+ raise ImportError
33
+
24
34
  from cryptography.hazmat.backends import default_backend
25
35
  from cryptography import exceptions as crypto_exceptions
26
36
 
@@ -40,11 +50,15 @@ try:
40
50
  CRYPTOGRAPHY_EXISTS = True
41
51
  except ImportError:
42
52
  CRYPTOGRAPHY_EXISTS = False
53
+ except BaseException as e:
54
+ # Still catch PanicExceptions just in case.
55
+ if "Python API call failed" in str(e):
56
+ CRYPTOGRAPHY_EXISTS = False
57
+ else:
58
+ # Otherwise raise the error again to avoid silently catching other errors
59
+ raise
43
60
 
44
- if sys.version_info[0] < 3:
45
- from base64 import encodestring as b64encode, decodestring as b64decode
46
- else:
47
- from base64 import encodebytes as b64encode, decodebytes as b64decode
61
+ from base64 import encodebytes as b64encode, decodebytes as b64decode
48
62
 
49
63
 
50
64
  PKCS8_HEADER = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A"
@@ -347,7 +361,7 @@ class RSAKeyBaseField(models.TextField):
347
361
 
348
362
 
349
363
  class PublicKeyField(RSAKeyBaseField):
350
- def from_db_value(self, value, expression, connection, context):
364
+ def from_db_value(self, value, expression, connection):
351
365
  if not value:
352
366
  return None
353
367
  return Key(public_key_string=value)
@@ -366,7 +380,7 @@ class PublicKeyField(RSAKeyBaseField):
366
380
 
367
381
 
368
382
  class PrivateKeyField(RSAKeyBaseField):
369
- def from_db_value(self, value, expression, connection, context):
383
+ def from_db_value(self, value, expression, connection):
370
384
  if not value:
371
385
  return None
372
386
  return Key(private_key_string=value)
@@ -2,6 +2,7 @@ import hashlib
2
2
  import uuid
3
3
 
4
4
  from django.db import models
5
+
5
6
  from morango.utils import _assert
6
7
 
7
8
 
@@ -41,7 +42,7 @@ class UUIDField(models.CharField):
41
42
  raise TypeError(self.error_messages["invalid"] % {"value": value})
42
43
  return value.hex
43
44
 
44
- def from_db_value(self, value, expression, connection, context):
45
+ def from_db_value(self, value, expression, connection):
45
46
  return self.to_python(value)
46
47
 
47
48
  def to_python(self, value):
morango/models/utils.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import hashlib
2
- import ifcfg
3
2
  import os
4
3
  import platform
5
4
  import socket
@@ -7,10 +6,10 @@ import subprocess
7
6
  import sys
8
7
  import uuid
9
8
 
10
- from .fields.uuids import sha2_uuid
11
-
9
+ import ifcfg
12
10
  from django.conf import settings
13
- from django.utils import six
11
+
12
+ from .fields.uuids import sha2_uuid
14
13
 
15
14
 
16
15
  def _get_database_path():
@@ -109,7 +108,7 @@ def _get_android_uuid():
109
108
  def _do_salted_hash(value):
110
109
  if not value:
111
110
  return ""
112
- if not isinstance(value, six.string_types):
111
+ if not isinstance(value, str):
113
112
  value = str(value)
114
113
  try:
115
114
  value = value.encode()
@@ -185,7 +184,7 @@ def _get_mac_address_flags(mac):
185
184
  """
186
185
  See: https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local
187
186
  """
188
- if isinstance(mac, six.integer_types):
187
+ if isinstance(mac, int):
189
188
  mac = _mac_int_to_ether(mac)
190
189
 
191
190
  first_octet = int(mac[:2], base=16)
morango/proquint.py CHANGED
@@ -4,7 +4,6 @@ The simplest ways to use this module are the :func:`humanize` and :func:`uuid`
4
4
  functions. For tighter control over the output, see :class:`HumanHasher`.
5
5
  """
6
6
  import uuid
7
- from django.utils import six
8
7
 
9
8
  # Copyright (c) 2014 SUNET. All rights reserved.
10
9
  #
@@ -59,7 +58,7 @@ def from_int(data):
59
58
  :type data: int
60
59
  :rtype: string
61
60
  """
62
- if not isinstance(data, six.integer_types):
61
+ if not isinstance(data, int):
63
62
  raise TypeError("Input must be integer")
64
63
 
65
64
  res = []
@@ -84,7 +83,7 @@ def to_int(data):
84
83
  :type data: string
85
84
  :rtype: int
86
85
  """
87
- if not isinstance(data, six.string_types):
86
+ if not isinstance(data, str):
88
87
  raise TypeError("Input must be string")
89
88
 
90
89
  res = 0
morango/registry.py CHANGED
@@ -2,12 +2,11 @@
2
2
  `SyncableModelRegistry` holds all syncable models for a project, on a per profile basis.
3
3
  This class is registered at app load time for morango in `apps.py`.
4
4
  """
5
- import sys
6
5
  import inspect
6
+ import sys
7
7
  from collections import OrderedDict
8
8
 
9
9
  from django.db.models.fields.related import ForeignKey
10
- from django.utils import six
11
10
 
12
11
  from morango.constants import transfer_stages
13
12
  from morango.errors import InvalidMorangoModelConfiguration
@@ -19,7 +18,7 @@ from morango.utils import SETTINGS
19
18
 
20
19
  def _get_foreign_key_classes(m):
21
20
  return set(
22
- [field.rel.to for field in m._meta.fields if isinstance(field, ForeignKey)]
21
+ [field.related_model for field in m._meta.fields if isinstance(field, ForeignKey)]
23
22
  )
24
23
 
25
24
 
@@ -36,6 +35,24 @@ def _multiple_self_ref_fk_check(class_model):
36
35
  return False
37
36
 
38
37
 
38
+ def _check_manager(name, objects):
39
+ from morango.models.manager import SyncableModelManager
40
+ from morango.models.query import SyncableModelQuerySet
41
+ # syncable model checks
42
+ if not isinstance(objects, SyncableModelManager):
43
+ raise InvalidMorangoModelConfiguration(
44
+ "Manager for {} must inherit from SyncableModelManager.".format(
45
+ name
46
+ )
47
+ )
48
+ if not isinstance(objects.none(), SyncableModelQuerySet):
49
+ raise InvalidMorangoModelConfiguration(
50
+ "Queryset for {} model must inherit from SyncableModelQuerySet.".format(
51
+ name
52
+ )
53
+ )
54
+
55
+
39
56
  class SyncableModelRegistry(object):
40
57
  def __init__(self):
41
58
  self.profile_models = {}
@@ -99,8 +116,6 @@ class SyncableModelRegistry(object):
99
116
 
100
117
  import django.apps
101
118
  from morango.models.core import SyncableModel
102
- from morango.models.manager import SyncableModelManager
103
- from morango.models.query import SyncableModelQuerySet
104
119
 
105
120
  model_list = []
106
121
  for model in django.apps.apps.get_models():
@@ -111,49 +126,10 @@ class SyncableModelRegistry(object):
111
126
  raise InvalidMorangoModelConfiguration(
112
127
  "Syncing models with more than 1 self referential ForeignKey is not supported."
113
128
  )
114
- try:
115
- from mptt import models
116
- from morango.models.morango_mptt import (
117
- MorangoMPTTModel,
118
- MorangoMPTTTreeManager,
119
- MorangoTreeQuerySet,
120
- )
129
+ # Check both the objects and the syncing_objects querysets.
130
+ _check_manager(name, model.objects)
131
+ _check_manager(name, model.syncing_objects)
121
132
 
122
- # mptt syncable model checks
123
- if issubclass(model, models.MPTTModel):
124
- if not issubclass(model, MorangoMPTTModel):
125
- raise InvalidMorangoModelConfiguration(
126
- "{} that inherits from MPTTModel, should instead inherit from MorangoMPTTModel.".format(
127
- name
128
- )
129
- )
130
- if not isinstance(model.objects, MorangoMPTTTreeManager):
131
- raise InvalidMorangoModelConfiguration(
132
- "Manager for {} must inherit from MorangoMPTTTreeManager.".format(
133
- name
134
- )
135
- )
136
- if not isinstance(model.objects.none(), MorangoTreeQuerySet):
137
- raise InvalidMorangoModelConfiguration(
138
- "Queryset for {} model must inherit from MorangoTreeQuerySet.".format(
139
- name
140
- )
141
- )
142
- except ImportError:
143
- pass
144
- # syncable model checks
145
- if not isinstance(model.objects, SyncableModelManager):
146
- raise InvalidMorangoModelConfiguration(
147
- "Manager for {} must inherit from SyncableModelManager.".format(
148
- name
149
- )
150
- )
151
- if not isinstance(model.objects.none(), SyncableModelQuerySet):
152
- raise InvalidMorangoModelConfiguration(
153
- "Queryset for {} model must inherit from SyncableModelQuerySet.".format(
154
- name
155
- )
156
- )
157
133
  if model._meta.many_to_many:
158
134
  raise UnsupportedFieldType(
159
135
  "{} model with a ManyToManyField is not supported in morango."
@@ -178,7 +154,7 @@ class SyncableModelRegistry(object):
178
154
  self._insert_model_in_dependency_order(model, profile)
179
155
 
180
156
  # for each profile, create a dict mapping from morango_model_name to model class
181
- for profile, model_list in six.iteritems(self.profile_models):
157
+ for profile, model_list in self.profile_models.items():
182
158
  mapping = OrderedDict()
183
159
  for model in model_list:
184
160
  mapping[model.morango_model_name] = model
@@ -196,6 +172,7 @@ class SessionMiddlewareOperations(list):
196
172
  Iterable list class that holds and initializes a list of transfer operations as configured
197
173
  through Morango settings, and associate the group with a transfer stage by `related_stage`
198
174
  """
175
+
199
176
  __slots__ = ("related_stage",)
200
177
 
201
178
  def __init__(self, related_stage):
@@ -230,7 +207,9 @@ class SessionMiddlewareOperations(list):
230
207
  return result
231
208
  else:
232
209
  raise NotImplementedError(
233
- "Operation for {} stage has no middleware".format(self.related_stage)
210
+ "Operation for {} stage has no applicable middleware for context '{}'".format(
211
+ self.related_stage, context.__class__.__name__
212
+ )
234
213
  )
235
214
 
236
215
 
@@ -1,3 +1,5 @@
1
+ from contextlib import contextmanager
2
+
1
3
  from morango.models.core import Buffer
2
4
  from morango.models.core import RecordMaxCounter
3
5
  from morango.models.core import RecordMaxCounterBuffer
@@ -10,6 +12,19 @@ class BaseSQLWrapper(object):
10
12
  def __init__(self, connection):
11
13
  self.connection = connection
12
14
 
15
+ def _is_transaction_isolation_error(self, error):
16
+ """
17
+ Determine if an error is related to transaction isolation
18
+ :param error: An exception
19
+ :return: A bool whether the error is a transaction isolation error
20
+ """
21
+ return False
22
+
23
+ @contextmanager
24
+ def _set_transaction_repeatable_read(self):
25
+ """Set the current transaction isolation level"""
26
+ yield
27
+
13
28
  def _create_placeholder_list(self, fields, db_values):
14
29
  # number of rows to update
15
30
  num_of_rows = len(db_values) // len(fields)
@@ -176,3 +191,22 @@ class BaseSQLWrapper(object):
176
191
  name=name, fields=", ".join(field_sqls)
177
192
  )
178
193
  cursor.execute(sql, fields_params)
194
+
195
+ def _lock_all_partitions(self, shared=False):
196
+ """
197
+ Execute a lock within the database for all partitions, if the backend supports it. The lock
198
+ should block until acquired
199
+
200
+ :param shared: Whether or not the lock is exclusive or shared
201
+ """
202
+ pass
203
+
204
+ def _lock_partition(self, partition, shared=False):
205
+ """
206
+ Execute a lock within the database for a specific partition, if the database supports it.
207
+ The lock should block until acquired
208
+
209
+ :param partition: The partition prefix string to lock
210
+ :param shared: Whether or not the lock is exclusive or shared
211
+ """
212
+ pass