Add unique_together validation check on internal attirbutes of JSONField - django

I am using following model.
class MyProfile(models.Model):
name = models.CharField(max_length=100, default=None)
account_id = models.CharField(max_length=64, default=None)
prof_config = JSONField(null=True, blank=True, default=None)
result = JSONField(null=True, blank=True, default=None)
class Meta(object):
app_label = "app"
verbose_name = "MyProfile"
unique_together = [["account_id", "prof_config"]]
Previously prof_config included:
prof_config = {"username":"pranav","password":"123456"}
But now I have changed it to :
prof_config = {"username":"pranav","password":"123456","last_sync_time":null}
And as I want unique_together validation only on account_id, username and password.
For that I changed unique_together to:
unique_together = [["account_id", "prof_config__username","prof_config__password"]]
But it didn't work. It gave me following error (if last sync time is null for both profiles):
"Error while creating My Profile, error:duplicate key value violates unique
constraint \"app_myprofile_account_id_prof_config_b94a5cdc_uniq\"\nDETAIL: Key
(account_id, prof_config)=(4, {\"password\": \"123456\", \"username\": \"pranav\",
\"last_sync_time\": null}) already exists.\n", "causality": "", "opid": "fc3501fa",
"opid_chain": "fc3501fa", "level": "ERROR"}
I am getting this error even after I have added unique_together for account_id, username and password ([["account_id", "prof_config__username","prof_config__password"]]). But it's still taking whole prof_config. And If last sync time is different the profile is being created.
So Is there any way to do this.

You can override clean method in ModelForm and do manual validation
def clean(self):
cleaned_data = self.cleaned_data
# Your custom validation
...
# raise error if something is not right
raise ValidationError('Error')
return cleaned_data

The error that you ran into is related to the existing data in your database,
The error is saying you have already data in the database that doesn't follow your new unique_together rule
You should find and remove/edit the specific record that caused this error

Related

django get_or_create throws Integrity Error

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'].

Django model unique=True not throwing any error which used in atomic transaction

Scenario:
I am trying to create/insert data to Django model (POSTGRES database) employee and profile.
When I insert duplicate records (i.e. with duplicate work_email and duplicate employee_id) I am expecting a database error in an atomic transaction.
However, the below code doesn't throw any error, but when I see the database table no duplicate is created.
Dropped a database and created a new DB and new migrations to make sure there is no problem with the sync. However, the result is the same with no errors.
Any help is appreciated. Thanks
class Employee(models.Model):
"""
Employee table containing all the employee information.
"""
profile = models.OneToOneField(Profile, on_delete=models.CASCADE)
id = models.CharField(max_length=50, unique=True, blank=False, default=uuid.uuid4, editable=False, db_index=True)
employee_id = models.CharField(
max_length=100,
blank=False,
unique=True,
primary_key=True,
error_messages={'employee_id': "A user with employee id already exists."}
)
class Profile(AbstractUser):
"""
Enhancing user model with additional fields. This is in relation with a table ProfileExtras.
Extras can be utilised to add any fields to further enhance Profile with key value pair.
"""
email = None
date_of_birth = models.DateField(blank=False)
work_email = models.EmailField(
max_length=50,
blank=False,
unique=True,
db_index=True,
error_messages={'work_email': "A user with work email already exists."}
)
def create_employee(app_context, data_dict):
try:
with transaction.atomic():
employee_model = models.Employee()
profile_model = models.Profile()
# Data insertion logic
# e.g. setattr(employee_model, "first_name", "xxxxxx")
employee_model.profile = profile_model
profile_model.save()
employee_model.save()
except Exception as e:
log.error(e)
raise e
Found the solution. I was testing with Django test. It is found that data is not retained between each test, hence the duplicate error is not raised.

Django form validation working unexpectedly?

I have this form in my view:
joinform = JoinProjectForm(request.POST)
print(request.POST)
print(joinform)
if joinform.is_valid():
joinkey = joinform.cleaned_data['joinkey']
project = Project.objects.filter(joinkey=joinkey).first()
project.users.add(request.user)
return redirect('problemdashboard:problem-dashboard', project_id=project.pk)
else:
joinform = JoinProjectForm()
And this is my model:
class Project(MainAbstractModel):
users = models.ManyToManyField(User)
title = models.CharField(max_length=25, default="")
joinkey = models.IntegerField(null=True, unique=True)
Now what I don't understand is, why is the form validation returning False? It says that a project with that joinkey already exists, but I'm not trying to create a new project with that joinkey, I simply want to add an user to the project with the joinkey that the user inputs in the form. What am I missing here?
Oh and the form itself is very simple:
class JoinProjectForm(ModelForm):
class Meta:
model = Project
fields = ['joinkey']
Error is being caused by the null=True, unique=True in:
joinkey = models.IntegerField(null=True, unique=True)
This is checking that every Project has a unique joinkey, if not then you will get 'a project with that joinkey already exists' error.
Since you have null=True and unique=True, django is considering None as a unique entry, remove null=True.

Django RestFramework - NOT NULL constraint failed

My Model:
class Wishlist(models.Model):
home = models.ForeignKey(Home, on_delete=models.CASCADE, null=False, blank=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=False)
def __str__(self):
return "{} - {}".format(self.user.username, self.home.address)
class Meta:
ordering = ('user',)
My serializer
class WishlistSerializer(serializers.ModelSerializer):
home = serializers.RelatedField(required=True, queryset=home_models.Home.objects.all())
user = serializers.RelatedField(required=True, queryset=User.objects.all())
class Meta:
model = Wishlist
fields = ('id', 'user', 'home',)
My View
class WishlistAdd(CreateAPIView):
"""
Add a new Home in User wishlist
"""
serializer_class = serializers.UserWishlistSerializer
queryset = Wishlist.objects.all()
When I try to do a POST request to create the new record I receive the following error: IntegrityError at /user/wishlist/ NOT NULL constraint failed: user_wishlist.home_id
All of these happens after a git merge, but i don't notice any differences between the branches
My guess is that your sqlite database is tracked by git. This means that whenever you switch to the broken branch, lack of data integrity causes this error – even if the application code is perfectly the same. I recommend adding the database file to your .gitignore, and looking into Django's fixtures instead.
That error means you did not provide a home id in your post payload, though this error should be caught on the serializer level because you specified required there as well. The reason it is required is because because you specified in your model field as a non-nullable field
home = models.ForeignKey(Home, on_delete=models.CASCADE, null=False, blank=False)
if you dont wish for it to be required specify null=True, blank=True. Also you need to ensure that home exists. good luck.

Skip one or more constraints in Django form.is_valid()

Given the following model:
class User(models.Model):
role = models.IntegerField(default=0, blank=True)
name = models.CharField(max_length=255, blank=False, null=False)
email = models.EmailField(max_length=128, unique=True, blank=False, null=False)
I need that form.is_valid() would skip the unique constraint on email field.
It's essential, that emails would be unique, however in one particular view I wanna use get_or_create, which doesn't seem to work:
if form.is_valid():
usr, usr_created = models.User.objects.get_or_create(email=form.email)
<...>
Is this possible?
This is not possible.
unique=True creates a database constraint that checks for the uniqueness of the field. By its very design this disallows non-unique values, and the only way to circumvent this is to remove the constraint.
If the problem is that you want to allow multiple empty values, you can set null=True. Multiple NULL values are still considered unique by the constraint.
Don't list the field you want to override in the fields list and create a new custom field in the ModelForm:
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ["role", "name"]
email = forms.EmailField(max_length=128)
def save(self, commit=True):
instance = super(PatientForm, self).save(commit=False)
instance.email= self.cleaned_data.get("email")
return instance.save(commit)