django update - update logic of already set mandatory fields - django

I have a problem when I update an object. This is the model:
class HourRecord(models.Model):
day_of_work = models.PositiveIntegerField(max_length=1)
date_of_work = models.DateField(verbose_name='creation date')
who_worked = models.ForeignKey(User)
project = models.ForeignKey(Project, related_name='hour_record_set', null=True)
created_by = models.ForeignKey(User, editable=False, related_name='hour_record_creator')
created = models.DateTimeField(auto_now_add=True, editable=False, verbose_name='creation date')
modified_by = models.ForeignKey(User, editable=False, related_name='hour_record_modifier')
modified = models.DateTimeField(auto_now=True, editable=False)
def save(self, request):
if not self.id:
self.who_worked = request.user
self.created_by = request.user
self.modified_by = request.user
super(HourRecord, self).save()
The created_by field should only be set if its a new object. Therefore the "if not self.id:". This worked fine till now.
Till now I updated the object like this:
if hrform.is_valid():
hour_record = hrform.save(commit=False)
hour_record.save(request)
But when I update the model like this I get an error:
project = Project.objects.get(id=project_pk)
hr_object = HourRecord(id=hour_record_pk, day_of_work=weekday, date_of_work=date, who_worked=request.user, project=project)
hr_object.save(request)
The error message is:
Column 'created_by_id' cannot be null
Thats strange to me since the created_by column has been already set. I checked it in the database. After this error message when I check again the object is updated and created_by_id is set to null. Actually this is strange. I get an error message + the row is updated.
The second approach is called from within a json data handling view. But I don't think that it has anything to do with it. I think I can circumvent this problem when I reset the created_by_id, but that is not what I want to do, since it corrupts the logic of this created_by field.

