Django Save Override Throwing Primary Duplicate Errors - django

So, I have a model called ScheduleItem
class ScheduleItem(models.Model):
agreement = FK
location = FK
start = models.DateTimeField()
end = models.DateTimeField()
totalHours = DecimalField
def get_total_hours(self):
start = timedelta(hours=self.start.hour, minutes=self.start.minute)
end = timedelta(hours=self.end.hour, minutes=self.end.minute)
td = (end-start).seconds
totalHours=Decimal(td/Decimal(60)/Decimal(60))
return totalHours
def save(self,*args,**kwargs):
if self.pk == None:
super(ScheduleItem,self).save(self,*args,**kwargs)
self.refresh_from_db() # to access the datetime values, rather than unicode POST
self.totalHours = self.get_total_hours()
else:
self.totalHours = self.get_total_hours()
super(ScheduleItem,self).save(self,*args,**kwargs)
This throws PRIMARY key errors. I get duplicate entries with the second super(ScheduleItem,self). I cannot for the life of me figure out how to check for pk to access the datetime value and then save again within the save override method. I've tried moving things around, I've tried saving within the get_total_hours() function, with nothing but trouble.
I just want the object to be committed to the db so I can get the datetime objects and then calculate the total hours.
I'd rather not convert to datetime within the save function.
Does anyone have any tip or can anyone tell me where I'm going wrong?

You should not pass self to save(). You're calling super().save() as a bound method on an instance, so self is implicitly passed as the first argument. Change it to this:
def save(self,*args,**kwargs):
if self.pk is None:
super(ScheduleItem,self).save(*args,**kwargs)
self.refresh_from_db() # to access the datetime values, rather than unicode POST
self.totalHours = self.get_total_hours()
else:
self.totalHours = self.get_total_hours()
super(ScheduleItem,self).save(*args,**kwargs)
You get this weird behaviour because the first positional argument is force_insert, and the model instance evaluates to True. The second call to super().save() tries to force an insert with the same pk you previously saved.

Related

Passing model fields into functions as parameters to create other fields django

I following up with my question Creating new objects under specific parameters . I was trying to find out how to write functions into my django models to create default values on fields. I know how to do that, but now would like to take it a step further by adding parameters to the functions and using previous fields as the parameters. So far I have tried the following and each have given their own error messages when I try and migrate them.
class NewTest(models.Model):
score = models.IntegerField(default=0)
def num(score): # Want the score parameter to be the score field
new_num = score
return new_num
last_score = models.IntegerField(default=num(score))
Error: int() argument must be a string, a bytes-like object or a number, not 'IntegerField'
class NewTest(models.Model):
score = models.IntegerField(default=0)
def num(self):
new_num = self.score
return new_num
last_score = models.IntegerField(default=num(self))
NameError: name 'self' is not defined
class NewTest(models.Model):
score = models.IntegerField(default=0)
def num():
new_num = self.score
return new_num
last_score = models.IntegerField(default=num)
NameError: name 'self' is not defined
Does anybody know how to do this, or know where there is documentation on this?
Django's model field default callable can't actually be an instance method. Due to how this callable is used in migrations it must be serializable for migrations, e.g. in the migrations you'll see a reference to the path of the function, such as default="my_app.models.my_callable_default".
The way I've seen other solve this is to override the models save method to check those fields and set the default value there.
class NewTest(models.Model):
score = models.IntegerField(default=0)
last_score = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if not self.pk: # Check for create
self.last_score = self.score
else:
# Get the existing score from the database, because this instance has been modified
score_from_db = NewTest.objects.values("score").get(pk=self.pk)["score"]
self.last_score = self.score
return super().save(*args, **kwargs) # Continue with save as normal
This solution overrides the save method of your model to assign another fields value to the last_score field during creation, and if it's an update it will pull the existing object from the database to get the last score (I'm not sure if this is what you are after). The if not self.pk is used to check if the current save is being called for a new object (create), or an existing object (update), because the database assigns the primary key during creation.

