Is it possible to add a default value to ArrayField?
I tried to do this for email field, but this did not work:
constants.py:
ORDER_STATUS_CHANGED = 'order_status_changed'
NEW_SIGNAL = 'new_signal'
NOTIFICATION_SOURCE = (
(ORDER_STATUS_CHANGED, 'Order Status Changed'),
(NEW_SIGNAL, 'New Signal'),
)
models.py:
from notifications import constants
from django.contrib.postgres.fields import ArrayField
class NotificationSetting(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='notification_setting')
telegram = ArrayField(models.CharField(
choices= constants.NOTIFICATION_SOURCE,
max_length=30
), default=list)
email = ArrayField(models.CharField(
choices= constants.NOTIFICATION_SOURCE,
max_length=16
), default=list(dict(constants.NOTIFICATION_SOURCE).keys()))
class Meta:
db_table = 'notification_settings'
def __str__(self):
return f'Notification setting for user {self.user}'
And override the save method of the model would be bad practice, I think.
The problem is that in the django admin site I see that the default values did not count when the object was created. (UPD. Maibe i have problem with my custom ChoiseArrayField widged)
And i get this mesagge:
WARNINGS:
notifications.NotificationSetting.email: (postgres.E003) ArrayField default should be a callable instead of an instance so that it's not shared between all field instances.
HINT: Use a callable instead, e.g., uselistinstead of[]``
The default property on an ArrayField should be a callable. You can read more about that here: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/.
What you are getting by placing directly there list(dict(constants.NOTIFICATION_SOURCE).keys()) is just a warning so it should still add the defaults to the field. By placing this default directly there it will put in the migrations the following thing and the values will be shared across all field instances:
default=['order_status_changed', 'new_signal']
To get rid of the warning you should create a function that returns the default value:
def get_email_default():
return list(dict(constants.NOTIFICATION_SOURCE).keys())
and put the function as the default to the field:
email = ArrayField(models.CharField(
choices= constants.NOTIFICATION_SOURCE,
max_length=16
), default=get_email_default)
By doing this the warning will be gone and from the function you can have logic for choosing the default value.
After doing this, in the migrations the default value will look like this:
default=my_model.models.get_email_default
Related
#models.py
class Mymodel(models.Model):
name = models.CharField(max_length=100,null=False,blank=False)
email = models.EmailField(max_length=100,null=False,blank=False)
password = models.CharField(max_length=120,null=False,blank=False)
email_notification = models.BooleanField()
#views.py
obj=MyModel.objects.create(name="ok",password="dsfdsfdsfdsfsfds",email_notification=1)
even if email was required field,then also object was created when I see in the admin panel.What can be the issue,Why object got created,even if email field was mandatory?
Also if I go in admin panel and open that object and click save then it raises that email is required
Note: You don't to have provide null=False,blank=False in your fields because those are the values used by default.(See the Django Field __int__ signature.).
def __init__(self, verbose_name=None, name=None, primary_key=False,
max_length=None, unique=False, blank=False, null=False,
db_index=False, rel=None, default=NOT_PROVIDED, editable=True,
serialize=True, unique_for_date=None, unique_for_month=None,
unique_for_year=None, choices=None, help_text='', db_column=None,
db_tablespace=None, auto_created=False, validators=(),
error_messages=None):
By default all the fields in database are created with NOT NULL constraint. If you set null=True for a particular field, then django sets NULL on the column in your DB. It’s the database equivalent of Python’s None keyword.
Example with null argument
Assume that I have the following Mymodel in my my_app and I set email field to null=True.
class MyModel(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(max_length=100, null=True)
password = models.CharField(max_length=120)
email_notification = models.BooleanField()
In Shell,
>> from my_app.models import MyModel
>> new = MyModel.objects.create(name="ok",
password="dsfdsfdsfdsfsfds",
email_notification=1)
>> new.email == None
>> True # As you can see Django sets db value
# as NULL and when we query the data it converts back to Python `None` object.
Example without null argument
Assume that I have the following Mymodel in my my_app.(remember null will be False by default)
class MyModel(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(max_length=100)
password = models.CharField(max_length=120)
email_notification = models.BooleanField()
In Shell,
>> from my_app.models import MyModel
>> new_obj = MyModel.objects.create(name="test",
password="test",
email_notification=1)
>> new_obj.email == ''
>> True
Ie,Django CharField and TextField the default values are stored in the DB as an empty string (''). In other words, if you create an object without providing values for a CharField(or a TextField) under the hood Django invokes the get_default method and returns '' (only in this case). This value will be stored in the database.
The following is the source code of get_default method.
def get_default(self):
"""Return the default value for this field."""
return self._get_default()
#cached_property
def _get_default(self):
if self.has_default():
if callable(self.default):
return self.default
return lambda: self.default
if not self.empty_strings_allowed or self.null and not connection.features.interprets_empty_strings_as_nulls:
return return_None
return str # return empty string
Let's answer your question:
Why object got created,even if email field was mandatory?
The answer is EmailField is an instance of CharField Hence default value '' will be used while creating an object in database. That is why you are not getting django.db.utils.IntegrityError.
>> new_obj = Mymodel.objects.create(name='tes1t', password='test1', email_notification=1)
>>> new_obj.email
''
Also if I go to the admin panel and open that object and click save then
it raises an error indicating that email is required
Remember blank is different from null. null is purely database-related, whereas blank is validation-related. So when you create an object directly in Python code, or execute raw SQL yourself, you are actually bypassing all of Django’s input validation. But in admin, Django is validating the input through the model form. Since in your case blank is set to False(blank not allowed), model form will raise Email is required Error.
Here is the relevant Django documentation for blank argument.
Field.blank
If True, the field is allowed to be blank. Default is False. Note
that this is different than null. null is purely database-related,
whereas blank is validation-related. If a field has blank=True, form
validation will allow entry of an empty value. If a field has
blank=False, the field will be required.
Additional resources
Django tips: the difference between ‘blank’ and ‘null’
differentiate null=True, blank=True in django
There is no issue with django you will have to create proper Django Model Form validation so that the empty string isn't ignored and it will raise an error for the blank field.
Using the Django REST Framework, I would like to allow users to create and save instances of a Django model through a ListCreateAPIView (via POST). One of the fields (a foreign-key field called domain) shall be determined from a view parameter as defined in urls.py.
Furthermore, the user can modify the model instance later using PUT or PATCH requests to a RetrieveUpdateDestroyAPIView endpoint (using the same serializer). I don't want the user to be able to modify the domain field at this point.
While I have the code for the model and the view / serializer structure ready, I'm not sure how to tell the serializer to determine the value of the domain field based on the view parameter. Here's what I got:
class RRset(models.Model):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(null=True)
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, related_name='rrsets')
subname = models.CharField(max_length=255, blank=True)
type = models.CharField(max_length=10)
... and a straight-forward ListCreateAPIView:
class RRsetsDetail(generics.ListCreateAPIView):
serializer_class = RRsetSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
name = self.kwargs['name']
return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk)
urls.py contains the following line:
url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/$', RRsetsDetail.as_view(), name='rrsets')
This allows the user to list and create RRset objects using the RRsetsSerializer serializer (the name field is listed for completeness only, but I do not believe it to be important in this context):
class RRsetSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField()
def get_name(self, obj):
return '.'.join(filter(None, [obj.subname, obj.domain.name])) + '.' # returns 'subname.name.'
class Meta:
model = RRset
fields = ('created', 'updated', 'domain', 'name', 'type',)
read_only_fields = ('created', 'updated', 'domain', 'type',)
Questions:
What do I need to modify to have the serializer take the domain name from the view name parameter?
The the serializer's read_only_fields setting prevents the user from modifying the domain field later. However, I'm not sure if this setting somehow interacts with the serializer trying to set a default value (can the serializer write the default value, even if read-only is set)?
To summarize: What I'm looking for is something like a "write-once field with a default value based on a view parameter".
I think you are looking for a HiddenField with a combination of CreateOnlyDefault
HiddenField
A field class that does not take a value based on user input, but instead takes its value from a default value or callable.
CreateOnlyDefault
A default class that can be used to only set a default argument during
create operations. During updates the field is omitted.
It takes a single argument, which is the default value or callable
that should be used during create operations.
And because you want to access the view, you can't just use callable, but you have to use Class-based callable which can have access to a context data.
class DomainDefault(object):
def set_context(self, serializer_field):
view = serializer_field.context['view']
request = serializer_field.context['request']
self.domain = ...#determine the domain based on request+view
def __call__(self):
return self.domain
class RRsetSerializer(serializers.ModelSerializer):
domain = serializers.HiddenField(default=serializers.CreateOnlyDefault(DomainDefault()))
I have a model with two entities, Person and Code. Person is referenced by Code twice, a Person can be either the user of the code or the approver.
What I want to achieve is the following:
if the user provides an existing Person.cusman, no further action is needed.
if the user provides an unknown Person.cusman, a helper code looks up other attributes of the Person (from an external database), and creates a new Person entity.
I have implemented a function triggered by pre_save signal, which creates the missing Person on the fly. It works fine as long as I use python manage.py shell to create a Code with nonexistent Person.
However, when I try to add a new Code using the admin form or a CreateView descendant I always get the following validation error on the HTML form:
Select a valid choice. That choice is not one of the available choices.
Obviously there's a validation happening between clicking on the Save button and the Code.save() method, but I can't figure out which is it. Can you help me which method should I override to accept invalid foreign keys until pre_save creates the referenced entity?
models.py
class Person(models.Model):
cusman = models.CharField(
max_length=10,
primary_key=True)
name = models.CharField(max_length=30)
email = models.EmailField()
def __unicode__(self):
return u'{0} ({1})'.format(self.name, self.cusman)
class Code(models.Model):
user = models.ForeignKey(
Person,
on_delete=models.PROTECT,
db_constraint=False)
approver = models.ForeignKey(
Person,
on_delete=models.PROTECT,
related_name='approves',
db_constraint=False)
signals.py
#receiver(pre_save, sender=Code)
def create_referenced_person(sender, instance, **kwargs):
def create_person_if_doesnt_exist(cusman):
try:
Person = Person.objects.get(pk=cusman)
except Person.DoesNotExist:
Person = Person()
cr = CusmanResolver()
Person_details = cr.get_person_details(cusman)
Person.cusman = Person_details['cusman']
Person.name = Person_details['name']
Person.email = Person_details['email']
Person.save()
create_Person_if_doesnt_exist(instance.user_id)
create_Person_if_doesnt_exist(instance.approver_id)
views.py
class CodeAddForm(ModelForm):
class Meta:
model = Code
fields = [
'user',
'approver',
]
widgets = {
'user': TextInput,
'approver': TextInput
}
class CodeAddView(generic.CreateView):
template_name = 'teladm/code_add.html'
form_class = CodeAddForm
You misunderstood one thing: You shouldn't use TextField to populate ForeignKey, because django foreign keys are populated using dropdown/radio button to refer to the id of the object in another model. The error you got means you provided wrong information that doesn't match any id in another model(Person in your case).
What you can do is: not using ModelForm but Form. You might have some extra work to do after you call form.is_valid(), but at least you could code up your logic however you want.
from the documentation:
read_only
Set this to True to ensure that the field is used when serializing a representation, but is not used when updating an instance during deserialization.
Defaults to False
required
Normally an error will be raised if a field is not supplied during deserialization. Set to false if this field is not required to be present during deserialization.
Defaults to True.
So I have a model which has a field that's not nullable but I want it to be populated in the pre_save method, so I have set the field to required=False in serializer, but doesn't seem to work. I am still getting error when saving the record.
class FavoriteListSerializer(serializers.ModelSerializer):
owner = serializers.IntegerField(required=False)
class Meta:
model = models.FavoriteList
Update:
I have added serializer_class = serializers.FavoriteListSerializer to the ViewSet, now instead of getting This field is required, which I think got past the validation but then I am getting This field cannot be null. I have checked the pre_save method is not being executed, any ideas?
Yeah, I ran into this issue at some point as well. You need to also update the validation exclusions.
class FavoriteListSerializer(serializers.ModelSerializer):
owner = serializers.IntegerField(required=False)
class Meta:
model = models.FavoriteList
def get_validation_exclusions(self):
exclusions = super(FavoriteListSerializer, self).get_validation_exclusions()
return exclusions + ['owner']
Late Entry to this thread. This issue was fixed in django-rest-framework 2.3.13. Here is the link of the PR.
You use it like this in your case:
class Meta:
model = models.FavoriteList
optional_fields = ['owner', ]
In case somebody lands here with a similar issue, pay attention to the following attributes along with required:
allow_blank:
If set to True then the empty string should be considered a valid value.
allow_null:
Normally an error will be raised if None is passed to a serializer field.
required:
Normally an error will be raised if a field is not supplied during deserialization.
I was straggling to figure out why I was getting a validation error with required=False where I had missed the allow_null attribute.
In 2020, for DRF 3.12.x, the approach that I prefer the approach that relies on
Serializer's extra_kwargs.
So assuming your
class FavoriteListSerializer(serializers.ModelSerializer):
owner = serializers.IntegerField(required=False)
class Meta:
model = models.FavoriteList
fields = ["owner"] # and whatever other fields you want to expose
extra_kwargs = {"owner": {"required": False, "allow_null": True}}
If you have unique_together constraint on one of the fields you are trying to set required=False you need to set validators=[] in serializers Meta like
class FavoriteListSerializer(serializers.ModelSerializer):
owner = serializers.IntegerField(required=False)
class Meta:
model = models.FavoriteList
validators = []
Here is the original answer
You can also do this:
class ASerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
...
As referred here: https://www.django-rest-framework.org/api-guide/validators/#advanced-field-defaults
There you can also find the case when you also wanna let the view show owner
I would set model field to allow null value (and possible also default to None)
class FavoriteList(models.Model):
owner = models.PositiveIntegerField(null=True, default=None)
Then it's possible to just leave owner field to Meta section. These fields, without any extra settings, will automatically get all attributes from model field and be non-required.
class FavoriteListSerializer(serializers.ModelSerializer):
class Meta:
model = models.FavoriteList
fields = ('owner',)
I've been getting the most weird error ever. I have a Person model
class Person(models.Model):
user = models.OneToOneField(User, primary_key=True)
facebook_id = models.CharField(max_length=225, unique=True, null=True, blank=True)
twitter_id = models.CharField(max_length=225, unique=True, null=True, blank=True)
suggested_person = models.BooleanField(default=False)
I recently added the twitter_id field. When I access the Django admin page, and try to change the 'person' into a suggested_person, I get the following error:
Person with this Twitter id already exists.
I find this error to be extremely strange because the Facebook_id field is designed the exact same way as the Twitter_id field.
What could be the reason for this?
None of the answers clearly describe the root of the problem.
Normally in the db you can make a field null=True, unique=True and it will work... because NULL != NULL. So each blank value is still considered unique.
But unfortunately for CharFields Django will save an empty string "" (because when you submit a form everything comes into Django as strings, and you may have really wanted to save an empty string "" - Django doesn't know if it should convert to None)
This basically means you shouldn't use CharField(unique=True, null=True, blank=True) in Django. As others have noted you probably have to give up the db-level unique constraint and do your own unique checks in the model.
For further reference, see here: https://code.djangoproject.com/ticket/4136
(unfortunately no good solution decided at time of writing)
NOTE:
As pointed out by #alasdair in a comment, that bug has now been fixed - since Django 1.11 you can use CharField(unique=True, null=True, blank=True) without having to manually convert blank values to None
This is an old one but I had a similar issue just now and though I would provide an alternative solution.
I am in a situation where I need to be able to have a CharField with null=True, blank=True and unique=True. If I submit an empty string in the admin panel it will not submit because the blank string is not unique.
To fix this, I override the 'clean' function in the ModelForm, and in there I check if it's a blank string and return the result accordinly.
class MyModelChangeForm(forms.ModelForm):
class Meta:
model = models.MyModel
fields = ['email', 'name', 'something_unique_or_null',]
def clean_something_unique_or_null(self):
if self.cleaned_data['something_unique_or_null'] == "":
return None
else:
return self.cleaned_data['something_unique_or_null']
This fixed the problem for me without having to sacrifice the unique attribute on the model field.
Hope this helps.
EDIT:
You need to change where I have put "something_unique_or_null" to the name of your field. For example "clean_twitter_id".
In Django 1.11, form CharFields will have an empty_value argument, which allows you to use None if the field is empty.
Model forms, including the Django admin, will automatically set empty_value=None if the model's CharField has null=True.
Therefore, you will be able to use null=True, blank=True and unique=True together in your model CharField without the unique constraint causing problems.
Since you have null=True, blank=True and unique=True, django is considering None or blank as a unique entry. Remove the unique constraint and handle the uniqueness part in the code.
It's important to solve this at the model level, not at the form level, since data can enter through APIs, through import scripts, from the shell, etc. The downside of setting null=True on a CharField is that the column could end up with both empty strings and NULLs, which is slightly ambiguous but not generally a problem in my experience. If you're willing to live with that ambiguity, here's how to do it in a few steps:
1) Set null=True, blank=True on the field and migrate in the change.
2) Massage your data so that all existing empty strings are changed to NULLs:
items = Foo.objects.all()
for item in items:
if not item.somefield:
item.somefield = None
item.save()
3) Add a custom save() method to your model:
def save(self, *args, **kwargs):
# Empty strings are not unique, but we can save multiple NULLs
if not self.somefield:
self.somefield = None
super().save(*args, **kwargs) # Python3-style super()
4) Set unique=True on the field and migrate that in as well.
Now you'll be able to store somefield as empty or as a unique value whether you're using the admin or any other data entry method.
If you prefer not to have several migrations, here's an example of how to do it in a single migration:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
def set_nulls(apps, schema_editor):
Event = apps.get_model("events", "Event")
events = Event.objects.all()
for e in events:
if not e.wdid:
e.wdid = None
e.save()
class Migration(migrations.Migration):
dependencies = [
('events', '0008_something'),
]
operations = [
migrations.AlterField(
model_name='event',
name='wdid',
field=models.CharField(blank=True, max_length=32, null=True),
),
migrations.RunPython(set_nulls),
migrations.AlterField(
model_name='event',
name='wdid',
field=models.CharField(blank=True, max_length=32, null=True, unique=True),
),
]
The root of the problem is that Django persist the empty value as empty-string, and not as null. To fix this, you can subclass CharField as follows:
class CharNullField(models.CharField):
description = "CharField that stores NULL"
def get_db_prep_value(self, value, connection=None, prepared=False):
value = super(CharNullField, self).get_db_prep_value(value, connection, prepared)
if value=="":
return None
else:
return value
So get_db_prep_value will make it sure that null gets persisted.
You have to provide default=None in the field
facebook_id = models.CharField(max_length=225,unique=True, null=True,blank=True,default=None)
This worked for me . I used it in phone number. So it can be unique if entered and not mandatory.
you are accepting blank values and expect them to be unique. this means there can only be ONE entry with a blank twitter_id
you can
either remove the unique contraint
remove the blank =True
give a default value for the field ( but default needs to be unique)