Skip to content

Instantly share code, notes, and snippets.

@frankwiles
Created July 12, 2019 21:10
Show Gist options
  • Save frankwiles/74233a1261d41a21b8987bdd64773f82 to your computer and use it in GitHub Desktop.
Save frankwiles/74233a1261d41a21b8987bdd64773f82 to your computer and use it in GitHub Desktop.
Keeping Django Models Ordered Example
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]:
@Jwrecker
Copy link

It would not update the other steps when I would change one of them

@frankwiles
Copy link
Author

What database are you using?

@Jwrecker
Copy link

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)

@Jwrecker
Copy link

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()

@frankwiles
Copy link
Author

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.

@Jwrecker
Copy link

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.

@frankwiles
Copy link
Author

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.

@Jwrecker
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment