When to use NullBooleanField in Django - django

I have a button that, when clicked, should save in the database that the user has drunk water. I just wanted to check whether NullBooleanField would be the correct way to define this.
A broader question that if answered would be useful to the community is a list of optimal circumstances under which to use NullBooleanField. But I'm not asking that here. Just in case you wanted a better challenge.
Thank you in advance.

The question you need to answer to find out whether you should use the BooleanField or the NullBooleanField is actually concerning the possible states of the value of the field you want to represent in your model:
2 possible states:
user has drunk water
user has not drunk water
→ use BooleanField
3 possible states:
user has drunk water
user has not drunk water
it is not known whether the user has or has not drunk water
→ use NullBooleanField.
UPDATE:
NullBooleanField is deprecated in version 3.1. Instead use BooleanField with null=True.

Django 2.1 introduced null=True for BooleanField. Using NullBooleanField is now discouraged.
So use, x = BooleanField(null=True) instead of x = NullBooleanField()
Here's a simple use case: If you only need to record the "Yes" or "No" status, use Boolean without null. But if you want to have 3 conditions say, "Yes", "No", and "Don't Know", use it with null=True.

I think you should use NullBooleanField only when you have three possible choices: Unknown, Yes (True) and No (False).
In your case you have only two possible values - Yes (user has drunk water) and No (user has NOT drunk water) so a BooleanField would be better.
One more reason to use a BooleanField in your case is because the default form widget for this field is a CheckboxInput (docs), while the default form widget for a NullBooleanField is a NullBooleanSelect (docs). And since you use a checkbox, a BooleanField would do the job better.

Take advantage of the NULL properties
I use it quite often when I need to enforce some specific constrains in my data, but allow some others. Multiple NULL values can coexist in a column defined UNIQUE. Let's take an address model implementation as an example:
The business rules are:
A user can have up to 1 billing address
A user can have multiple shipping addresses
One way to implement that is by making a single address table with a foreign key to the user and an extra flag that indicates if that address is a billing address or not:
class Address(models.Model):
... # <- address fields
user = models.ForeignKey(User, on_delete=models.CASCADE)
billing_address = models.NullBooleanField(default=None)
You can now simply enforce the business rules at a database level by making user and billing_address unique together.:
class Meta:
constraints = [
models.UniqueConstraint(
fields=['user', 'billing_address'],
name='ensure single billing address'
)
]
The trick to make this work is that the billing_address must be True when the address is a billing address but it should be None (instead of False) when the address is a shipping address.
You can further enforce the validation by adding another constraint to make sure that no False values are added. But this is usually not necessary and could be done at the application level:
class Meta:
constraints = [
models.UniqueConstraint(
fields=['user', 'billing_address'],
name='ensure single billing address'
),
models.CheckConstraint(
check=~Q(billing_address=False),
name='no False allowed'
)
]

Biggest advantage of using NullBooleanField for PostgreSQL database is your table won't be re-written, any new field without null=True will cause a table re-write, it's fine for small tables but could take a significant amount of time and resource for large tables and you won't be able to write to your table during a re-write

Related

Django MultiSelectField with multiple fields per choice

I'm using MultiSelectField in my form, which allows the user to select multiple choices:
models.py
physical_limits = MultiSelectField('Physical Limitations', choices=PHYSICAL_LIMIT, max_length=255)
screenshot link
However, I would like to add few additional fields per choice, e.g. min value, max value, units, etc (for the user to fill in, for each choice he makes).
For example, if the user selects option (2), which is "Solid", he will then be able to fill in additional corresponding "sub-fields" for this selection.
In some way, I feel like I'm looking for a different field type, which acts more like a table or array, for the user to fill. I couldn't find such a thing though..
"Vision"
Can you please guide me how to achieve that goal?
Thanks ahead,
Shahar

In Django, what does symmetrical=True do?

For example:
class Contact(models.Model):
contacts = models.ManyToManyField('self', through='ContactRelationship', symmetrical=False)
What does the symmetrical=False parameter do?
When should it be left as True, and when should it be set as False?
How does this settings affect the database (does it create extra columns etc)?
Let's say you have two instances of Contact, John and Judy. You may decide to make John a contact of Judy. Should this action also make Judy a contact of John? If so, symmetrical=True. If not, symmetrical=False
Here is what is says in the documentation:
Only used in the definition of ManyToManyFields on self. Consider the following model:
from django.db import models
class Person(models.Model):
friends = models.ManyToManyField("self")
When Django processes this model, it identifies that it has a ManyToManyField on itself, and as a result, it doesn’t add a person_set attribute to the Person class. Instead, the ManyToManyField is assumed to be symmetrical – that is, if I am your friend, then you are my friend.
By default, the value of symmetrical is True for Many to Many Field which is a bi-directional relationship.
Using a through table (symmetrical=False):
But you can also imagine a situation where you don't need this type of relationship so you can add symmetrical=False. And, this can be achieved by using a through table because by default symmetrical is False if you use a through table:
Recursive relationships using an intermediary model are always defined as non-symmetrical – that is, with symmetrical=False – therefore, there is the concept of a “source” and a “target”. In that case 'field1' will be treated as the “source” of the relationship and 'field2' as the “target”.
So you can imagine a situation where you do need the direction i.e. let's say there is a Node model and it has a relationship with itself using a through table. If we didn't have the requirement of direction here we could go with the example shown earlier. But now we also need a direction from one node to another where one being source and another one being target and due to nature of this relationship it cannot be symmetrical.

