I'm writing tests for several object models and the following test isn't giving the expected result:
def test_image_on_delete(self):
image = Image(id=self.TEST_IMAGE_ID, name=self.TEST_IMAGE_NAME, src=self.TEST_IMAGE_SRC)
image.save()
category = Category.objects.get(id=self.TEST_ID)
category.image = Image.objects.get(id=self.TEST_IMAGE_ID)
Image.objects.get(id=self.TEST_IMAGE_ID).delete()
self.assertIsNone(category.image)
Here is the image field in Category:
image = ForeignKey(Image, on_delete=SET_NULL, null=True)
I've confirmed that the Image is being deleted, but the assertIsNone fails because the Category's image field doesn't get cleared.
Is there something wrong with the way I've written this test or the ForeignKey field?
Note: I'm using TestCase from django.test
You can refresh your object from the database:
category.image.delete()
# Reload the object from the database.
category.refresh_from_db()
self.assertIsNone(category.image)
Documentation:
https://docs.djangoproject.com/en/3.2/ref/models/instances/#refreshing-objects-from-database
I figured out the issue. on_delete modifies the SAVED version of my Category object, so the copy of the variable I have locally isn't changed.
Here's my modified test that passes:
def test_image_on_delete(self):
image = Image(id=self.TEST_IMAGE_ID, name=self.TEST_IMAGE_NAME, src=self.TEST_IMAGE_SRC)
image.save()
category = Category.objects.get(id=self.TEST_ID)
category.image = Image.objects.get(id=self.TEST_IMAGE_ID)
self.assertIsNotNone(category.image)
category.save()
Image.objects.get(id=self.TEST_IMAGE_ID).delete()
category = Category.objects.get(id=self.TEST_ID)
self.assertIsNone(category.image)
Related
here's what I have now:
im = get_object_or_404(Images, uid=uid)
next = get_object_or_404(Images, pk=im.pk+1)
but if I deleted one instance always the next will be 404 because of the pk, so how can I get directly the next?
You can use .get_next_by_FOO
For every DateField and DateTimeField that does not have null=True, the object will have get_next_by_FOO() and get_previous_by_FOO() methods, where FOO is the name of the field. This returns the next and previous object with respect to the date field, raising a DoesNotExist exception when appropriate.
For example, when your Images model has created_at with models.DateTimeField;
class Images(models.Model):
....
created_at = models.DateTimeField(auto_now_add=True)
So, you can use with;
img = get_object_or_404(Images, uid=uid)
next_img = img.get_next_by_created_at()
this is what worked for me:
next = Images.objects.filter(pk__gt=im.pk).order_by('pk').first()
I was using django-hitcont to count the views on my Post model. I am trying to get the most viewed post in my ListView using this query objects.order_by('hit_count_generic__hits') and it is working fine on SQLite but on PostgreSQL, it is giving me this error :
django.db.utils.ProgrammingError: operator does not exist: integer = text LINE 1: ...R JOIN "hitcount_hit_count" ON ("posts_post"."id" = "hitcoun....
models.py
class Post(models.Model, HitCountMixin):
author = models.ForeignKey(User, related_name='authors', on_delete=models.CASCADE)
title = models.CharField('Post Title', max_length = 150)
description = models.TextField('Description', max_length=1000, blank = True)
date_posted = models.DateTimeField('Date posted', default = timezone.now)
date_modifed = models.DateTimeField('Date last modified', default = timezone.now)
document = models.FileField('Document of Post', upload_to='documents', \
validators=[FileExtensionValidator(allowed_extensions = ['pdf', 'docx']), validate_document_size] \
)
hit_count_generic = GenericRelation(
HitCount,
object_id_field='object_pk',
related_query_name='hit_count_generic_relation'
)
views.py
queryset = Post.objects.order_by('hit_count_generic__hits')
I found this issue on Github related to the problem, but I am still not able to figure out the mentioned workaround.
When comparing different types (in this example integer and text), equals operator throws this exception. To fix that, convert HitCount model pk field to integer and you are good to go. To do that, you need to create and apply migration operation. Django is a really good framework to handle this kind of operations. You just need to check values are not null and are "convertable" to integer. Just change the field type and run two commands below.
python manage.py makemigrations
python manage.py migrate
Before updating your model, I highly recommend you to take a backup in case of failure. This is not an easy operation but you can follow the these links to understand what is going on during this the process.
migrations dump and restore initial data
If you don't care the data on table, just drop table and create a brand new migration file and recreate table.
I have a test in which I create a Profile model with factory_boy and a biography with value 'Starting biography'.
Then I get the form from the page and I fill it the bio field with 'Test Updated Bio' and I see that the response has the updated value, but the database has not.
When I get the profile page after update I have the 'Test Updated Bio' in the HTML and 'Starting biography' in the biography field of the Profile model.
class ProfileFactory(DjangoModelFactory):
class Meta:
model = 'profiles.Profile'
#user = SubFactory(UserFactory)
user = SubFactory('users.tests.factories.UserFactory', profile=None)
bio = 'Starting Bio'
The test function
def test_user_change_biography(self):
test_bio = 'Test Updated Biography'
form = self.app.get(
reverse('profiles:update'),
user=self.profile.user,
).form
form['bio'] = test_bio
tmp = form.submit().follow().follow()
print('\n\n\n', tmp, '\n\n\n')
print('\n\n\n', self.profile.__dict__, '\n\n\n')
self.assertEqual(self.profile.bio, test_bio)
I thought that there could be some caching mechanism but I don't know. Some ideas ?
I solved the problem by refreshing the object from the database:
self.profile.refresh_from_db()
self.assertEqual(self.profile.bio, test_bio)
I want to copy images from one model to another within the project. Suppose these are my models:
class BackgroundImage(models.Model):
user = models.ForeignKey(User)
image = models.ImageField(upload_to=get_upload_file_name)
caption = models.CharField(max_length=200)
pub_date = models.DateTimeField(default=datetime.now)
class ProfilePicture(models.Model):
user = models.ForeignKey(User)
image = models.ImageField(upload_to=get_upload_file_name)
caption = models.CharField(max_length=200)
pub_date = models.DateTimeField(default=datetime.now)
#classmethod
def create_from_bg(cls, bg_img):
img = cls(user=bg_img.user, image=bg_img.image, caption=bg_img.caption+'_copy', pub_date=bg_img.pub_date)
img.save()
return img
For now, I can do these:
To get the user
>>>m = User.objects.get(username='m')
To get the user's profile picture set
>>>m_pro_set = m.profilepicture_set.all()
>>>m_pro_set
[<ProfilePicture: pro_mik>]
Get an image object from Background image of the user
>>>m_back_1 = m.backgroundimage_set.get(id=2)
>>>m_back_1
<BackgroundImage: bg_mik>
And then:
>>>profile_pic = ProfilePicture.create_from_bg(m_back_1)
Now when I check it, it does create a new instance.
>>>m_pro_set
[<ProfilePicture: pro_mik>,<ProfilePicture: bg_mik>]
But, if I check on the path, and even on the media folder, its the same image and not an actual copy of the image file.
>>>profile_pic.image
<ImageFileField: uploaded_files/1389904144_ken.jpg>
>>>m_back_1.image
<ImageFileField: uploaded_files/1389904144_ken.jpg>
How do I go about, to actually copy the original image file within the models? Any help will be much appreciated! Thank you.
so I know this question is pretty old, but hopefully this answer help someone...
My approach for doing this, uploading the photo to the proper path for the suggested model, is:
from django.core.files.base import ContentFile
picture_copy = ContentFile(original_instance.image.read())
new_picture_name = original_instance.image.name.split("/")[-1]
new_instance.image.save(new_picture_name, picture_copy)
Please check that in my case, the new name is just the same file name, but to be updated in the new model image field's path. In your case, depending on what you have inside "get_upload_file_name" it could leads to the same path again (since is used in both classes). Also you can create a new, random name.
Hope this helps someone =)
Best & Short solution is that
existing_instance = YourModel.objects.get(pk=1)
new_instance.image = existing_instance.image
It's work fine for me.
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).