Skip to main content
Build Status Coverage Status

A ReverseUnique model field implementation for Django

The ReverseUnique field can be used to access single model instances from the reverse side of ForeignKey. Essentially, ReverseUnique can be used to generate OneToOneField like behaviour in the reverse direction of a normal ForeignKey. The idea is to add an unique filter condition when traversing the foreign key to reverse direction.

To be able to use reverse unique, you will need a unique constraint for the reverse side or otherwise know that only one instance on the reverse side can match.

Example

It is always nice to see actual use cases. We will model employees with time dependent salaries in this example. This use case could be modelled as:

class Employee(models.Model):
    name = models.TextField()

class EmployeeSalary(models.Model):
    employee = models.ForeignKey(Employee, related_name='employee_salaries')
    salary = models.IntegerField()
    valid_from = models.DateField()
    valid_until = models.DateField(null=True)

It is possible to save data like “Anssi has salary of 10€ from 2000-1-1 to 2009-12-31, and salary of 11€ from 2010-1-1 to infinity (modelled as None in the models).

Unfortunately when using these models it isn’t trivial to just fetch the employee and his salary to display to the user. It would be possible to do so by looping through all salaries of an employee and checking which of the EmployeeSalaries is currently in effect. However, this approach has a couple of drawbacks:

  • It doesn’t perform well in list views

  • Most of all It is impossible to execute queries that refer the employee’s current salary. For example, getting the top 10 best paid employees or the average salary of employees is impossible to achieve in single query.

Django-reverse-unique to the rescue! Lets change the Employee model to:

from datetime import datetime
class Employee(models.Model):
    name = models.TextField()
    current_salary = models.ReverseUnique(
        "EmployeeSalary",
        filter=Q(valid_from__gte=datetime.now) &
               (Q(valid_until__isnull=True) | Q(valid_until__lte=datetime.now))
    )

Now we can simply issue a query like:

Employee.objects.order_by('current_salary__salary')[0:10]

or:

Employee.objects.aggregate(avg_salary=Avg('current_salary__salary'))

What did happen there? We added a ReverseUnique field. This field is the reverse of the EmployeeSalary.employee foreign key with an additional restriction that the relation must be valid at the moment the query is executed. The first “EmployeeSalary” argument refers to the EmployeeSalary model (we have to use string as the EmployeeSalary model is defined after the Employee model). The filter argument is a Q-object which can refer to the fields of the remote model.

Another common problem for Django applications is how to store model translations. The storage problem can be solved with django-reverse-unique. Here is a complete example for that use case:

from django.db import models
from reverse_unique import ReverseUnique
from django.utils.translation import get_language, activate

class Article(models.Model):
    active_translation = ReverseUnique("ArticleTranslation",
                                       filters=Q(lang=get_language))

class ArticleTranslation(models.Model):
    article = models.ForeignKey(Article)
    lang = models.CharField(max_length=2)
    title = models.CharField(max_length=100)
    body = models.TextField()

    class Meta:
        unique_together = ('article', 'lang')

activate("fi")
objs = Article.objects.filter(
    active_translation__title__icontains="foo"
).select_related('active_translation')
# Generated query is
#    select article.*, article_translation.*
#      from article
#      join article_translation on article_translation.article_id = article.id
#                               and article_translation.lang = 'fi'
# If you activate "en" instead, the lang is changed.
# Now you can access objs[0].active_translation without generating more
# queries.

Similarly one could fetch current active reservation for a hotel room etc.

Installation

The requirement for ReverseUnique is Django 1.6+. You will need to place the reverse_unique directory in Python path, then just use it like done in above example. The tests (reverse_unique/tests.py) contain a couple more examples. Easiest way to install is:

pip install -e git://github.com/akaariai/django-reverse-unique.git#egg=reverse_unique

Testing

You’ll need to have a supported version of Django installed. Go to testproject directory and run:

python manage.py test reverse_unique

Metadata

Release files for django-reverse-unique-charettes 1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-reverse-unique-charettes 1.1
File Size Uploaded
django-reverse-unique-charettes-1.1.tar.gz 9.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-reverse-unique-charettes 1.1
File Interpreter ABI Platform
django_reverse_unique_charettes-1.1-py2.py3-none-any.whl Python 2, Python 3 none any Details

Total release size: 20.7 kB

Release files / django-reverse-unique-charettes-1.1.tar.gz

Download URL django-reverse-unique-charettes-1.1.tar.gz
Size 9.4 kB
Tags Source
SHA-256 checksum
How to use checksums
f19097cd5bc42642f962ebc929cfd27a1bbf63ff5c2c577ad15f9b182af57adb
BLAKE2b-256 checksum
How to use checksums
cd758a7cd4493dc3c876c461f8facb94a31d7fb355dc4879c7c0c3c3f3f9aee2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.8.10

Release files / django_reverse_unique_charettes-1.1-py2.py3-none-any.whl

Download URL django_reverse_unique_charettes-1.1-py2.py3-none-any.whl
Size 11.3 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
3409edce3b119b9bddcb69593d02b7022d5fde73f6b7f3a31530c4ca20bc6a71
BLAKE2b-256 checksum
How to use checksums
c0c108d2dc31d0f84709e8278715034253b0cddec1c95a943d396396a0a022db
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.8.10

Release history Release notifications | RSS feed

This release

1.1 This release

2 release files

1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page