When exposing data from a Django model to an API or serializer, the model’s field names don’t always match what you want to return. The usual approach: fetch instances, then rename in Python. There’s a better option: let the database do the work using F() inside values().

The problem: model field names dictate output

class Task(models.Model):
    name = models.CharField(...)
    created_at = models.DateTimeField(...)

If you want to return task_name instead of name, the typical approach is to fetch the data and rename in Python, either with a dict comprehension or inside the serializer. Either way, the transformation happens after the fact, in memory.

F() in values(): aliasing at the SQL level

Django lets you pass F() expressions as keyword arguments to values(). The alias becomes the field name in the result.

from django.db.models import F

Task.objects.values(
    identifier=F('id'),
    task_name=F('name'),
    created_at=F('created_at'),
)

The generated SQL:

SELECT id AS identifier, name AS task_name, created_at AS created_at
FROM tasks_task

The database renames the columns, and Django returns dictionaries with the right names directly. No Python transformation.

Concrete use cases

Adapting to a naming convention

A model with legacy or internal field names can be exposed with more explicit names without touching the model itself:

from django.db.models import F

TaskRelation.objects.values(
    relation_id=F('id'),
    parent_task=F('parent_task_id'),
    order=F('position'),
)

Building an API response

When constructing a JSON payload without DRF, or feeding a flat serializer:

def get_tasks_payload(user_id: int) -> list[dict]:
    return list(
        Task.objects.filter(owner_id=user_id).values(
            id=F('id'),
            label=F('name'),
            createdAt=F('created_at'),
        )
    )

The payload comes out with the exact keys the frontend expects. No post-processing.

Translating field names

In a multilingual context, or when exposing data to partners with an imposed nomenclature:

Product.objects.values(
    reference=F('sku'),
    label=F('name'),
    unit_price=F('price'),
)

What this changes in practice

Renaming in Python means looping over every dict to build a new one. On a QuerySet of 10,000 rows, that’s 10,000 extra allocations. With a SQL alias, the database returns the correctly named columns directly. Django reads them once, with no intermediate layer.

That’s not a negligible micro-optimization on exports, reports, or endpoints returning large volumes of data.

One limit to know

values() returns dictionaries, not model instances. You lose access to model methods and properties. This approach suits data-oriented reads (APIs, exports, aggregations), not business logic that needs model behaviour.


Want to go further on QuerySet optimisations? The in_bulk() article shows how to eliminate N+1 with O(1) access by identifier.