The goal with this is for the user to create an instance of the model where they populate a source URLField, and on save, it would fetch the URL and save it as a file.
class ComicImage(UUIDModel):
src = models.URLField('Comic URL', max_length=512, blank=True)
img = models.FileField('Images', null=True, blank=True, upload_to='comics')
def save(self, *args, **kwargs):
img_data = BytesIO(requests.get(self.src).content)
self.img.save(f'{self.hash}-i.webp', content=img_data)
super().save(*args, **kwargs)
But I keep getting an error
ValueError: The 'img' attribute has no file associated with it.
Which appears that it's not assigning a file to the field. Or rather, it doesn't until AFTER the model is saved.
Alternatively:
I also tried
self.img.save(f'{self.hash}-i.webp', content=img_data, save=True)
But it gets stuck in a loop of saving forever. If I set save=False, then it creates the model but not the file.
Is there a way to create/populate/modify the file before it's saved?
Update:
For anyone curious, I figured out what and why and how to fix it.
In my view I had:
fields = ['html', 'tags', 'title', 'text', 'taken_date', 'image']
And am using {{ form.as_p }} in my template. Apparently once that gets posted from the form it really, really doesn't want anything else touching the form fields that wasn't already in the form.
So I took out the 'tags' field from my view and it works.
Thanks to everyone that responded.
Original Question:
Using Django 2.0.1 and PostgreSQL 9.2.18
I'm writing a simple photogallery application. In it I have a photo object and PhotoTag object. The photo can have many tags and the tags can be associated with many photos, thus it needing to be a ManyToManyField.
Upon save of the submitted photo, a post_save receiver calls functions to make thumbnails (which work fine) and a function to update tags.
The photo gets saved fine, update_tags gets called fine, tags get read from the photo fine, tags get saved into PhotoTag fine. But the manytomany table tying the two together does not get the new rows inserted. Unless the code exits abnormally during either the update_tags function or the post_save receiver function, thumbs after update_tags is called.
I've even tried using a connection.cursor to write directly into the m2m table and it has the same behavior.
If I try to call save() on the Photo object again, I just get into an infinite loop due to the post_save signal.
I'm baffled as to what is going on. Any clues?
# models.py
def update_tags(instance):
tags = get_tags(instance.image)
# Set initial values
pt = []
tagid = ''
photoid = instance.id
# Loop through tag list and insert into PhotoTag and m2m relation
for x in range(0, len(tags)):
# Make sure this tag doesn't already exist
if PhotoTag.objects.filter(tag_text=tags[x]).count() == 0:
pt = PhotoTag.objects.create(tag_text=tags[x])
tagid = PhotoTag.objects.latest('id').id
instance.tags.add(pt)
else:
# Only working with new tags right now
pass
return
class Photo(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
title = models.CharField(max_length=200, null=True, blank=True)
text = models.TextField(null=True, blank=True)
html = models.BooleanField(default=False)
filename = models.CharField(default='', max_length=100, blank=True,
null=True)
image = models.ImageField(upload_to=upload_path)
location = models.CharField(max_length=100, blank=True, null=True)
entry_date = models.DateTimeField(default=timezone.now)
taken_date = models.DateTimeField(blank=True, null=True)
tags = models.ManyToManyField(PhotoTag, blank=True)
#receiver(post_save, sender=Photo)
def thumbs(sender, instance, **kwargs):
"""
Upon photo save, create thumbnails and then
update PhotoTag and m2m with any Exif/XMP tags
in the photo.
"""
mk_thumb(instance.image, 'mid')
mk_thumb(instance.image, 'th')
mk_thumb(instance.image, 'sm')
update_tags(instance)
return
-------------
From views.py
-------------
class PhotoCreate(LoginRequiredMixin, CreateView):
model = Photo
template_name = 'photogallery/photo_edit.html'
fields = ['html', 'tags', 'title', 'text', 'taken_date', 'image']
def get_initial(self):
self.initial = {'entry_date': timezone.now()}
return self.initial
def form_valid(self, form):
form.instance.author = self.request.user
return super(PhotoCreate, self).form_valid(form)
Update:
def save(self, mkthumb='', *args, **kwargs):
super(Photo, self).save(*args, **kwargs)
if mkthumb != "thumbs":
self.mk_thumb(self.image, 'mid')
self.mk_thumb(self.image, 'th')
self.mk_thumb(self.image, 'sm')
self.update_tags()
mkthumb = "thumbs"
return
I had a similar issue where I was trying to add a group when a user instance was saved.
The anwer why this is happening is at the docs and more explicitly (using code) at this ticket.
When saving a ModelForm() (hitting save in the admin), first an instance of the object is saved, then all its signals are triggered etc. The third step is to save all m2m relations using ModelForm().cleaned_data. If ModelForm().cleaned_data['tags'] is None, all the relations created from your signal, will be deleted.
A hackish solution, is to use a post_save signal with transaction.on_commit() which will execute the relevant code after the existing transaction (which includes the procedure of saving all m2m relations) is committed to the database.
def on_transaction_commit(func):
''' Create the decorator '''
def inner(*args, **kwargs):
transaction.on_commit(lambda: func(*args, **kwargs))
return inner
#receiver(post_save, sender=Photo)
#on_transaction_commit
def tags(instance, raw, **kwargs):
"""
Create the relevant tags after the transaction
of instance is committed, unless the database is
populated with fixtures.
"""
if not raw:
update_tags(instance)
A more sound solution if your many to many relation has not blank=True is to use a m2m_changed() signal, as explained in this post or the before mentioned ticket.
The best of all, is to ditch the signals and override the ModelForm().clean() method for the case where a ModelForm() is used, and also override the Model().save() method in case the model is directly saved.
A ModelForm().instance.my_flag will be useful so you can check for an existing Model().my_flag in Model().save() to avoid accessing twice the database.
override your save method like
def save(self, *args, **kwargs):
tags = get_tags(self.image)
# Set initial values
pt = None
# Loop through tag list and insert into PhotoTag and m2m relation
for x in range(0, len(tags)):
# Make sure this tag doesn't already exist
if PhotoTag.objects.filter(tag_text=tags[x]).count() == 0:
pt = PhotoTag.objects.create(tag_text=tags[x])
self.tags.add(pt)
else:
# Only working with new tags right now
pass
super(Photo, self).save(*args, **kwargs)
I'm stuck using a non-Django legacy MySQL database. I need to write code that generates a unique filename each time a model object is saved. The following doesn't work. It won't overwrite the filename field. It saves fine with whatever the filename field was set to. Is this because that field is set as the primary key?
(I realize my code isn't creating a random filename--haven't gotten that far yet. Also, I know this will only save once since it needs to be unique, but it won't even save the first time).
class Agenda(models.Model):
type = models.IntegerField()
filename = models.CharField(max_length=45, primary_key=True)
date = models.DateField()
class Meta:
managed = False
db_table = 'gbminutes'
def save(self, *args, **kwargs):
self.filename = 'ATESTFILE'
super(Agenda, self).save(*args, **kwargs)
I have a model for Document like :
class Document(models.Model):
document_name = models.CharField(max_length=64)
author = models.ForeignKey(Client, null=False, default=1)
size = models.IntegerField(default=0)
version = models.CharField(max_length=64)
file = models.FileField(upload_to='documents/%Y/%m/%d')
but I also want to upload that file. My form looks like
class UploadDocumentForm(ModelForm):
class Meta:
model = Document
fields = ('document_name', 'author', 'size', 'version', 'file',)
def __init__(self, *args, **kwargs):
super(UploadDocumentForm, self).__init__(*args, **kwargs)
Now obviously, I want my file to be saved in a folder and not have its own column i nthe db. In db I only want the other data. Problem is, django tries to persist it and crashes with
The above exception (table document has no column named file)
Is there a way to make file a non persisted field or something?
Im following this tutorial, https://simpleisbetterthancomplex.com/tutorial/2016/08/01/how-to-upload-files-with-django.html
I tried removing file form the model and adding it just in the form somehow but it doesn't work. It is not recognized liek that. I just want it to not be persisted in the sqlite database. Any ideas?
I thought about making a second model without the file and create somehow both of them or override the save method.. Im not sure how to make it work.
I am working with Python 2.7, Django 1.9 and sorl.thumbnail.
I cannot manage to create a view to delete in one time the original picture file, the Picture entry in the database, the thumbnail pictures generated by sorl.thulbnail, and the thumbnail_kvstore entry in the database.
Here is my Picture model:
class Picture(models.Model):
file = models.ImageField(upload_to="pictures")
slug = models.SlugField(max_length=100, blank=True)
user = models.ForeignKey(User, null=True, blank=True)
exiflnglat = models.PointField(dim=3, geography=True, blank=True, null=True)
objects = models.GeoManager()
def __str__(self):
return self.slug
#models.permalink
def get_absolute_url(self):
return ('upload-new', )
And here is my view:
from sorl.thumbnail import delete
def deletepicnthumbs(request, pk):
allpicfromuser = Picture.objects.filter(user=request.user)
pictodelete = allpicfromuser.get(id=pk)
delete(pictodelete)
return redirect(adddetails)
This view does not delete anything.. What I am doing wrong ?
Thanks a lot
It seems that you are trying to use the delete method from sorl to delete an instance of the Picture model instead of the picture itself.
This code should work :
from sorl.thumbnail import delete
def deletepicnthumbs(request, pk):
pictodelete = Picture.objects.get_object_or_404(id=pk, user=request.user)
# Delete the thumbnails, as well as the original image
# If you want to keep the original image, pass ```delete_file=False```
delete(pictodelete.file)
# We use Django's method to delete the Picture instance, if needed
pictodelete.delete()
return redirect(adddetails)
Hope this helps,
Adela