Skip to content

Commit 89a58a0

Browse files
authored
Merge pull request #5998 from stsewd/add-move-method
Add move method to automation rule
2 parents 46abaa8 + bad40c3 commit 89a58a0

File tree

2 files changed

+338
-0
lines changed

2 files changed

+338
-0
lines changed

readthedocs/builds/models.py

+82
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from django.conf import settings
1010
from django.db import models
11+
from django.db.models import F
1112
from django.urls import reverse
1213
from django.utils import timezone
1314
from django.utils.translation import ugettext
@@ -1035,6 +1036,87 @@ def apply_action(self, version, match_result):
10351036
raise NotImplementedError
10361037
action(version, match_result, self.action_arg)
10371038

1039+
def move(self, steps):
1040+
"""
1041+
Change the priority of this Automation Rule.
1042+
1043+
This is done by moving it ``n`` steps,
1044+
relative to the other priority rules.
1045+
The priority from the other rules are updated too.
1046+
1047+
:param steps: Number of steps to be moved
1048+
(it can be negative)
1049+
:returns: True if the priority was changed
1050+
"""
1051+
total = self.project.automation_rules.count()
1052+
current_priority = self.priority
1053+
new_priority = (current_priority + steps) % total
1054+
1055+
if current_priority == new_priority:
1056+
return False
1057+
1058+
# Move other's priority
1059+
if new_priority > current_priority:
1060+
# It was moved down
1061+
rules = (
1062+
self.project.automation_rules
1063+
.filter(priority__gt=current_priority, priority__lte=new_priority)
1064+
# We sort the queryset in asc order
1065+
# to be updated in that order
1066+
# to avoid hitting the unique constraint (project, priority).
1067+
.order_by('priority')
1068+
)
1069+
expression = F('priority') - 1
1070+
else:
1071+
# It was moved up
1072+
rules = (
1073+
self.project.automation_rules
1074+
.filter(priority__lt=current_priority, priority__gte=new_priority)
1075+
.exclude(pk=self.pk)
1076+
# We sort the queryset in desc order
1077+
# to be updated in that order
1078+
# to avoid hitting the unique constraint (project, priority).
1079+
.order_by('-priority')
1080+
)
1081+
expression = F('priority') + 1
1082+
1083+
# Put an imposible priority to avoid
1084+
# the unique constraint (project, priority)
1085+
# while updating.
1086+
self.priority = total + 99
1087+
self.save()
1088+
1089+
# We update each object one by one to
1090+
# avoid hitting the unique constraint (project, priority).
1091+
for rule in rules:
1092+
rule.priority = expression
1093+
rule.save()
1094+
1095+
# Put back new priority
1096+
self.priority = new_priority
1097+
self.save()
1098+
return True
1099+
1100+
def delete(self, *args, **kwargs): # pylint: disable=arguments-differ
1101+
"""Override method to update the other priorities after delete."""
1102+
current_priority = self.priority
1103+
project = self.project
1104+
super().delete(*args, **kwargs)
1105+
1106+
rules = (
1107+
project.automation_rules
1108+
.filter(priority__gte=current_priority)
1109+
# We sort the queryset in asc order
1110+
# to be updated in that order
1111+
# to avoid hitting the unique constraint (project, priority).
1112+
.order_by('priority')
1113+
)
1114+
# We update each object one by one to
1115+
# avoid hitting the unique constraint (project, priority).
1116+
for rule in rules:
1117+
rule.priority = F('priority') - 1
1118+
rule.save()
1119+
10381120
def get_description(self):
10391121
if self.description:
10401122
return self.description

readthedocs/rtd_tests/tests/test_automation_rules.py

