-
-
Save frankwiles/74233a1261d41a21b8987bdd64773f82 to your computer and use it in GitHub Desktop.
from django.db import models, transaction | |
from django.db.models import F, Max | |
class StepManager(models.Manager): | |
""" | |
Manager to encapsulate bits of business logic | |
""" | |
def move(self, obj, new_order): | |
""" Move an object to a new order position """ | |
qs = self.get_queryset() | |
with transaction.atomic(): | |
if obj.order > int(new_order): | |
qs.filter( | |
task=obj.task, order__lt=obj.order, order__gte=new_order | |
).exclude(pk=obj.pk).update(order=F("order") + 1) | |
else: | |
qs.filter( | |
task=obj.task, order__lte=new_order, order__gt=obj.order | |
).exclude(pk=obj.pk).update(order=F("order") - 1) | |
obj.order = new_order | |
obj.save() | |
def create(self, **kwargs): | |
instance = self.model(**kwargs) | |
with transaction.atomic(): | |
# Get our current max order number | |
results = self.filter(task=instance.task).aggregate(Max("order")) | |
current_order = results["order__max"] | |
if current_order is None: | |
current_order = 0 | |
value = current_order + 1 | |
instance.order = value | |
instance.save() | |
return instance | |
class Task(models.Model): | |
name = models.CharField(max_length=100) | |
class Step(models.Model): | |
task = models.ForeignKey(Task, related_name="steps", on_delete=models.CASCADE) | |
name = models.CharField(max_length=100) | |
order = models.IntegerField(default=1) | |
objects = StepManager() |
from tasks.models import * | |
t = Task.objects.create(name="Test1") | |
s1 = Step.objects.create(task=t, name="Testing1") | |
s2 = Step.objects.create(task=t, name="Testing2") | |
s3 = Step.objects.create(task=t, name="Testing3") | |
s4 = Step.objects.create(task=t, name="Testing4") | |
# Show the existing numbers/structure | |
In [9]: for s in Step.objects.all(): | |
...: print(f"pk={s.pk} order={s.order} name={s.name}") | |
...: | |
pk=1 order=1 name=Testing1 | |
pk=2 order=2 name=Testing2 | |
pk=3 order=3 name=Testing3 | |
pk=4 order=4 name=Testing4 | |
# Move the 4th item to the 2nd position | |
In [10]: Step.objects.move(s4, 2) | |
In [11]: for s in Step.objects.all(): | |
...: print(f"pk={s.pk} order={s.order} name={s.name}") | |
...: | |
pk=1 order=1 name=Testing1 | |
pk=2 order=3 name=Testing2 | |
pk=3 order=4 name=Testing3 | |
pk=4 order=2 name=Testing4 | |
In [12]: |
What database are you using?
For example, here is my test:
Print step list
print(f1)
print(s_1_1)
print(s_1_2)
print(s_1_3)
Change step 1 order to 3
Step.objects.move(s_1_1, 3)
print(f1)
print(s_1_1)
print(s_1_2)
print(s_1_3)
Change step 1 order back to 1
Step.objects.move(s_1_1, 1)
print(f1)
print(s_1_1)
print(s_1_2)
print(s_1_3)
Change step 3 order to 1
Step.objects.move(s_1_3, 1)
When I would run it, I would get,
Flow
3: s_1_1
2: s_1_2
3: s_1_3
Flow
1: s_1_1
2: s_1_2
3: s_1_3
Flow
1: s_1_1
2: s_1_2
1: s_1_3
(Don't worry about the flow, the steps are just a subset of that, like task)
Here is my test's set up work
load = StepType(title="Load", url_validation="Optional", has_verification="Optional", has_fixture="Optional")
load.save()
Create Project
p = Project(title="Project")
p.save()
Create Flow
f1 = Flow(project=p, title="Flow1", order=1, passed="False")
f1.save()
Create Flow #2
f2 = Flow(project=p, title="Flow2", order="2", passed="False")
Create Flow #3
f3 = Flow(project=p, title="Flow3", order="3", passed="False")
Create Steps, s_(Flow#)_(Step#)
s_1_1 = Step(flow=f1, type=load, item="URL")
s_1_1.save()
s_1_2 = Step.objects.create(flow=f1, type=load, item="Login")
s_1_2.save()
s_1_3 = Step.objects.create(flow=f1, type=load, item="Logout")
s_1_3.save()
Actually re-reading the code I think you're trying to accomplish something slightly differently than what I intended in the blog post.
Because qs[new_order-1]
doesn't make a lot of sense in terms of "moving" items. I get what you're doing with the long floating point math, but just don't see why it's necessary.
I agree, I feel your way is more concise and allows order to be a part of the database, I just can't seem to get it to work. I just kind of threw it together to make my tests work.
Oh I think I see your issue. These model objects, the ones NOT moving are not going to get their values updated in an ipython shell. You have to requery them to see the updated order values. If you use my code and re-query the Steps each time you'll see the movement.
Thanks, I figured it out. Your code worked great, sorry for doubting you :). Thanks for taking time to help me out! I really appreciate it.
It would not update the other steps when I would change one of them