vabada
vabada

Reputation: 1814

Django - Reorder (update) objects with unique_together = (ForeignKey, IntegerField)

I have a model that represents a Session (say a one hour class for kids) that has a number of associated Tasks that must follow an order. Easier to understand:

class Task(models.Model):
    (...)
    order = models.PositiveIntegerField('offset from the beginning of a Session')
    session = models.ForeignKey(Session, related_name='tasks')

    unique_together = ('session', 'order')
    ordering = ('order', )

Say I want to update a single session's order (imagine a common drag and drop reordering). Thus, by updating one task's order I would need to reorder the rest of the tasks so that the uniqueness does not brake.

Graphical example (let's say I want task with pk=5014 to be in the second position (order=1))

Initial state:                    Final state:

| pk   | order | name |           | pk   | order | name |
|------|-------|------|           |------|-------|------|
| 5011 | 0     | A    |           | 5011 | 0     | A    |
| 5012 | 1     | B    |   ---->   | 5014 | 1     | D    |
| 5013 | 2     | C    |           | 5012 | 2     | B    |
| 5014 | 3     | D    |           | 5013 | 3     | C    |

My question is how to update it with Django ORM (if possible) or what would be an elegant way to do it, since I can only think of saving the desired task with a new order (say order=50), then rearrange one by one the rest of tasks and finally reassigning the desired order to the desired task.

Is there something like a bulk_update (I didn't find anything similar after a long research) that allows me to modify the order fields for all tasks and then saving all at once? Or do I have to take care of this unique_together myself?

Upvotes: 3

Views: 952

Answers (2)

seeg
seeg

Reputation: 1638

It's usually best to reuse existing solutions:

https://github.com/bfirsh/django-ordered-model

though it still boils down to 2 saves in sequence:

https://github.com/bfirsh/django-ordered-model/blob/master/ordered_model/models.py#L122-L123

See also:

https://www.djangopackages.com/grids/g/model-ordering/

Best bet would be to write a custom SQL query, something along the lines of

https://dba.stackexchange.com/questions/131118/how-can-i-swap-two-values-from-particular-column-in-a-table-in-postgres

It's also good idea to have DB constraints as the unique_together in your example.

Upvotes: 2

vabada
vabada

Reputation: 1814

I'll post the code I developed as a solution for my problem; although I am not quite happy (I think there must be somehow a better solution), but just in case it helps someone.

I defined this method for the model Task:

def update_order(self, new_order):
    """
    Update the order of this task and reorder the rest of tasks
    of the session accordingly
    """
    current_order = self.order
    tasks = Task.objects.filter(session=self.session)
    current_highest_order = tasks.last().order
    if new_order > current_highest_order or new_order < 0:
        raise ValidationError(_('New order out of range'))
    operation = -1 if new_order < current_order else +1
    # set temporary order higher than any other to allow reordering the rest
    self.order = current_highest_order + 1
    self.save()
    # reassign the rest of tasks' order accordingly
    tasks = Task.objects.filter(session=self.session)
    for i in range(current_order, new_order, operation):
        task = tasks[i + operation] if operation == -1 else tasks[i]
        task.order -= operation
        task.save()
    # Restore a proper order value (the desired new_order) to this task
    self.order = new_order
    self.save()

Any comments will be really appreciated.

Upvotes: 0

Related Questions