Overriding save method to create second auto incrementing field in Django - python

I am trying to override the save method on a model in order to generate a unique, second auto-incrementing id.
I create my class and override the save() method, but for some reason it is erroring out with the following error:
TypeError: %d format: a number is required, not NoneType
Here's the code:
class Person(models.Model):
target = models.OneToOneField(Target)
person = models.OneToOneField(User)
gender = models.CharField(max_length=1)
gender_name = models.CharField(max_length=100)
person_id = models.CharField(max_length=100)
def save(self, *args, **kwargs):
self.person_id = "%07d" % self.id
super(Person, self).save(*args, **kwargs)
Is it because I didn't pass an id parameter and it hasn't saved yet? Is there anyway to generate a value from the id?

Safest and easiest way to achieve what you want is to use a post_save signal because it is fired right after save is called, but before the transaction is committed to the database.
from django.dispatch import receiver
from django.db.models.signals import post_save
#receiver(post_save, sender=Person)
def set_person_id(sender, instance, created, **kwargs):
if created:
instance.person_id = "%07d" % instance.id
instance.save()

Yes, self.id will be Nonein some cases, and then the assignment will fail.
However you cannot just the assignment and the call to super, as suggested in the comments, because then you wouldn't be persisting the assignment to the database layer.
You need to check whether the model has an id and then proceed differently:
def save(self, *args, **kwargs):
if not self.id: # Upon instance creation
super(Person, self).save(*args, **kwargs) # Acquire an ID
self.person_id = "%07d" % self.id # Set the person_id
return super(Person, self).save(*args, **kwargs)
This issues two save operations to the database. You will want to wrap them in a transaction to make sure your database receives these two fields simultaneously.
from django.db import IntegrityError, transaction
class Person(models.Model):
target = models.OneToOneField(Target)
person = models.OneToOneField(User)
gender = models.CharField(max_length=1)
gender_name = models.CharField(max_length=100)
person_id = models.CharField(max_length=100)
def create_person_id(self):
if not self.id: # Upon instance creation
super(Person, self).save(*args, **kwargs) # Acquire an ID
self.person_id = "%07d" % self.id
def save(self, *args, **kwargs):
try:
with transaction.atomic():
self.create_person_id
return super(Person, self).save(*args,**kwargs)
except IntegrityError:
raise # or deal with the error

I agree that signals might be the better option, if not, try using pk instead of id.
class Person(models.Model):
# [ . . . ]
def save(self, *args, **kwargs):
self.person_id = "%07d" % self.pk
super(Person, self).save(*args, **kwargs)

Related

How to get ForeignKey field value during new model creation?