Storing Default Values for BooleanField() and IntegerField() in Django REST

I know as per the documentation the following fields do not take allow_blank=True and allow_null=True
BooleanField()
IntegerField()
I need to allow the client to not specify g or d (as per below) and to store the value in the DB as None.
g = serializers.BooleanField()
d = serializers.IntegerField()
Any ideas ?
The different options for handling of empty, blank and null fields are (necessarily) a little subtle, so its unsurprising that it sometimes trips folks up.
I know as per the documentation the following fields do not take allow_blank=True and allow_null=True
That's incorrect:
IntegerField(allow_null=True) is valid.
If you want to allow null inputs for boolean fields you need to use the NullBooleanField() class.
You are correct that neither of them take allow_blank, as the empty string isn't going to be a valid value in either case.
I need to allow the client to not specify g or d (as per below) and to store the value in the DB as None.
You can either use IntegerField(default=None) and NullBooleanField(default=None).
In this case when the values are omitted they will be included as None in serializer.validated_data. You'll want to make sure you use null=True/NullBooleanField on the model field.
Or IntegerField(required=False) and NullBooleanField(required=False).
In this case when the values are omitted they will not be included in serializer.validated_data, and the model field default will be used. You'll want to make sure you use default=None and null=True/NullBooleanField on the model field.
Note that there was a bug when using the Browsable API that empty fields in HTML input did not get the default values set. This is resolved in the upcoming 3.1.4 release.
The initial argument suggested in Edwin's answer can also be useful, but is for setting an value to be initially rendered in HTML form fields.
Django Rest Framework Serializer has initial options to set the dafault value of a field.
g = serializers.BooleanField(initial=True)
d = serializers.IntegerField(initial=0)
http://www.django-rest-framework.org/api-guide/fields/

How to deal with the required value which does not have a default value in Django model

The version of my Django is 1.7.
I have a model named Booking, it has a Boolean field named is_departure, which is used to describe the booking is departure or arrival.
class Booking(models.Model):
...
is_departure = models.BooleanField()
...
When I migrate my app, it will return me a warning that is_departure does not have a default value.
However, I do not want to add a default value for is_departure. This is a required value and it needs to be filled by user. I do not want to use NullBooleanField neither, because is_departure should not be null.
Is there any good way to remove this warning?
The problem is, what will Django put as a value for all the existing rows that now have a is_departure value that according to you, cannot be null, you can't satisfy this constraint.
If you're still developing, then you can reset the DB and you can indeed use BooleanField without default (since there will be no existing rows violating this)
Otherwise, I'd make the migration put a is_departure value (true or false) on the existing rows, consistent with your business logic

Django and writing queries with lots of joins

I have trouble to make these kind of queries with lots of joins. I didn't found examples, but I guess they are not so complicated to write. It's just there are several FKs.
Here is the models.py (not complicated)
class User(AbstractBaseUser, PermissionsMixin): # Django custom user model
# Some stuff
class CliProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
class BizProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
class Card(models.Model):
linked_client = models.ForeignKey(CliProfile, blank=True, null=True)
class Points(models.Model):
benef_card = models.ForeignKey(Card)
at_owner = models.ForeignKey(BizProfile)
creation_date = models.DateTimeField(auto_now_add=True)
Quick description of the model
a user can be a client (using CliProfile) or a business (using BizProfile)
each card is linked to a client
each card contains a [points - business] association
This way: a client has a card and can has 3 points at Pizza Hut, and 5 points at McDonalds with the same card)
The request I'm trying to write
Functionally speaking, the purpose is a owner (like PizzaHut) can see all his clients (client who have cards which has points at Pizza Hut)
Technically speaking, I'm trying to write a query to get all clients (ie. a CliProfile queryset) whose cards (at least 1 of all) whose points (at least 1 of all) whose owner (there is only 1) whose user (there is only 1) = request.user ?
Do you have any idea how to write such a query? Thanks a lot.
To match fields within models in filter() you need to use two underscores. The following worked for me
CliProfile.objects.filter(card__points__at_owner=request.user)
But #Alex's suggestion makes the most sense unless this was just an example of what you are trying to do.
If you wanted profiles that are associated with one of several cards you can use the __in field lookup:
CliProfile.objects.filter(card__in=IterableOfCards)
Also you don't use == in filter(). That would return True or False and then pass that value in the filter() call effectively making the call filter(True or False) which won't do anything useful. you have to use = because you are passing a named parameter into the filter function.
Why card instead of card_set()?
cart_set only exists within an instance of a CliProfile. You are not in an instance of a CliProfile, you are trying to get a list of them.
You can try it in the terminal and it will tell you the valid choices.
#Note that it doesn't matter what you put after=, since it fails before that is checked.
>>> CliProfile.objects.filter(card_set=True)
FieldError: Cannot resolve keyword 'card_set' into field. Choices are: card, id, user
a CliProfile can be referenced by multiple cards, which is why card_set exists in it but you are trying to match one card. The card whose points at_owner field is request.user.
You would use a_cliprofile_instance.card_set.filter() to get a subset of their cards or a_cliprofile_instance.card_set.all() to display all of their cards