I have a model like this:
class CreateDeal(models.Model):
name = models.CharField(max_length=100)
fuel = models.CharField(max_length=15)
mileage = models.PositiveIntegerField(db_index=True)
phone_number = models.CharField(max_length=17)
location = models.CharField(max_length=100, db_index=True)
car_picture = models.ImageField(upload_to='car_picture')
description = models.TextField()
price = models.PositiveSmallIntegerField(db_index=True)
available = models.BooleanField(default=True)
created_on = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.name
and I have a test class to test the model above like this:
class CreateDealTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(
username='alfa', email='alfa#hotmail.com', password='top_secret'
)
self.deal = CreateDeal.objects.create(
name='deal1', mileage=100, price=25, user=self.user
)
def test_deal_name(self):
deal = CreateDeal.objects.get(name='deal1')
expected_deal_name = f'{self.deal.name}'
self.assertAlmostEqual(expected_deal_name, str(deal))
if I run the test I have:
Ran 1 test in 0.166s
OK
My question is why django don't raise an exception since almost all fields in my model are required. And what I don't understand is if I remove one field of Createdeal in my setUp (like mileage, price, user or name) I have an error.
For instance if I remove mileage, I have this error:
raise utils.IntegrityError(*tuple(e.args))
django.db.utils.IntegrityError: (1048, "Column 'mileage' cannot be null")
Charfield, Imagefield and Textfield can be empty string which is valid at the database level, some of your fields have default values so they will be written if not set so that makes them also valid at the database level.
PositiveIntegerField and Foreign key cannot be set to empty string, just to value or null so they will fail since null=False by default.
The default blank=False option is only applied at the validation level, not at the database level. This means if you call full_clean() on your model, it will raise a ValidationError. But nothing stops you from saving an invalid model (save() does not call full_clean() as explained here).
Related
I have read this thread:
get_or_create throws Integrity Error
But still not fully understand when get_or_create returns False or IntegrityError.
I have the following code:
django_username = 'me'
user = get_user_model().objects.filter(username=django_username).first()
action_history, action_added = ActionModel.objects.get_or_create(
date=date_obj, # date object
account_name=unique_name, # e.g. account1234
target=follower_obj, # another model in django
user=user, # connected django user
defaults={'identifier': history['user_id']}
)
While the model looks like:
class ActionModel(models.Model):
"""
A model to store action history.
"""
identifier = models.BigIntegerField(
_("identifier"), null=True, blank=True) # target id
account_name = models.CharField(_("AccountName"), max_length=150, null=True, blank=True) # account name of the client
date = models.DateField(_("Date"), auto_now=False, auto_now_add=False) # action date
target = models.ForeignKey(Follower, verbose_name=_("Target"), on_delete=models.CASCADE) # username of the done-on action
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
editable=False,
db_index=True,
) # django user that performed the action
class Meta:
verbose_name = _("Action")
verbose_name_plural = _("Actions")
unique_together = [
['account_name','date','target'],
]
Sometimes it return IntegrityError, and sometimes (when unique constrain exists it will return False on created).
You have unique_together constraint.
Lets imagine you have object in db with following data
account_name='bob', date='2020-12-12', target='b', user='12'
In you get_or_create method you are doing this
ActionModel.objects.get_or_create(
date='2020-12-12',
account_name='bob',
target='b',
user="13"
)
you providing exactly this three parameters with this data, but user this time is 13, so django could not find any object and it tries to create one, but with this parametres you cant create object because there is unique constraint
OK.
I figured it out, I sent:
account_name = 'adi'
date = '07-02-21'
target = 'nana1'
user = 'me'
While it was not exist with the specific user = 'me' but with user = None:
account_name = 'adi'
date = '07-02-21'
target = 'nana1'
user = None
So the get was failing and the created try to duplicate the unique_together = ['account_name','date','target'].
I would like to create and save model instances with Django.
These are my models:
class Customer(models.Model):
id = models.IntegerField(primary_key=True, unique=True)
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
...
class Order(models.Model):
id = models.IntegerField(primary_key=True, unique=True)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
comment = models.TextField()
...
I create an order like this:
def store_storage_space_order(cleaned_data):
try:
user = User.objects.create_user(
cleaned_data["customer_email"],
cleaned_data["customer_email"],
1234
)
customer = Customer.objects.create(
user=user,
first_name=cleaned_data["customer_firstname"],
last_name=cleaned_data["customer_lastname"],
email=cleaned_data["customer_email"],
phone_number=cleaned_data["customer_phone"]
)
StorageSpaceOrder.objects.create(
customer=customer,
order_price=Decimal(cleaned_data["order_price"])
)
except Exception as exc:
logger.exception("Couldn't store any order information", exc, cleaned_data)
As far as I've learned, Django will save the object on calling create as well.
Trying to save the order, I get the following error message:
save() prohibited to prevent data loss due to unsaved related customer
What I don't get; customer is already there and saved in the database. There are no changes on the customer in between.
Also, I've tried customer_id=customer.id/pk, - but both ID and PK return None.
Why is this and what do I need to change? Loading the object again is not the preferred way, as I only got the ID which marks it as unique.
Thanks for your input :)
ID fields have to be auto-generated by Django, so you don't need to declare them in your models.
If you do, you override the default behaviour called when an instance of this model is created/saved.
In my django app, there is something strange that's happening & i'm not understanding.
I have two different tables (employeeProfile & purchaserShippingDetail) each has a field with the relation OneToOneField but with the 1st table (employeeProfile) in the field user that uses OneToOneField i can pass a string representation say Michael using api & i don't get an error but in my 2nd table that has similar structure when i add a string representation to i get
IntegrityError at /api/clients/shipping/
null value in column "owner_id" violates not-null constraint
1st Table model (works fine)
class employeeProfile(models.Model):
image = models.ImageField(default='default.png',upload_to='employee_photos/%Y/%m/%d/')
user = models.OneToOneField(CustomUser, on_delete=models.CASCADE, related_name="employee_profile")
phone_no = models.CharField(max_length=10, unique=True)
def __str__(self):
return self.user.name
2nd Table Model (The one that throws the "owner_id" violates not-null constraint error)
class purchaserShippingDetail(models.Model):
frequent_customer = models.BooleanField(default=False)
owner = models.OneToOneField(Purchaser, on_delete=models.CASCADE, related_name="purchaser_shipping")
address = models.CharField(max_length=12, blank=True)
zip_code = models.CharField(max_length=12, blank=True)
location = models.CharField(max_length=255)
def __str__(self):
return self.owner.name
Purchaser Model
class Purchaser(models.Model):
name = models.CharField(max_length=50)
phone = models.CharField(max_length=20, unique=True)
email = models.EmailField(max_length=255, unique=True, blank=True)
data_added = models.DateField(default=datetime.date.today)
def __str__(self):
return self.name
serializer for purchaserShippingDetail model
class purchaserShippingDetailSerializer(serializers.ModelSerializer):
owner = serializers.StringRelatedField(read_only=True)
class Meta:
model = purchaserShippingDetail
fields = '__all__'
Views.py for purchaserShippingDetail model
class purchaserShippingDetailsListCreateView(ListCreateAPIView):
serializer_class = purchaserShippingDetailSerializer
queryset = purchaserShippingDetail.objects.all()
EDIT: Added Purchaser model table
Could you please post the Purchaser model as well? There is a referenced field owner_id that we can't see in your post, which will explain more.
Have you added the owner field in a recent migration? It could be that you are adding a non-nullable field to a Table with existing rows, making those rows fail to satisfy the non-nullable condition.
You cannot add a default value on a OneToOneField so in that case you have to first add the field as null=True.
Then create an empty migration file to instantiate all rows on that table so no rows have a null value. Normally you do this by manage.py makemigrations app_name --empty.
That file could look something like
from django.db import migrations
def instantiate_owner(apps, schema_editor):
purchaserShippingDetail = apps.get_model("appname", "purchaserShippingDetail")
Purchaser = apps.get_model("some_other_app", "Purchaser")
for detail in purchaserShippingDetail.objects.all():
owner = Purchaser.objects.get(some_unique_criteria=detail.unique_criteria)
detail.owner = owner
detail.save()
class Migration(migrations.Migration):
dependencies = [
...,
]
operations = [
migrations.RunPython(instantiate_owner),
]
After that is complete you can remove the null=True in your model and make another migration.
I always use the following code to validate a form to prevent blank form submission. It always works in Django 1.8 but for some reason is not working in Django 2.2.
Here is the form
class CreateForm(forms.ModelForm):
class Meta:
model = Device
fields = ['category', 'item_name', 'quantity']
def clean_category(self):
category = self.cleaned_data.get('category')
if category == '':
raise forms.ValidationError('This field is required')
return category
def clean_item_name(self):
item_name = self.cleaned_data.get('item_name')
if item_name == '':
raise forms.ValidationError('This field is required')
return item_name
Here is the model
class Device(models.Model):
category = models.CharField(max_length=50, blank=True, null=True)
item_name = models.CharField(max_length=50, blank=True, null=True)
quantity = models.IntegerField(default='0', blank=False, null=True)
Thanks
I think the problem is that you did not check fro None, but nevertheless. I think you aim to do too much work yourself. You can just specify that the field is required=True [Django-doc], this will:
By default, each Field class assumes the value is required, so if you pass an empty value – either None or the empty string ("") – then clean() will raise a ValidationError exception.
So we can make the fields required with:
class CreateForm(forms.ModelForm):
category = forms.CharField(required=True, max_length=50)
item_name = forms.CharField(required=True, max_length=50)
class Meta:
model = Device
fields = ['category', 'item_name', 'quantity']
That being said, it is rather "odd" to specify blank=True [Django-doc] since this actually means that the field is not required in model forms. blank=True does not mean that the empty string is allowed, since even with blank=False, you can store empty strings in the field. A ModelForm will define (most of) its validation based on the model it "wraps", so that means that if you define the model better, you remove a lot of boilerplate code. Therefore I would advise eliminating blank=True.
Since you are specifying these fields as blank=True and null=True in your model so change those attributes
class Device(models.Model):
category = models.CharField(max_length=50, blank=False, null=False)
Or by default if you don't specify these blank and null attribute then it will be false by default so this should work also
class Device(models.Model):
category = models.CharField(max_length=50)
EDIT based on the comment:
Like the Willem said you need to check for None.You can do like this.
def clean_category(self):
category = self.cleaned_data.get('category')
if not category:
raise forms.ValidationError('This field is required')
return category
I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
When I call the below code through API, it shows the following error. But data is saving in the tables. I also want to implement the get and put calls for this api.
Got AttributeError when attempting to get a value for field `city` on serializer `CustomerSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'city'.
my Bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
my Bcustomer/serializers.py
from django.contrib.auth import get_user_model
from models import BCustomer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
city = serializers.CharField()
class Meta:
model = get_user_model()
fields = ('first_name', 'email','city')
def create(self, validated_data):
userModel = get_user_model()
email = validated_data.pop('email', None)
first_name = validated_data.pop('first_name', None)
city = validated_data.pop('city', None)
request = self.context.get('request')
creator = request.user
user = userModel.objects.create(
first_name=first_name,
email=email,
# etc ...
)
customer = BCustomer.objects.create(
customer=user,
city=city,
user=creator
# etc ...
)
return user
my Bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
my Bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
POST request format
{
"first_name":"Jsanefvf dss",
"city":"My City",
"email":"myemail#gmail.com",
#more fields
}
I also need to implement put and get for this api. Now data is saving in both tables but shows the error.
Sure it complains.
Validation goes well, so does the creation but once it's created, the view will deserialize the result and return it to the client.
This is the point where it goes south. Serializer is configured to think that city is a field of the default user while it actually is part of BCustomer. In order to work this around, you should set the source argument to the city field. You might need to update the serializer's create/update to reflect that change, not sure about that one.