Well, the issue appears to be that you are explicitly setting the id field when you instantiate the HourRecord object:
hr_object = HourRecord(id=hour_record_pk,...
So when you get to the save() method, it checks if it has an ID... and it does, so it doesn't set who_worked and created_by.
I wonder why you need to set the ID. Normally, you should let the database set it automatically via the autoincrement.
(Not related to your issue, but your save method should accept the force_update and force_insert parameters, and pass them to the super method. The easiest way to do this is to get into the habit of always using *args, **kwargs when overriding a method.)
Edit: To answer your question of why this doesn't work like an update statement, it's because you're explicitly replacing the old object with a new one. In effect, you're deleting the old db entry and inserting a completely new set of data. There'd be no way for Python to know whether you intended to keep a field's old value, or set it to NULL, for example.
The way to do this, as you have noted, is to get the existing object and updating its attributes explicitly.

Related

Django - Create custom PrimaryKey with selected ForeignKey object

Problem:
I want to create a custom-PrimaryKey in ItemCode Model contain with selected-ForeignKey object. So, It's gonna change the custom-PrimaryKey every time submit a new ForeignKey field.
Here's my Model:
class ItemCategory(models.Model):
idItemCat = models.CharField(primary_key=True max_length=5)
nameCategory = models.CharField(max_length=150)
class ItemCode(models.Model):
idItemCode = models.CharField(primary_key=True, editable=False, max_length=20)
idItemCat = models.ForeignKey(ItemCategory, on_delete=models.DO_NOTHING)
To illustrate, here's the example of custom-PrimaryKey I wanna create: MN.202203.001
MN means Monitor from selected-ForeignKey object
Then, it change every time I submit new ForeignKey field, like this: CM.202203.002
CM means Camera from new selected-ForeignKey object
What I've tried:
I've tried to using request method but I don't know how to implemented it in my Model.
def CustomPK(request):
if request.method == 'POST':
category = ItemCategory.objects.get(pk=request.POST.get('idItemCat'))
return category
Question:
Is there anyway to get or fetch the object from selected-ForeignKey field to make a custom-PrimaryKey with request method? or is there any better way to do it?
If my question isn't clear enough, please let me know...

You are trying to add a non-nullable field stock without a default

I have added a new field in my model but after that I have deleted db.sqlite3 (to ensure I don't get error below)
agrawalo#:~/myapp> ls
README.md config core manage.py requirements.txt
But still I get this error when I run makemigrations
agrawalo#:~/myapp> ./manage.py makemigrations
You are trying to add a non-nullable field 'high52' to stock without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
class Stock(models.Model):
name = models.CharField(max_length=255)
code = models.CharField(max_length=20, db_index=True)
price = models.DecimalField(max_digits=19, decimal_places=2)
diff = models.DecimalField(max_digits=19, decimal_places=2)
open_price = models.DecimalField(max_digits=19, decimal_places=2)
previous_close = models.DecimalField(max_digits=19, decimal_places=2)
low52 = models.DecimalField(max_digits=19, decimal_places=2)
high52 = models.DecimalField(max_digits=19, decimal_places=2)
last_updated = models.DateTimeField()
objects = DataFrameManager()
def save(self, *args, **kwargs):
''' On save, update timestamps '''
self.last_updated = timezone.now()
return super(Stock, self).save(*args, **kwargs)
def __str__(self):
return str(self.code)
low52 and high52 are the newly added fields. Please note that none of the other existing field throw this error.
You can either provide a default value to the field
high52 = models.DecimalField(max_digits=19, decimal_places=2, default=0.0)
or you can make it optional
high52 = models.DecimalField(max_digits=19, decimal_places=2, null=True, blank=True)
You can make a decision based on your choice.
To answer your question about the error, the previously existing fields might have been created in the initial migration itself and they don't need a default value. But for the newly added field, you need a default value for mandatory fields and this default value will be populated in the existing records. This doesn't depend on whether you have deleted the existing database or not. This depends on the current state of migrations for that model. Since this is not the initial migration, you will need to provide a default value or make it optional.
It doesn't matter if you deleted the database file or not. makemigrations does not check the database.
You can only add a non-nullable field to a model if you add it to a new model and make an initial migration. This is because, after you make that initial migration, Django has no way of knowing whether you deployed your application somewhere else, so it has no way of knowing if there are instances of a model out there. A situation where this would go wrong:
Create a model X and makemigrations on your local machine.
Deploy your Django application to a server, where the database is populated with instances of model X.
Delete your local database, add non-nullable field Y to model X, makemigrations.
Deploy you Django application to the server.
Problems occur.
The solution here is to either:
Set the Field to null=True
Add a default to the model.
Provide a default when making the migrations.
In your situation, I would say it is ok to provide a one-off default, because it sounds like you have no populated database yet.
You need to provide blank and null True for high52 field .
high52 = models.SomeField(blank=True,null=True)
If you don't want so then you can select any of these two options.
For example If high52 is CharField then you can choose the 1 option and provide some value like '..' or you can set defaults in your models.py

Django framework form is trying to insert a new record instead of updating

Django 1.11.4 python 3.6
I have a default Django framework form I use for both update and create records for a given model. The primary key ("id" field) is generated by Django. The rest is defined in my model (see below). The model is subclassed from AuditModel class which overloads save method.
Everything works at this point, i.e. I can create new records or update existing records using standard Django forms interface.
class Product(AuditModel):
internal_id = models.CharField(max_length=100, null=True, blank=True, help_text="Internal ID")
external_id = models.CharField(max_length=100, null=False, blank=False, help_text="External ID", verbose_name="External ID")
label = models.ForeignKey(Label, help_text="Label")
class AuditModel(models.Model):
created = models.DateTimeField(null=True,editable=False)
updated = models.DateTimeField(null=True,editable=False)
def save(self, *args, **kwargs):
date = timezone.localtime()
if self._state.adding :
self.created = date
self.updated = date
super(AuditModel, self).save()
My question: I would like external_id to be unique (but not a primary key), i.e.
external_id = models.CharField(max_length=100, unique=True, null=False, blank=False, help_text="External ID", verbose_name="External ID")
Once I added unique=True to the definition of the external_id field, the behaviour of the view changes: on attempt to update existing record I get an error message next to external_id textbox "Product with this External ID already exists." and no change happens in the DB. Somehow presence of unique=True in the definition of the external_id field make Django to think that I am not editing an existing record but arrived to this form to create a new entry.
The url I arrived to the screen with is correct, i.e /product/<some id here>/change/, not /product/add
In the DB all the existing values in external_id field are non-null (no empty strings either) and unique.
If I understood correctly by adding some debug, the error "Product with this External ID already exists" happens BEFORE save() is even called, like unique=True invokes some Django data validator that happened to be unaware of the current action (update vs insert) and the view is just reloaded with an error.
Solved - The problem was caused by commented out line # instance._state.adding = False inside def from_db() method of a superclass. No idea why it got commented out.

Update multiple fields of member of an instance simultaneously

Suppose I have a User model and this model
class modelEmployer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
employer_image = models.ImageField(upload_to='images/', default='')
Now suppose i have an instance of modelEmployer and I would like to update the content of the user object in it. I know I can do this
instance.user.email = new value
instance.first_name = new value
instance.save()
I read we can run an update on a queryset (even if it returns one object). Now suppose I have a dictionary like this
dict = {"first_name : "John","last_name" : "deer",....}
How can i do something like this
modelEmployer.object.filter(instance.user.email=dict["email"]).update(only update the user objects as I would like to update the user object of this field using directly the dictionary. Any suggestions ?
You can use explicitly mention the relation and do it,
dict = {"user__first_name" : "John","user__last_name" : "deer",....}
And in the ORM, do as
modelEmployer.object.filter(instance.user.email=dict["email"]).update(**dict)
Hope this will solve your issue

In Django filtering on __isnull seems to result in LIKE 'None'

I have a model with the following field
class Callstat(models.Model):
agentdisplayname = models.CharField(null=True, max_length=60, db_column='AgentDisplayName', blank=True)
class Meta:
db_table = u'callstat'
def __unicode__(self):
return u'%d: %s' % (self.callid, self.telnum)
However when I do the following,
call = Callstat.objects.all().filter(agentdisplayname__isnull=True)
MySQL's general_log shows the resulting SQL as
SELECT `callstat`.`AgentDisplayName` FROM `callstat` WHERE `callstat`.`AgentDisplayName` LIKE 'None';
The data in the database is added by a separate application and I have no control over the fact that it is leaving fields as NULL. I'm fairly certain this can't be a bug in Django so any help as to what I am doing wrong would be greatly appreciated.
EDIT: OK turns out I'd missed something further down in my code, and this filter had been overwritten by a __exact filter in a loop.
Set null=True on your column. By default null parameter it is set to false, which means NULL is not a permitted value.
agentdisplayname = models.CharField(null=True, max_length=60, db_column='AgentDisplayName', blank=True, null=True)
Probably you would need to change the existing db column as well.