43
43
ExternalVersionManager ,
44
44
InternalBuildManager ,
45
45
InternalVersionManager ,
46
- VersionAutomationRuleManager ,
47
46
VersionManager ,
48
47
)
49
48
from readthedocs .builds .querysets import (
@@ -1236,9 +1235,10 @@ class VersionAutomationRule(PolymorphicModel, TimeStampedModel):
1236
1235
related_name = 'automation_rules' ,
1237
1236
on_delete = models .CASCADE ,
1238
1237
)
1239
- priority = models .IntegerField (
1238
+ priority = models .PositiveIntegerField (
1240
1239
_ ('Rule priority' ),
1241
1240
help_text = _ ('A lower number (0) means a higher priority' ),
1241
+ default = 0 ,
1242
1242
)
1243
1243
description = models .CharField (
1244
1244
_ ('Description' ),
@@ -1283,8 +1283,6 @@ class VersionAutomationRule(PolymorphicModel, TimeStampedModel):
1283
1283
choices = VERSION_TYPES ,
1284
1284
)
1285
1285
1286
- objects = VersionAutomationRuleManager ()
1287
-
1288
1286
class Meta :
1289
1287
unique_together = (('project' , 'priority' ),)
1290
1288
ordering = ('priority' , '-modified' , '-created' )
@@ -1354,56 +1352,103 @@ def move(self, steps):
1354
1352
1355
1353
:param steps: Number of steps to be moved
1356
1354
(it can be negative)
1357
- :returns: True if the priority was changed
1358
1355
"""
1359
1356
total = self .project .automation_rules .count ()
1360
1357
current_priority = self .priority
1361
1358
new_priority = (current_priority + steps ) % total
1359
+ self .priority = new_priority
1360
+ self .save ()
1362
1361
1363
- if current_priority == new_priority :
1364
- return False
1362
+ def _change_priority (self ):
1363
+ """
1364
+ Re-order the priorities of the other rules when the priority of this rule changes.
1365
+
1366
+ If the rule is new, we just need to move all other rules down,
1367
+ so there is space for the new rule.
1368
+
1369
+ If the rule already exists, we need to move the other rules up or down,
1370
+ depending on the new priority, so we can insert the rule at the new priority.
1371
+
1372
+ The save() method needs to be called after this method.
1373
+ """
1374
+ total = self .project .automation_rules .count ()
1375
+
1376
+ # If the rule was just created, we just need to insert it at the given priority.
1377
+ # We do this by moving the other rules down before saving.
1378
+ if not self .pk :
1379
+ # A new rule can be created at the end as max.
1380
+ self .priority = min (self .priority , total )
1381
+
1382
+ # A new rule can't be created with a negative priority. All rules start at 0.
1383
+ self .priority = max (self .priority , 0 )
1365
1384
1366
- # Move other's priority
1367
- if new_priority > current_priority :
1368
- # It was moved down
1369
- rules = (
1370
- self .project .automation_rules
1371
- .filter (priority__gt = current_priority , priority__lte = new_priority )
1372
- # We sort the queryset in asc order
1373
- # to be updated in that order
1374
- # to avoid hitting the unique constraint (project, priority).
1375
- .order_by ('priority' )
1376
- )
1377
- expression = F ('priority' ) - 1
1378
- else :
1379
- # It was moved up
1380
1385
rules = (
1381
- self .project .automation_rules
1382
- .filter (priority__lt = current_priority , priority__gte = new_priority )
1383
- .exclude (pk = self .pk )
1386
+ self .project .automation_rules .filter (priority__gte = self .priority )
1384
1387
# We sort the queryset in desc order
1385
1388
# to be updated in that order
1386
1389
# to avoid hitting the unique constraint (project, priority).
1387
1390
.order_by ('-priority' )
1388
1391
)
1389
1392
expression = F ('priority' ) + 1
1393
+ else :
1394
+ current_priority = self .project .automation_rules .values_list (
1395
+ "priority" ,
1396
+ flat = True ,
1397
+ ).get (pk = self .pk )
1398
+
1399
+ # An existing rule can't be moved past the end.
1400
+ self .priority = min (self .priority , total - 1 )
1401
+
1402
+ # A new rule can't be created with a negative priority. all rules start at 0.
1403
+ self .priority = max (self .priority , 0 )
1404
+
1405
+ # The rule wasn't moved, so we don't need to do anything.
1406
+ if self .priority == current_priority :
1407
+ return
1408
+
1409
+ if self .priority > current_priority :
1410
+ # It was moved down, so we need to move the other rules up.
1411
+ rules = (
1412
+ self .project .automation_rules .filter (
1413
+ priority__gt = current_priority , priority__lte = self .priority
1414
+ )
1415
+ # We sort the queryset in asc order
1416
+ # to be updated in that order
1417
+ # to avoid hitting the unique constraint (project, priority).
1418
+ .order_by ("priority" )
1419
+ )
1420
+ expression = F ("priority" ) - 1
1421
+ else :
1422
+ # It was moved up, so we need to move the other rules down.
1423
+ rules = (
1424
+ self .project .automation_rules .filter (
1425
+ priority__lt = current_priority , priority__gte = self .priority
1426
+ )
1427
+ # We sort the queryset in desc order
1428
+ # to be updated in that order
1429
+ # to avoid hitting the unique constraint (project, priority).
1430
+ .order_by ("-priority" )
1431
+ )
1432
+ expression = F ("priority" ) + 1
1390
1433
1391
1434
# Put an impossible priority to avoid
1392
- # the unique constraint (project, priority)
1393
- # while updating.
1394
- self .priority = total + 99
1395
- self .save ()
1396
-
1397
- # We update each object one by one to
1398
- # avoid hitting the unique constraint (project, priority).
1435
+ # the unique constraint (project, priority) while updating.
1436
+ # We use update() instead of save() to avoid calling the save() method again.
1437
+ if self .pk :
1438
+ self ._meta .model .objects .filter (pk = self .pk ).update (priority = total + 99 )
1439
+
1440
+ # NOTE: we can't use rules.update(priority=expression), because SQLite is used
1441
+ # in tests and hits a UNIQUE constraint error. PostgreSQL doesn't have this issue.
1442
+ # We use update() instead of save() to avoid calling the save() method.
1399
1443
for rule in rules :
1400
- rule .priority = expression
1401
- rule .save ()
1444
+ self ._meta .model .objects .filter (pk = rule .pk ).update (priority = expression )
1402
1445
1403
- # Put back new priority
1404
- self .priority = new_priority
1405
- self .save ()
1406
- return True
1446
+ def save (self , * args , ** kwargs ):
1447
+ """Override method to update the other priorities before save."""
1448
+ self ._change_priority ()
1449
+ if not self .description :
1450
+ self .description = self .get_description ()
1451
+ super ().save (* args , ** kwargs )
1407
1452
1408
1453
def delete (self , * args , ** kwargs ):
1409
1454
"""Override method to update the other priorities after delete."""
@@ -1421,9 +1466,11 @@ def delete(self, *args, **kwargs):
1421
1466
)
1422
1467
# We update each object one by one to
1423
1468
# avoid hitting the unique constraint (project, priority).
1469
+ # We use update() instead of save() to avoid calling the save() method.
1424
1470
for rule in rules :
1425
- rule .priority = F ('priority' ) - 1
1426
- rule .save ()
1471
+ self ._meta .model .objects .filter (pk = rule .pk ).update (
1472
+ priority = F ("priority" ) - 1 ,
1473
+ )
1427
1474
1428
1475
def get_description (self ):
1429
1476
if self .description :
0 commit comments