Coverage for /Users/davegaeddert/Development/dropseed/plain/plain-models/plain/models/migrations/recorder.py: 88%

52 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-10-16 22:04 -0500

1from plain import models 

2from plain.models.db import DatabaseError 

3from plain.packages.registry import Packages 

4from plain.utils.functional import classproperty 

5from plain.utils.timezone import now 

6 

7from .exceptions import MigrationSchemaMissing 

8 

9 

10class MigrationRecorder: 

11 """ 

12 Deal with storing migration records in the database. 

13 

14 Because this table is actually itself used for dealing with model 

15 creation, it's the one thing we can't do normally via migrations. 

16 We manually handle table creation/schema updating (using schema backend) 

17 and then have a floating model to do queries with. 

18 

19 If a migration is unapplied its row is removed from the table. Having 

20 a row in the table always means a migration is applied. 

21 """ 

22 

23 _migration_class = None 

24 

25 @classproperty 

26 def Migration(cls): 

27 """ 

28 Lazy load to avoid PackageRegistryNotReady if installed packages import 

29 MigrationRecorder. 

30 """ 

31 if cls._migration_class is None: 

32 

33 class Migration(models.Model): 

34 app = models.CharField(max_length=255) 

35 name = models.CharField(max_length=255) 

36 applied = models.DateTimeField(default=now) 

37 

38 class Meta: 

39 packages = Packages() 

40 package_label = "migrations" 

41 db_table = "plainmigrations" 

42 

43 def __str__(self): 

44 return f"Migration {self.name} for {self.app}" 

45 

46 cls._migration_class = Migration 

47 return cls._migration_class 

48 

49 def __init__(self, connection): 

50 self.connection = connection 

51 

52 @property 

53 def migration_qs(self): 

54 return self.Migration.objects.using(self.connection.alias) 

55 

56 def has_table(self): 

57 """Return True if the plainmigrations table exists.""" 

58 with self.connection.cursor() as cursor: 

59 tables = self.connection.introspection.table_names(cursor) 

60 return self.Migration._meta.db_table in tables 

61 

62 def ensure_schema(self): 

63 """Ensure the table exists and has the correct schema.""" 

64 # If the table's there, that's fine - we've never changed its schema 

65 # in the codebase. 

66 if self.has_table(): 

67 return 

68 # Make the table 

69 try: 

70 with self.connection.schema_editor() as editor: 

71 editor.create_model(self.Migration) 

72 except DatabaseError as exc: 

73 raise MigrationSchemaMissing( 

74 "Unable to create the plainmigrations table (%s)" % exc 

75 ) 

76 

77 def applied_migrations(self): 

78 """ 

79 Return a dict mapping (package_name, migration_name) to Migration instances 

80 for all applied migrations. 

81 """ 

82 if self.has_table(): 

83 return { 

84 (migration.app, migration.name): migration 

85 for migration in self.migration_qs 

86 } 

87 else: 

88 # If the plainmigrations table doesn't exist, then no migrations 

89 # are applied. 

90 return {} 

91 

92 def record_applied(self, app, name): 

93 """Record that a migration was applied.""" 

94 self.ensure_schema() 

95 self.migration_qs.create(app=app, name=name) 

96 

97 def record_unapplied(self, app, name): 

98 """Record that a migration was unapplied.""" 

99 self.ensure_schema() 

100 self.migration_qs.filter(app=app, name=name).delete() 

101 

102 def flush(self): 

103 """Delete all migration records. Useful for testing migrations.""" 

104 self.migration_qs.all().delete()