I'm looking for the most efficient way to implement this kind of mechanism in Django model.
Let's assume a situation, where there are 2 very simple models:
class FKModel(models.Model):
value = BooleanField()
class AModel(models.Model):
fk = models.ForeignKey(FKModel)
a_value = models.CharField(max_length=150)
def clean(self, *args, **kwargs):
# the line below is incorrect
if not self.fk.value: # <--- how to do this in a proper way?
raise ValidationError('FKModel value is False')
super(AModel, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(AModel, self).save(*args, **kwargs)
I know, that I can do somethink like FKModel.objects.all()/.get(), but I don't think it is the best solution (as it requires additional requests to database).
I am not sure what you try to do in your clean() method, but I assume you are trying to constrain a not null condition for the foreign key. All fields are not null constrained by default, and you have to set null=False and blank=False if you want the field to accept nulls:
class AModel(models.Model):
fk = models.ForeignKey(FKModel, null=True, blank=True)
a_value = models.CharField(max_length=150)
If you want to constrain a not null condition for a field by hand, you should do it like this:
class FKModel(models.Model):
value = BooleanField()
class AModel(models.Model):
fk = models.ForeignKey(FKModel)
a_value = models.CharField(max_length=150)
def clean(self, *args, **kwargs):
# the line below is correct
if self.fk is None: # <--- this is the proper way?
raise ValidationError('FKModel value is False')
super(AModel, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(AModel, self).save(*args, **kwargs)
For retrieving database records and its related records, you use prefetch_related, and you get your record and its related records in one single database hit:
AModel.objects.all().prefetch_related('fk')

Form Problems - Setting Initial Value

I am trying to set the initial value of a field on a form. The field is not part of the model, but when I try and set it to a value the field is blank. From my research it could be because the form is "bound" which makes some sense to me, but in this case the field is not part of the model.
My form:
#Form for editing profile
class CatForm(forms.ModelForm):
pictureid = forms.CharField()
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
print(picid)
super(CatForm, self).__init__(*args, **kwargs)
self.fields['pictureid'] = forms.CharField(initial=picid, required=False)
The model:
class Cat(models.Model):
name = models.CharField(max_length=34,null=False)
From the view it is called like this:
catform = CatForm(request.POST, pictureid=instance.id)
I was expecting it to set the field to the value of the initial attribute, but it doesn't. I have tried testing it by directly adding a string, but doesn't set.
This is what seems to be working for me:
class CatForm(forms.ModelForm):
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
super(CatForm, self).__init__(*args, **kwargs)
self.fields['pictureid'] = forms.CharField(initial=picid)
I also needed to drop the "request.POST" from the call to this when initiating the form.
If you want to render the pictureid in GET request, then you can try like this:
catform = CatForm(initial={'pictureid': instance.id})
For GET request, you don't need to override the __init__ method.
But, if you want to use the Catform in POST request, to use the value of pictureid somewhere else(lets say in save method), then you will need to override __init__ method here.
class CatForm(forms.ModelForm):
pictureid = forms.CharField()
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
print(picid)
super(CatForm, self).__init__(*args, **kwargs)
self.pictureid = picid
def save(self, *args, **kwargs):
print(self.pictureid) # if you want to use it in save method
return super().save(*args, **kwargs)

Django method update_or_create troubles to increment value

I've got a Django models:
class Item_received_log(models.Model):
name = models.CharField(max_length=250)
quantity = models.FloatField(default=1)
class Inventory (models.Model):
name = models.CharField(max_length=250)
quantity = models.FloatField(default=1)
I would like to update Inventory.quantity each time new item to Item_received_log is posted with matching name. I am not sure if it is right but I've decided to override save method of Item_received_log class so it updates Inventory list upon saving:
def save(self, *args, **kwargs):
obj, created = Inventory.objects.update_or_create(
name=self.name,
defaults = {'quantity':(quantity + self.quantity)})
super(Item_received_log, self).save(*args, **kwargs)
And in returns:
NameError at /admin/accountable_persons/item_received_log/17/change/
global name 'quantity' is not defined
How can I resolve my issue or come up with better solution?
Would have been a lot easier if we could simply throw in an F() expression into the default part of update_or_create to do all the magic, but the issue requesting this feature is still open.
You can, however, use a more verbose approach for now:
from django.db.models import F
def save(self, *args, **kwargs):
obj, created = Inventory.objects.get_or_create(name=self.name)
obj.quantity = F('quantity') + self.quantity
obj.save()
super(Item_received_log, self).save(*args, **kwargs)

Django - How can i modify text before save them to database?

I want to input something like(via the admin page):
text = 't(es)t'
and save them as:
'test'
on database.
And I use this Regex to modify them:
re.sub(r'(.*)\({1}(.*)\){1}(.*)', r'\1\2\3', text)
I know how to transform text from 't(es)t' to 'test' but the problem is
when i use
name = models.CharField(primary_key=True, max_length=16)
to input text(from admin). It immediately save to database cannot modify it before saving.
Finally, From a single input from admin text = 't(es)t' (CharField).
What do i want?
To use 't(es)t' as a string variable.
Save 'test' to database
Try to overide the save method in your model,
class Model(model.Model):
name = models.CharField(primary_key=True, max_length=16)
# This should touch before saving
def save(self, *args, **kwargs):
self.name = re.sub(r'(.*)\({1}(.*)\){1}(.*)', r'\1\2\3', self.name)
super(Model, self).save(*args, **kwargs)
Update:
class Model(model.Model):
name = models.CharField(primary_key=True, max_length=16)
name_org = models.CharField(max_length=16)
# This should touch before saving
def save(self, *args, **kwargs):
self.name = re.sub(r'(.*)\({1}(.*)\){1}(.*)', r'\1\2\3', self.name)
self.name_org = self.name # original "t(es)t"
super(Model, self).save(*args, **kwargs)

How to change input fields while overriding a django save() method

Suppose I had this following code:
class MyModel(models.Model):
...
...
def save(self, *args, **kwargs):
# pre-save edits can go here...
super(MyModel, self).save(*args, **kwargs)
When I create and save a model, MyModel(blah, blah, blah), there is a possibility that
one of the input fields is "None". In the overridden save method, the goal is to check for if a field is none, and if one is, change it to some other default value.
Are the input fields in args or kwargs? And is overriding save() even the proper way to do this?
I was thinking something like this:
def save(self, *args, **kwargs):
if 'username' in args and args['username'] is None:
args['username'] = some_default_value
super(MyModel, self).save(*args, **kwargs)
So where are the input params? args* or **kwargs, thank you.
I think it's better a pre_save signal to see if a input value is None:
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
class MyModel(models.Model):
field1 = models.TextField()
field2 = models.IntegerField()
#receiver(pre_save, sender=MyModel)
def mymodel_save_handler(sender, instance, *args, **kwargs):
if instance.field1 is None or instance.field1 == "":
instance.field1 = default_value
If you prefer override save method you can access to the model fields with self
def save(self, *args, **kwargs):
if self.username is None:
self.username = some_default_value
super(MyModel, self).save(*args, **kwargs)

Categories