Home > Net >  Order Django Queryset by attribute that could be char or int?
Order Django Queryset by attribute that could be char or int?

Time:07-28

I have the following model that I need to order by the grade attribute, but that attribute can be either a character or integer (K, KA, KF, NA, 1, 2, 3, etc.)... what is the best way to accomplish that? We are using MS SQL as a backend (I cannot use __regex because the DB version does not support it).

I annotated a first_letter_group attribute onto the queryset because any grade with multiple letters should be treated as it's first letter (KA = K, KN = K, etc.)

I'm trying to order these results by alpha first, then ascending numerically (e.g. 'P, K, 1, 2, 3, 4...')

# This is an unmanaged model
class Enrollment(models.Model):
    id = models.IntegerField(db_column="ID", primary_key=True)
    location_id = models.IntegerField(db_column="loc_id")
    grade = models.CharField(db_column="grade", max_length=10)
    end_year = models.IntegerField(db_column="end_year")
    run_date = models.DateField(db_column="run_date")
    student_count = models.IntegerField(db_column="students")

I've tried annotating additional fields where I cast the results to CharField or IntegerField but they (expectedly) error out when they hit something that does not convert.

    all_enrollments = (
        Enrollment.objects.filter(
            location_id__in=location_ids,
            end_year=today.year,
        )
        .order_by("-run_date")
        .annotate(first_letter_group=Substr("grade", 1, 1))
    )

    ordered_enrollment_list = (
        all_enrollments.annotate(
            number_field=Cast(
                "first_letter_group", output_field=IntegerField()
            ),
            str_field=Cast(
                "first_letter_group", output_field=IntegerField()
            ),
        )
        .order_by("-str_field", "number_field")
    )

I can't figure out how to do this without the __regex functionality which our DB does not support.

CodePudding user response:

It's not pretty, but as far as i have understood your question it does the job

numbers = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']

ordered_enrollment_list = (
        all_enrollments.annotate(
            number_field=Case(
                  When(first_letter_group__in=numbers, then=Cast(F('first_letter_group'), IntegerField())),
                  default=Value(0),
                  output_field=IntegerField()
               ),
            str_field=Case(
                  When(first_letter_group__in=numbers, then=Value(False)),
                  default=Cast(F('first_letter_group'), CharField()),
                  output_field=CharField()
               ),
        )
        .order_by('-str_field', 'number_field')
    )
  • Related