+256
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,259 @@ def test_add_rule_regex(self):
183183
)
184184
assert self.project.automation_rules.count() == 4
185185
assert rule.priority == 10
186+
187+
188+
@pytest.mark.django_db
189+
class TestAutomationRuleMove:
190+
191+
@pytest.fixture(autouse=True)
192+
def setup_method(self):
193+
self.project = get(Project)
194+
self.rule_0 = self._add_rule('Zero')
195+
self.rule_1 = self._add_rule('One')
196+
self.rule_2 = self._add_rule('Two')
197+
self.rule_3 = self._add_rule('Three')
198+
self.rule_4 = self._add_rule('Four')
199+
self.rule_5 = self._add_rule('Five')
200+
assert self.project.automation_rules.count() == 6
201+
202+
def _add_rule(self, description):
203+
rule = RegexAutomationRule.objects.add_rule(
204+
project=self.project,
205+
description=description,
206+
match_arg='.*',
207+
version_type=BRANCH,
208+
action=VersionAutomationRule.ACTIVATE_VERSION_ACTION,
209+
)
210+
return rule
211+
212+
def test_move_rule_one_step(self):
213+
self.rule_0.move(1)
214+
new_order = [
215+
self.rule_1,
216+
self.rule_0,
217+
self.rule_2,
218+
self.rule_3,
219+
self.rule_4,
220+
self.rule_5,
221+
]
222+
223+
for priority, rule in enumerate(self.project.automation_rules.all()):
224+
assert rule == new_order[priority]
225+
assert rule.priority == priority
226+
227+
def test_move_rule_positive_steps(self):
228+
self.rule_1.move(1)
229+
self.rule_1.move(2)
230+
231+
new_order = [
232+
self.rule_0,
233+
self.rule_2,
234+
self.rule_3,
235+
self.rule_4,
236+
self.rule_1,
237+
self.rule_5,
238+
]
239+
240+
for priority, rule in enumerate(self.project.automation_rules.all()):
241+
assert rule == new_order[priority]
242+
assert rule.priority == priority
243+
244+
def test_move_rule_positive_steps_overflow(self):
245+
self.rule_2.move(3)
246+
self.rule_2.move(2)
247+
248+
new_order = [
249+
self.rule_0,
250+
self.rule_2,
251+
self.rule_1,
252+
self.rule_3,
253+
self.rule_4,
254+
self.rule_5,
255+
]
256+
257+
for priority, rule in enumerate(self.project.automation_rules.all()):
258+
assert rule == new_order[priority]
259+
assert rule.priority == priority
260+
261+
def test_move_rules_positive_steps(self):
262+
self.rule_2.move(2)
263+
self.rule_0.refresh_from_db()
264+
self.rule_0.move(7)
265+
self.rule_4.refresh_from_db()
266+
self.rule_4.move(4)
267+
self.rule_1.refresh_from_db()
268+
self.rule_1.move(1)
269+
270+
new_order = [
271+
self.rule_4,
272+
self.rule_1,
273+
self.rule_0,
274+
self.rule_3,
275+
self.rule_2,
276+
self.rule_5,
277+
]
278+
279+
for priority, rule in enumerate(self.project.automation_rules.all()):
280+
assert rule == new_order[priority]
281+
assert rule.priority == priority
282+
283+
def test_move_rule_one_negative_step(self):
284+
self.rule_3.move(-1)
285+
new_order = [
286+
self.rule_0,
287+
self.rule_1,
288+
self.rule_3,
289+
self.rule_2,
290+
self.rule_4,
291+
self.rule_5,
292+
]
293+
294+
for priority, rule in enumerate(self.project.automation_rules.all()):
295+
assert rule == new_order[priority]
296+
assert rule.priority == priority
297+
298+
def test_move_rule_negative_steps(self):
299+
self.rule_4.move(-1)
300+
self.rule_4.move(-2)
301+
302+
new_order = [
303+
self.rule_0,
304+
self.rule_4,
305+
self.rule_1,
306+
self.rule_2,
307+
self.rule_3,
308+
self.rule_5,
309+
]
310+
311+
for priority, rule in enumerate(self.project.automation_rules.all()):
312+
assert rule == new_order[priority]
313+
assert rule.priority == priority
314+
315+
def test_move_rule_negative_steps_overflow(self):
316+
self.rule_2.move(-3)
317+
self.rule_2.move(-2)
318+
319+
new_order = [
320+
self.rule_0,
321+
self.rule_1,
322+
self.rule_3,
323+
self.rule_2,
324+
self.rule_4,
325+
self.rule_5,
326+
]
327+
328+
for priority, rule in enumerate(self.project.automation_rules.all()):
329+
assert rule == new_order[priority]
330+
assert rule.priority == priority
331+
332+
def test_move_rules_negative_steps(self):
333+
self.rule_2.move(-2)
334+
self.rule_5.refresh_from_db()
335+
self.rule_5.move(-7)
336+
self.rule_3.refresh_from_db()
337+
self.rule_3.move(-2)
338+
self.rule_1.refresh_from_db()
339+
self.rule_1.move(-1)
340+
341+
new_order = [
342+
self.rule_2,
343+
self.rule_3,
344+
self.rule_1,
345+
self.rule_0,
346+
self.rule_5,
347+
self.rule_4,
348+
]
349+
350+
for priority, rule in enumerate(self.project.automation_rules.all()):
351+
assert rule == new_order[priority]
352+
assert rule.priority == priority
353+
354+
def test_move_rules_up_and_down(self):
355+
self.rule_2.move(2)
356+
self.rule_5.refresh_from_db()
357+
self.rule_5.move(-3)
358+
self.rule_3.refresh_from_db()
359+
self.rule_3.move(4)
360+
self.rule_1.refresh_from_db()
361+
self.rule_1.move(-1)
362+
363+
new_order = [
364+
self.rule_0,
365+
self.rule_1,
366+
self.rule_3,
367+
self.rule_5,
368+
self.rule_4,
369+
self.rule_2,
370+
]
371+
372+
for priority, rule in enumerate(self.project.automation_rules.all()):
373+
assert rule == new_order[priority]
374+
assert rule.priority == priority
375+
376+
def test_delete_fist_rule(self):
377+
self.rule_0.delete()
378+
assert self.project.automation_rules.all().count() == 5
379+
380+
new_order = [
381+
self.rule_1,
382+
self.rule_2,
383+
self.rule_3,
384+
self.rule_4,
385+
self.rule_5,
386+
]
387+
388+
for priority, rule in enumerate(self.project.automation_rules.all()):
389+
assert rule == new_order[priority]
390+
assert rule.priority == priority
391+
392+
def test_delete_last_rule(self):
393+
self.rule_5.delete()
394+
assert self.project.automation_rules.all().count() == 5
395+
396+
new_order = [
397+
self.rule_0,
398+
self.rule_1,
399+
self.rule_2,
400+
self.rule_3,
401+
self.rule_4,
402+
]
403+
404+
for priority, rule in enumerate(self.project.automation_rules.all()):
405+
assert rule == new_order[priority]
406+
assert rule.priority == priority
407+
408+
def test_delete_some_rule(self):
409+
self.rule_2.delete()
410+
assert self.project.automation_rules.all().count() == 5
411+
412+
new_order = [
413+
self.rule_0,
414+
self.rule_1,
415+
self.rule_3,
416+
self.rule_4,
417+
self.rule_5,
418+
]
419+
420+
for priority, rule in enumerate(self.project.automation_rules.all()):
421+
assert rule == new_order[priority]
422+
assert rule.priority == priority
423+
424+
def test_delete_some_rules(self):
425+
self.rule_2.delete()
426+
self.rule_0.refresh_from_db()
427+
self.rule_0.delete()
428+
self.rule_5.refresh_from_db()
429+
self.rule_5.delete()
430+
431+
assert self.project.automation_rules.all().count() == 3
432+
433+
new_order = [
434+
self.rule_1,
435+
self.rule_3,
436+
self.rule_4,
437+
]
438+
439+
for priority, rule in enumerate(self.project.automation_rules.all()):
440+
assert rule == new_order[priority]
441+
assert rule.priority == priority

0 commit comments

Comments
 (0)