Django db.connection.cursor.fetchone() returns unexpected result

I have this manager in my models.py
class ItemManager(models.Manager):
def get_fee(self):
from django.db import connection
cursor = connection.cursor()
cursor.execute("""
SELECT fee
FROM item
WHERE itemID = %d AND item.type = %d
""", [self.myItemID, self.myItemType])
fee = cursor.fetchone()
return fee
and class
Sample(models.Model):
sampleID = models.AutoField(primary_key=True)
itemID = models.ForeignKey(Item)
item.type = models.ForeignKey(Item)
...
def save(self, *args, **kwargs):
is_new = self.pk is None
super(Sample, self).save(*args, **kwargs)
if is_new:
cd.amount = MonthlyFeeManager()
cd.save()
Then it produces an error:
Cannot convert <myapp.models.ItemManager object at 0xa6f07ec> to Decimal
In general, i want to execute a RAW SQL query in a manager and use it to get the result from the query. I tried to search but most returns are tuples, not a value.
This is not how you use a manager. Even with a perfectly normal class instance, your attempt wouldn't give you what you wanted: you would need to instantiate it and call get_fee() on the instance.
With a manager, you don't need to instantiate it, because that's already done for you as the objects attribute on the model. But you still need to call the relevant method:
cd.amount = Sample.objects.get_fee()
However, this still won't work. That's because you've referred to self.myItemId and self.myItemType, which don't exist on the Manager object. Which leads me to the conclusion that you don't want a Manager object at all: you just want a standard model method. And there's no need for the raw SQL, either: your code is perfectly easily expressed using normal model query syntax.
(I can't show you exactly what it would look like, because the ForeignKeys in your example don't make any sense, and it's not clear where fee is supposed to be coming from.)

Django : Validate data by querying the database in a model form (using custom clean method)

I am trying to create a custom cleaning method which look in the db if the value of one specific data exists already and if yes raises an error.
I'm using a model form of a class (subsystem) who is inheriting from an other class (project).
I want to check if the sybsystem already exists or not when i try to add a new one in a form.
I get project name in my view function.
class SubsytemForm(forms.ModelForm):
class Meta:
model = Subsystem
exclude = ('project_name')
def clean(self,project_name):
cleaned_data = super(SubsytemForm, self).clean(self,project_name)
form_subsystem_name = cleaned_data.get("subsystem_name")
Subsystem.objects.filter(project__project_name=project_name)
subsystem_objects=Subsystem.objects.filter(project__project_name=project_name)
nb_subsystem = subsystem_objects.count()
for i in range (nb_subsystem):
if (subsystem_objects[i].subsystem_name==form_subsystem_name):
msg = u"Subsystem already existing"
self._errors["subsystem_name"] = self.error_class([msg])
# These fields are no longer valid. Remove them from the
# cleaned data.
del cleaned_data["subsystem_name"]
return cleaned_data
My view function :
def addform(request,project_name):
if form.is_valid():
form=form.save(commit=False)
form.project_id=Project.objects.get(project_name=project_name).id
form.clean(form,project_name)
form.save()
This is not working and i don't know how to do.
I have the error : clean() takes exactly 2 arguments (1 given)
My model :
class Project(models.Model):
project_name = models.CharField("Project name", max_length=20)
Class Subsystem(models.Model):
subsystem_name = models.Charfield("Subsystem name", max_length=20)
projects = models.ForeignKey(Project)
There are quite a few things wrong with this code.
Firstly, you're not supposed to call clean explicitly. Django does it for you automatically when you call form.is_valid(). And because it's done automatically, you can't pass extra arguments. You need to pass the argument in when you instantiate the form, and keep it as an instance variable which your clean code can reference.
Secondly, the code is actually only validating a single field. So it should be done in a specific clean_fieldname method - ie clean_subsystem_name. That avoids the need for mucking about with _errors and deleting the unwanted data at the end.
Thirdly, if you ever find yourself getting a count of something, iterating through a range, then using that index to point back into the original list, you're doing it wrong. In Python, you should always iterate through the actual thing - in this case, the queryset - that you're interested in. However, in this case that is irrelevant anyway as you should query for the actual name directly in the database and check if it exists, rather than iterating through checking for matches.
So, putting it all together:
class SubsytemForm(forms.ModelForm):
class Meta:
model = Subsystem
exclude = ('project_name')
def __init__(self, *args, **kwargs):
self.project_name = kwargs.pop('project_name', None)
super(SubsystemForm, self).__init__(*args, **kwargs)
def clean_subsystem_name(self):
form_subsystem_name = self.cleaned_data.get("subsystem_name")
existing = Subsystem.objects.filter(
project__project_name=self.project_name,
subsytem_name=form_subsystem_name
).exists()
if existing:
raise forms.ValidationError(u"Subsystem already existing")
return form_subsystem_name
When you do form=form.save(commit=False) you store a Subsystem instance in the variable form but the clean method is defined in SubsystemForm. Isn't it?

Django: Adding objects to a related set without saving to DB

I'm trying to write an internal API in my application without necessarily coupling it with the database.
class Product(models.Model):
name=models.CharField(max_length=4000)
price=models.IntegerField(default=-1)
currency=models.CharField(max_length=3, default='INR')
class Image(models.Model):
# NOTE -- Have changed the table name to products_images
width=models.IntegerField(default=-1)
height=models.IntegerField(default=-1)
url=models.URLField(max_length=1000, verify_exists=False)
product=models.ForeignKey(Product)
def create_product:
p=Product()
i=Image(height=100, widght=100, url='http://something/something')
p.image_set.add(i)
return p
Now, when I call create_product() Django throws up an error:
IntegrityError: products_images.product_id may not be NULL
However, if I call p.save() & i.save() before calling p.image_set.add(i) it works. Is there any way that I can add objects to a related object set without saving both to the DB first?
def create_product():
product_obj = Product.objects.create(name='Foobar')
image_obj = Image.objects.create(height=100, widght=100, url='http://something/something', product=product_obj)
return product_obj
Explanation:
Product object has to be created first and then assign it to the Image object because id and name here is required field.
I am wondering why wouldn't you not require to make a product entry in DB in first case? If there is any specific reason then i may suggest you some work around?
EDIT: Okay! i think i got you, you don't want to assign a product to an image object initially. How about creating a product field as null is equal to true.
product = models.ForeignKey(Product, null=True)
Now, your function becomes something like this:
def create_product():
image_obj = Image.objects.create(height=100, widght=100, url='http://something/something')
return image_obj
Hope it helps you?
I got same issue with #Saurabh Nanda
I am using Django 1.4.2. When I read in django, i see that
# file django/db/models/fields/related.py
def get_query_set(self):
try:
return self.instance._prefetched_objects_cache[rel_field.related_query_name()]
except (AttributeError, KeyError):
db = self._db or router.db_for_read(self.model, instance=self.instance)
return super(RelatedManager,self).get_query_set().using(db).filter(**self.core_filters)
# file django/db/models/query.py
qs = getattr(obj, attname).all()
qs._result_cache = vals
# We don't want the individual qs doing prefetch_related now, since we
# have merged this into the current work.
qs._prefetch_done = True
obj._prefetched_objects_cache[cache_name] = qs
That 's make sese, we only need to set property _prefetched_objects_cache for the object.
p = Product()
image_cached = []
for i in xrange(100):
image=Image(height=100, widght=100, url='http://something/something')
image_cached.append(image)
qs = p.images.all()
qs._result_cache = image_cached
qs._prefetch_done = True
p._prefetched_objects_cache = {'images': qs}
Your problem is that the id isn't set by django, but by the database (it's represented in the database by an auto-incremented field), so until it's saved there's no id. More about this in the documentation.
I can think of three possible solutions:
Set a different field of your Image model as the primary key (documented here).
Set a different field of your Production model as the foreign key (documented here).
Use django's database transactions API (documented here).

