Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unique model field in Django and case sensitivity (postgres)

Consider the following situation: -

Suppose my app allows users to create the states / provinces in their country. Just for clarity, we are considering only ASCII characters here.

In the US, a user could create the state called "Texas". If this app is being used internally, let's say the user doesn't care if it is spelled "texas" or "Texas" or "teXas"

But importantly, the system should prevent creation of "texas" if "Texas" is already in the database.

If the model is like the following:

class State(models.Model):     name = models.CharField(max_length=50, unique=True) 

The uniqueness would be case-sensitive in postgres; that is, postgres would allow the user to create both "texas" and "Texas" as they are considered unique.

What can be done in this situation to prevent such behavior. How does one go about providing case-insenstitive uniqueness with Django and Postgres

Right now I'm doing the following to prevent creation of case- insensitive duplicates.

class CreateStateForm(forms.ModelForm):     def clean_name(self):         name = self.cleaned_data['name']         try:             State.objects.get(name__iexact=name)         except ObjectDoesNotExist:             return name         raise forms.ValidationError('State already exists.')      class Meta:         model = State 

There are a number of cases where I will have to do this check and I'm not keen on having to write similar iexact checks everywhere.

Just wondering if there is a built-in or better way? Perhaps db_type would help? Maybe some other solution exists?

like image 348
chefsmart Avatar asked Dec 07 '09 04:12

chefsmart


People also ask

How do I make Postgres case sensitive?

PostgreSQL names are case sensitive. By default, AWS Schema Conversion Tool (AWS SCT) uses object name in lowercase for PostgreSQL. In most cases, you'll want to use AWS Database Migration Service transformations to change schema, table, and column names to lower case.

How do you make sure a field is unique in Django?

However, certain fields may also need to be unique, because certain fields may need to be unique without any repetition. So, to make a field unique in Django is very basic. You just set the unique attribute to true.

Are PostgreSQL queries case sensitive?

PostgreSQL is a case-sensitive database by default, but provides various possibilities for performing case-insensitive operations and working with collations.

What is unique together in Django models?

This is a list of lists that must be unique when considered together. It's used in the Django admin and is enforced at the database level (i.e., the appropriate UNIQUE statements are included in the CREATE TABLE statement). A ManyToManyField cannot be included in unique_together.


2 Answers

You could define a custom model field derived from models.CharField. This field could check for duplicate values, ignoring the case.

Custom fields documentation is here http://docs.djangoproject.com/en/dev/howto/custom-model-fields/

Look at http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py for an example of how to create a custom field by subclassing an existing field.

You could use the citext module of PostgreSQL https://www.postgresql.org/docs/current/static/citext.html

If you use this module, the the custom field could define "db_type" as CITEXT for PostgreSQL databases.

This would lead to case insensitive comparison for unique values in the custom field.

like image 74
Mayuresh Avatar answered Sep 29 '22 02:09

Mayuresh


Alternatively you can change the default Query Set Manager to do case insensitive look-ups on the field. In trying to solve a similar problem I came across:

http://djangosnippets.org/snippets/305/

Code pasted here for convenience:

from django.db.models import Manager from django.db.models.query import QuerySet  class CaseInsensitiveQuerySet(QuerySet):     def _filter_or_exclude(self, mapper, *args, **kwargs):         # 'name' is a field in your Model whose lookups you want case-insensitive by default         if 'name' in kwargs:             kwargs['name__iexact'] = kwargs['name']             del kwargs['name']         return super(CaseInsensitiveQuerySet, self)._filter_or_exclude(mapper, *args, **kwargs)  # custom manager that overrides the initial query set class TagManager(Manager):     def get_query_set(self):         return CaseInsensitiveQuerySet(self.model)  # and the model itself class Tag(models.Model):     name = models.CharField(maxlength=50, unique=True, db_index=True)      objects = TagManager()      def __str__(self):         return self.name 
like image 26
Foo Avatar answered Sep 29 '22 03:09

Foo