Giter Club home page Giter Club logo

Comments (8)

shuckc avatar shuckc commented on June 27, 2024 4

Hopefully resolved by #248

from django-ordered-model.

timhaley94 avatar timhaley94 commented on June 27, 2024 2

I also had problems with bottom() not working when using with_respect_to.

For my use case, the model is ordered with respect to active. The problem came when I updated active and then tried to call bottom(). The problem is, the bottom() method looks for the object with the highest order with the same value of active, but it includes the instance it was called on.

My solution was to override the bottom method and exclude the instance that it was called on. I took it straight from the source code of this package but added the exclude clause.

Future people, feel free to use it, if it helps.

def bottom(self):
    last = (
        self.get_ordering_queryset()
            .exclude(id=self.id)
            .aggregate(Max('order'))
            .get('order__max')
    )

    self.to(last + 1)

Every time this model saves. I check to see if active got updated, and, if it did, I call the bottom() method. This keeps the ordering from breaking when active changes.

def save(self, *args, **kwargs):
    old_active_value = MyModel.objects.get(id=self.id).active

    super().save(*args, **kwargs)

    if self.active is not old_active_value:
        self.bottom()

from django-ordered-model.

timhaley94 avatar timhaley94 commented on June 27, 2024 2

@pablolmedorado @shuckc

Ah, you are right. It would awesome if there was a reshuffle() method that checked for holes and closed them. However, in the meantime, here's my new iteration:

def bottom(self):
    # Looks for the max value for 'order' in a given subset, or 0 if the subset is empty
    last = (
        self.get_ordering_queryset()
            .exclude(id=self.id)
            .aggregate(Max('order'))
            .get('order__max')
    ) or 0

    self.to(last + 1)

def save(self, *args, **kwargs):
    # If this instance is being created for the first time, old instance won't exist
    try:
        old_instance = MyModel.objects.get(id=self.id)
    except MyModel.DoesNotExist:
        old_instance = None

    super().save(*args, **kwargs)

    if old_instance is not None and self.active is not old_instance.active:
        # If active changed, the instance is switching to a different subset,
        # when need to send that instance to the bottom of that subset.
        self.bottom()

        try:
            # If there was an object after the instance in the OLD subset, move it to the
            # instance's old spot.
            MyModel.objects
                .get(active=old_instance.active, order=old_instance.order + 1)
                .to(old_instance.order)
        except MyModel.DoesNotExist:
            pass

from django-ordered-model.

sean-reed avatar sean-reed commented on June 27, 2024 1

@Hafnernuss There is a bug in your adaption, it doesn't save when updating an existing instance when the order_with_respect_to field is unchanged.

A corrected version that is also adapted to work with both single and multiple order_with_respect_to fields:

  def save(self, *args, **kwargs):
        if getattr(self, 'id') is not None:
            # This is an existing object, so we need to check if any order_with_respect_to field has changed.
            if isinstance(self.order_with_respect_to, str): # Single order_with_respect_to field.
                order_with_respect_to_fields = [self.order_with_respect_to]
            else:   # Multiple order_with_respect_to fields.
                order_with_respect_to_fields = self.order_with_respect_to
            for field in order_with_respect_to_fields:
                current_respect_field = getattr(self, field)
                old_instance = self.__class__.objects.get(id=getattr(self, 'id'))
                old_respect_field = getattr(old_instance, field)
                if old_respect_field != current_respect_field:
                    old_instance.bottom()
                    setattr(self, self.order_field_name, None)
                    break

        super().save(*args, **kwargs)

from django-ordered-model.

shuckc avatar shuckc commented on June 27, 2024

@timhaley94 This looks like most of the solution, it seems that it could leave a "hole" in the original order_with_respect sequence though. I like the technique to load the old value of the in the save() method and check for changes. It can fairly easily be made model-agnostic by using getattr()

from django-ordered-model.

shuckc avatar shuckc commented on June 27, 2024

It would be really great to get this into a PR and add some tests for this change.

from django-ordered-model.

duwangthefirst avatar duwangthefirst commented on June 27, 2024

I may have SOLVED this problem !!!!!

Problem

I want to implememt a category tree,

the original structure is like:

  • c1 order:0
  • c2 order:1
  • c3 order:2
    • c3-1 order:0
    • c3-2 order:1
    • c3-3 order:2

when I switch c3-2's parent from c3 to None(parent_category==None for root category),

this is what I want:

  • c1 order:0
  • c2 order:1
  • c3 order:2
    • c3-1 order:0
    • c3-3 order:1
  • c3-2 order:3

this is the actual result:

  • c1 order:0
  • c2 order:1
  • c3-2 order:1
  • c3 order:2
    • c3-1 order:0
    • c3-3 order:2

Solution

extend OrderedModelBase and override save() method:

from django.db import models
from ordered_model.models import OrderedModelBase


class BaseOrderedModel(OrderedModelBase):
    index = models.PositiveIntegerField(db_index=True, editable=False, verbose_name='index')
    order_field_name = "index"

    class Meta:
        abstract = True
        ordering = ("index", )

    def save(self, *args, **kwargs):
        current_respect_field = getattr(self, self.order_with_respect_to)
        if getattr(self, 'id') is not None:
            # when update existing instance
            old_instance = self.__class__.objects.get(id=getattr(self, 'id'))
            old_respect_field = getattr(old_instance, self.order_with_respect_to)
            if old_respect_field != current_respect_field:
                # if order_with_respect_to field has changed
                # 1. set instance to bottom of old subset before save
                old_instance.bottom()

                # 2. set self.[order_field_name] to None and save instance
                setattr(self, self.order_field_name, None)
                super().save(*args, **kwargs)
            else:
                # if order_with_respect_to field hasn't changed
                super().save(*args, **kwargs)
        else:
            # when create new instance
            super().save(*args, **kwargs)

Example

Let's define a Category model which has a ForeignKey field parent_category pointing to itself, and also with order_with_respect_to = 'parent_category'

class Category(BaseOrderedModel):
    id = models.BigAutoField(primary_key=True)
    name = models.CharField(max_length=50, verbose_name="名称")

    parent_category = models.ForeignKey('self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='parent category')

    order_with_respect_to = 'parent_category'

    class Meta(BaseOrderedModel.Meta):
        ordering = ('parent_category__id', 'index', )

    def __str__(self):
        return self.name

and according to my testing, everything works fine.

from django-ordered-model.

Hafnernuss avatar Hafnernuss commented on June 27, 2024

@duwangthefirst good idea, albeit only working if you have a single order_with_respect_to field. I can confirm that this seems to work and is a probably an easy workaround until pull requests such as #272 are merged.

I made an easy adaption to support multiple fields:

    def save(self, *args, **kwargs):
        if getattr(self, 'id') is not None:
            for field in self.order_with_respect_to:
                current_respect_field = getattr(self, field)
                # when update existing instance
                old_instance = self.__class__.objects.get(id=getattr(self, 'id'))
                old_respect_field = getattr(old_instance, field)
                if old_respect_field != current_respect_field:
                    old_instance.bottom()

                    setattr(self, self.order_field_name, None)
                    super().save(*args, **kwargs)
                    break
        else:
            # when create new instance
            super().save(*args, **kwargs)

from django-ordered-model.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.