Unique fields that allow nulls in Django

I have model Foo which has field bar. The bar field should be unique, but allow nulls in it, meaning I want to allow more than one record if bar field is null, but if it is not null the values must be unique.
Here is my model:
class Foo(models.Model):
name = models.CharField(max_length=40)
bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
And here is the corresponding SQL for the table:
CREATE TABLE appl_foo
(
id serial NOT NULL,
"name" character varying(40) NOT NULL,
bar character varying(40),
CONSTRAINT appl_foo_pkey PRIMARY KEY (id),
CONSTRAINT appl_foo_bar_key UNIQUE (bar)
)
When using admin interface to create more than 1 foo objects where bar is null it gives me an error: "Foo with this Bar already exists."
However when I insert into database (PostgreSQL):
insert into appl_foo ("name", bar) values ('test1', null)
insert into appl_foo ("name", bar) values ('test2', null)
This works, just fine, it allows me to insert more than 1 record with bar being null, so the database allows me to do what I want, it's just something wrong with the Django model. Any ideas?
EDIT
The portability of the solution as far as DB is not an issue, we are happy with Postgres.
I've tried setting unique to a callable, which was my function returning True/False for specific values of bar, it didn't give any errors, however seamed like it had no effect at all.
So far, I've removed the unique specifier from the bar property and handling the bar uniqueness in the application, however still looking for a more elegant solution. Any recommendations?
Django has not considered NULL to be equal to NULL for the purpose of uniqueness checks since ticket #9039 was fixed, see:
http://code.djangoproject.com/ticket/9039
The issue here is that the normalized "blank" value for a form CharField is an empty string, not None. So if you leave the field blank, you get an empty string, not NULL, stored in the DB. Empty strings are equal to empty strings for uniqueness checks, under both Django and database rules.
You can force the admin interface to store NULL for an empty string by providing your own customized model form for Foo with a clean_bar method that turns the empty string into None:
class FooForm(forms.ModelForm):
class Meta:
model = Foo
def clean_bar(self):
return self.cleaned_data['bar'] or None
class FooAdmin(admin.ModelAdmin):
form = FooForm
** edit 11/30/2015: In python 3, the module-global __metaclass__ variable is no longer supported.
Additionaly, as of Django 1.10 the SubfieldBase class was deprecated:
from the docs:
django.db.models.fields.subclassing.SubfieldBase has been deprecated and will be removed in Django 1.10.
Historically, it was used to handle fields where type conversion was needed when loading from the database,
but it was not used in .values() calls or in aggregates. It has been replaced with from_db_value().
Note that the new approach does not call the to_python() method on assignment as was the case with SubfieldBase.
Therefore, as suggested by the from_db_value() documentation and this example, this solution must be changed to:
class CharNullField(models.CharField):
"""
Subclass of the CharField that allows empty strings to be stored as NULL.
"""
description = "CharField that stores NULL but returns ''."
def from_db_value(self, value, expression, connection, contex):
"""
Gets value right out of the db and changes it if its ``None``.
"""
if value is None:
return ''
else:
return value
def to_python(self, value):
"""
Gets value right out of the db or an instance, and changes it if its ``None``.
"""
if isinstance(value, models.CharField):
# If an instance, just return the instance.
return value
if value is None:
# If db has NULL, convert it to ''.
return ''
# Otherwise, just return the value.
return value
def get_prep_value(self, value):
"""
Catches value right before sending to db.
"""
if value == '':
# If Django tries to save an empty string, send the db None (NULL).
return None
else:
# Otherwise, just pass the value.
return value
I think a better way than overriding the cleaned_data in the admin would be to subclass the charfield - this way no matter what form accesses the field, it will "just work." You can catch the '' just before it is sent to the database, and catch the NULL just after it comes out of the database, and the rest of Django won't know/care. A quick and dirty example:
from django.db import models
class CharNullField(models.CharField): # subclass the CharField
description = "CharField that stores NULL but returns ''"
__metaclass__ = models.SubfieldBase # this ensures to_python will be called
def to_python(self, value):
# this is the value right out of the db, or an instance
# if an instance, just return the instance
if isinstance(value, models.CharField):
return value
if value is None: # if the db has a NULL (None in Python)
return '' # convert it into an empty string
else:
return value # otherwise, just return the value
def get_prep_value(self, value): # catches value right before sending to db
if value == '':
# if Django tries to save an empty string, send the db None (NULL)
return None
else:
# otherwise, just pass the value
return value
For my project, I dumped this into an extras.py file that lives in the root of my site, then I can just from mysite.extras import CharNullField in my app's models.py file. The field acts just like a CharField - just remember to set blank=True, null=True when declaring the field, or otherwise Django will throw a validation error (field required) or create a db column that doesn't accept NULL.
You can add UniqueConstraint with condition of nullable_field=null and not to include this field in fields list.
If you need also constraint with nullable_field wich value is not null, you can add additional one.
Note: UniqueConstraint was added since django 2.2
class Foo(models.Model):
name = models.CharField(max_length=40)
bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
class Meta:
constraints = [
# For bar == null only
models.UniqueConstraint(fields=['name'], name='unique__name__when__bar__null',
condition=Q(bar__isnull=True)),
# For bar != null only
models.UniqueConstraint(fields=['name', 'bar'], name='unique__name__when__bar__not_null')
]
Because I am new to stackoverflow I am not yet allowed to reply to answers, but I would like to point out that from a philosophical point of view, I can't agree with the most popular answer tot this question. (by Karen Tracey)
The OP requires his bar field to be unique if it has a value, and null otherwise. Then it must be that the model itself makes sure this is the case. It cannot be left to external code to check this, because that would mean it can be bypassed. (Or you can forget to check it if you write a new view in the future)
Therefore, to keep your code truly OOP, you must use an internal method of your Foo model. Modifying the save() method or the field are good options, but using a form to do this most certainly isn't.
Personally I prefer using the CharNullField suggested, for portability to models I might define in the future.
The quick fix is to do :
def save(self, *args, **kwargs):
if not self.bar:
self.bar = None
super(Foo, self).save(*args, **kwargs)
This is fixed now that https://code.djangoproject.com/ticket/4136 is resolved. In Django 1.11+ you can use models.CharField(unique=True, null=True, blank=True) without having to manually convert blank values to None.
Another possible solution
class Foo(models.Model):
value = models.CharField(max_length=255, unique=True)
class Bar(models.Model):
foo = models.OneToOneField(Foo, null=True)
I recently had the same requirement. Instead of subclassing different fields, I chose to override the save() metod on my model (named 'MyModel' below) as follows:
def save(self):
"""overriding save method so that we can save Null to database, instead of empty string (project requirement)"""
# get a list of all model fields (i.e. self._meta.fields)...
emptystringfields = [ field for field in self._meta.fields \
# ...that are of type CharField or Textfield...
if ((type(field) == django.db.models.fields.CharField) or (type(field) == django.db.models.fields.TextField)) \
# ...and that contain the empty string
and (getattr(self, field.name) == "") ]
# set each of these fields to None (which tells Django to save Null)
for field in emptystringfields:
setattr(self, field.name, None)
# call the super.save() method
super(MyModel, self).save()
If you have a model MyModel and want my_field to be Null or unique, you can override model's save method:
class MyModel(models.Model):
my_field = models.TextField(unique=True, default=None, null=True, blank=True)
def save(self, **kwargs):
self.my_field = self.my_field or None
super().save(**kwargs)
This way, the field cannot be blank will only be non-blank or null. nulls do not contradict uniqueness
For better or worse, Django considers NULL to be equivalent to NULL for purposes of uniqueness checks. There's really no way around it short of writing your own implementation of the uniqueness check which considers NULL to be unique no matter how many times it occurs in a table.
(and keep in mind that some DB solutions take the same view of NULL, so code relying on one DB's ideas about NULL may not be portable to others)