Eray Erdin
Eray Erdin

Reputation: 3149

Django Sort ManyToMany Field in A Nonsignificant Order

I have two models as below:

class Stop(models.Model):
    """
    Showing bus stops in İzmir.
    """

    code = models.PositiveIntegerField(
        unique=True,
        primary_key=True,
        verbose_name="Code"
    )

    label = models.CharField(
        null=False,
        blank=False,
        max_length=64,
        verbose_name="Label"
    )

    coor = ArrayField(
        models.FloatField(),
        size=2,
        verbose_name="Coordination"
    )

    class Meta:
        verbose_name = "Stop"
        verbose_name_plural = "Stops"
        ordering = ["label"]

    def __str__(self):
        return self.label

class Route(models.Model):
    """
    Bus routes of İzmir.
    """

    code = models.PositiveSmallIntegerField(
        unique=True,
        primary_key=True,
        verbose_name="Code"
    )

    stops = models.ManyToManyField(
        Stop,
        null=True,
        blank=True,
        related_name="routes",
        verbose_name="Stops"
    )

    terminals = ArrayField(
        models.CharField(
            null=False,
            blank=False,
            max_length=32,
        ),
        size=2,
        default=[],
        verbose_name="Terminals"
    )

    departure_times = ArrayField(
        ArrayField(
            models.TimeField(
                null=False,
                blank=False
            ),
            null=True,
            default=[]
        ),
        default=[],
        size=6,
        verbose_name="Departure Times"
    )

    class Meta:
        verbose_name = "Route"
        verbose_name_plural = "Routes"
        ordering = ["code"]

    def __str__(self):
        return "{}: {} - {}".format(str(self.code), self.terminals[0], self.terminals[1])

As you can see, Route has a ManyToManyFields which takes Stop instances.

I put the instances with a script which it scraps a couple of web page, it seems I will use crontab to keep them updated. In the data I am scraping, Stop objects are ordered. The thing is, there are no significant filter to sort e.g. a Stop instance comes after another.

Django (or Django Rest Framework) returns Stop instances of Route instance in alphabetic order, e.g.

{
    "code": 285,
    "terminals": [
        "EVKA 1",
        "KONAK"
    ],
    "stops": [
        40586,
        40633,
        12066,
        40645,
        40627,
        40647,
        40588,
        40592,
        40623,
        40016,
        40506,
        40508,
        40528,
        40462,
        40631,
        40014,
        40619,
        40530,
        12060,
        40661,
        40504,
        40488,
        40653,
        40590,
        40512,
        40464,
        10240,
        10036,
        12068,
        40514,
        40510,
        40658,
        40002,
        40649,
        12070,
        40004,
        40010,
        40656,
        12064,
        40614,
        40012
    ],
    ...
}

In which stops[0] returns a Stop instance beginning with A and sorts like that.

So, is there a way to order like a list in Python? Like, there is no significant point, you just append to the end and return so.


Environment

Upvotes: 0

Views: 623

Answers (1)

Todor
Todor

Reputation: 16030

The position of a stop is relative to a Route, e.g. one stop can be first for route 1, 2nd for route 2 and etc. So this is a perfect example that you need more metadata about the Route-Stop relation. Djagno solves this by letting you provide a Intermediate Table with two ForeignKey and the metadata you need for the relation.

class Stop(models.Model):
    #...

class Route(models.Model):

    #...

    stops = models.ManyToManyField(Stop, through='RouteStop', blank=True, related_name="routes", verbose_name="Stops")


class RouteStop(models.Model):
    stop = models.ForeignKey(Stop)
    route = models.ForeignKey(Route)
    position = models.PositiveSmallIntegerField()

    class Meta:
        unique_together = (("stop", "route"),)

Now when you get Routes you can order route.stops by RouteStop.position, something like:

Route.objects.all().prefetch_related(
    Prefetch('stops', queryset=Stop.objects.all().order_by('routestop__position'))
)

Upvotes: 1

Related Questions