https://github.com/hapytex/django-fdict
Dictionary mappings in the database.
https://github.com/hapytex/django-fdict
Last synced: 7 days ago
JSON representation
Dictionary mappings in the database.
- Host: GitHub
- URL: https://github.com/hapytex/django-fdict
- Owner: hapytex
- License: other
- Created: 2026-04-04T06:35:32.000Z (2 months ago)
- Default Branch: master
- Last Pushed: 2026-04-04T15:05:43.000Z (2 months ago)
- Last Synced: 2026-04-04T16:51:18.917Z (2 months ago)
- Language: Python
- Size: 32.2 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# `django-fdict`
> *Dictionary mappings in the database.*
# Introduction
In databases, one typically uses `JOIN`s to perform mappings. Imagine for example we have a database of products, then we can add the vat regime, and make a JOIN:
```python
from django.db import models
class VATRate(models.Model):
name = models.CharField(max_length=32, unique=True)
pct = models.FloatField()
class Product(models.Model):
name = models.CharField(max_length=255)
vat_rate = models.ForeignKey(VATRate, on_delete=models.PROTECT)
```
and then we can annotate by using a JOIN, like:
```python
Product.objects.annotate(vat_pct=F('vate_rate__pct'))
```
or we can load the entire `VATRate` object along each product with `.select_related(..)`:
```python
Product.objects.select_related('vate_rate')
```
and then fetch it through `.vat_rate`.
---
Occasionally, we might want to perform mappings with a Python dictionary. This might be the case when we have no control over the database tables, and the vat rate is missing.
If for example the `Product` contains a `CharField` with the vat rate:
```python
class Product(models.Model):
name = models.CharField(max_length=255)
country = models.CharField(max_length=2)
country_of_origin = models.CharField(max_length=2)
vat_rate = models.CharField(max_length=32)
```
We might want to perform a dictionary mapping with:
```python
# will *not* work
vat_rates = {'R03': 0.21, 'R02': 0.12, 'R01': 0.06}
Product.annotate(vat_pct=vat_rates[F('vat_rate')])
```
This is the problem `django-fdict` wants to solve. We first wrap the entries in an `FDict`, which is a subclass of `dict`:
```python
# *hopefully* works
from django_fdict import FDict
vat_rates = FDict({'R03': 0.21, 'R02': 0.12, 'R01': 0.06})
Product.annotate(vat_pct=vat_rates[F('vat_rate')])
```
this will then set an attribute `vat_pct` to the
Usually mapping just to get an attribute is *not* a good idea. It is mainly done for further filtering *in* the queryset, like:
```python
# *hopefully* works
from django_fdict import FDict
vat_rates = FDict({'R03': 0.21, 'R02': 0.12, 'R01': 0.06})
Product.annotate(vat_pct=vat_rates[F('vat_rate')]).filter(vat_pct__range=(0.10, 0.15))
```
# Multiple fields
One can also apply the trick with two or more fields. Then `FDict` is given a dictionary where the keys are tuples, for example:
```python
# *hopefully* works
from django_fdict import FDict
vat_rates = FDict({('R03', 'BE'): 0.21, ('R02', 'BE'): 0.12, ('R01', 'BE'): 0.06, ('R03', 'FR'): 0.2, ('R02', 'FR'): 0.1})
Product.annotate(vat_pct=vat_rates[F('vat_rate'), F('country')]).filter(vat_pct__range=(0.10, 0.15))
```
which normally fetches the VAT rates based on the vat rate label, and the country code.
One can also pass non-`F` values in the lookups, then it will first filter down the dictionary:
```python
# *hopefully* works
from django_fdict import FDict
vat_rates = FDict({('R03', 'BE'): 0.21, ('R02', 'BE'): 0.12, ('R01', 'BE'): 0.06, ('R03', 'FR'): 0.2, ('R02', 'FR'): 0.1})
Product.annotate(vat_pct=vat_rates[F('vat_rate'), 'BE']).filter(vat_pct__range=(0.10, 0.15))
```
now we thus always use `BE` (Belgium) as country in the dictionary lookups.
# Query-like keys/values
Strictly speaking the keys, and the values in the dictionary can also be database expressions, for example with the given model:
```python
# *hopefully* works
from django_fdict import FDict
origin = F('country_of_origin')
vat_rates = FDict({('R03', origin): 0.21, ('R02', origin): 0.12, ('R01', origin): 0.06})
Product.annotate(vat_pct=vat_rates[F('vat_rate'), F('country')])
```
it will produce `NULL` if the `country_of_origin` is not the same as the `country` field. But this makes mapping more complicated and eventually the order in which the entries are mapped might determine the outcome.
# Technical details
Databases allow to work with `CASE … WHEN … THEN … ELSE … END`. The `FDict` converts the dictionary into a (long) chain of `WHEN` expressions.
So for the first example we use:
```sql
SELECT *
CASE
WHEN vat_label = 'R03' THEN 0.21
WHEN vat_label = 'R02' THEN 0.12
WHEN vat_label = 'R01' THEN 0.06
ELSE NULL
END as vat_pct
FROM product
```
this can also work for more complicated conditions, like:
```sql
SELECT *
CASE
WHEN vat_label = 'R03' AND country='BE' THEN 0.21
WHEN vat_label = 'R02' AND country='BE' THEN 0.12
WHEN vat_label = 'R01' AND country='BE' THEN 0.06
WHEN vat_label = 'R03' AND country='FR' THEN 0.20
WHEN vat_label = 'R02' AND country='FR' THEN 0.10
ELSE NULL
END as vat_pct
FROM product
```