Django 1.11 Image Gallery with Django Filer - python

Problem
I need to display a gallery of images on a product page. This worked when we were at Django 1.6, but have since upgraded to Django 1.11 (Big Process). I am now stuck at how to get this to work within the new environment. Right now clicking Add Image brings up a pop up where I can select the image, and the regions associated with it (US, Canada, Spain, Etc..), but after clicking "Save" The popup title changes to Popup closing... and never closes - also the image is not added to the gallery. The image I upload itself IS added to the rest of the Images within filer, however it is not added to the ProductGallery Model.
What I've Got
Django: 1.11.7
Django-Filer: 1.2.7
Django-Suit: 0.2.25
Vanilla-Views: 1.0.4
I have Product models, these products have a many to many relationship to a ProductGallery model like so:
class Product(models.Model):
gallery = models.ManyToManyField('products.ProductGallery')
The ProductGallery is supposed to house Images and Videos allowing for upload of either, and providing one list to iterate through on the front end for display purposes.
The ProductGallery is defined as:
class ProductGallery(models.Model):
limit = models.Q(app_label='media', model='image') | models.Q(app_label='media', model='video')
order = models.PositiveIntegerField(default=0)
content_type = models.ForeignKey(ContentType, limit_choices_to=limit)
object_id = models.PositiveIntegerField(db_index=True)
content_object = generic.GenericForeignKey('content_type', 'object_id')
class Meta:
ordering = ('order',)
def __str__(self):
return six.text_type(self.content_object)
where media.image is defined as: (I'll ignore video for the time being)
class Image(CountryInfoModel, models.Model):
image = FilerImageField(null=True, blank=True)
def __str__(self):
return str(self.image.name or self.image.original_filename)
I've got a view for Adding new Media like so:
class AddMedia(LoginRequiredMixin, StaffuserRequiredMixin, JsonRequestResponseMixin, GenericView):
require_json = True
def post(self, request, *args, **kwargs):
object_id = self.request_json["objectId"]
object_var = self.request_json["objectVarName"]
content_type_id = self.request_json["contentType"]
order = self.request_json["order"]
media_id = self.request_json["mediaId"]
media_type = self.request_json["mediaType"]
content_type = _get_content_type_or_404(content_type_id)
content_object = _get_object_or_404(content_type, object_id)
model_var = getattr(content_object, object_var)
try:
if media_type.lower() == "image":
obj = Image.objects.get(pk=media_id)
elif media_type.lower() == "video":
obj = Video.objects.get(pk=media_id)
else:
raise Http404("Invalid mediaType parameter: {0}".format(media_type))
media_item = model_var.create(content_object=obj)
media_item.order = order
media_item.save()
except model_var.model.DoesNotExist:
pass
return self.render_json_response({'message': "Order successfully updated"})
And I think thats all the pieces there are to this. I am lost on why when I click "save" the Image is not saved to the ProductGallery model at all. I'd be happy to provide more context if needed, and any help is very much appreciated.

Just in case anyone else comes across this and wants to know how it was fixed.
It turns out that some of the django-admin template functionality had been overwritten and was causing some issues.
Specifically this project had overwritten parts of the save button functionality. The function dismissAddRelatedObjectPopup used to be named dismissAddAnotherPopup
These functions were overwritten to provide the custom functionality outlined above with the ProductGallery. Django went to call the function, but this was throwing an error on the popup related to something called SelectBoxwhich then broke the ajax call that was needed to save the model correctly.
Hopefully this can help someone in the future!

Related

How to upload files into BinaryField using FileField widget in Django Admin?

I want to create a model Changelog and make it editable from Admin page. Here is how it is defined in models.py:
class Changelog(models.Model):
id = models.AutoField(primary_key=True, auto_created=True)
title = models.TextField()
description = models.TextField()
link = models.TextField(null=True, blank=True)
picture = models.BinaryField(null=True, blank=True)
title and description are required, link and picture are optional. I wanted to keep this model as simple as possible, so I chose BinaryField over FileField. In this case I wouldn't need to worry about separate folder I need to backup, because DB will be self-contained (I don't need to store filename or any other attributes, just image content).
I quickly realized, that Django Admin doesn't have a widget for BinaryField, so I tried to use widget for FileField. Here is what I did to accomplish that (admin.py):
class ChangelogForm(forms.ModelForm):
picture = forms.FileField(required=False)
def save(self, commit=True):
if self.cleaned_data.get('picture') is not None:
data = self.cleaned_data['picture'].file.read()
self.instance.picture = data
return self.instance
def save_m2m(self):
# FIXME: this function is required by ModelAdmin, otherwise save process will fail
pass
class Meta:
model = Changelog
fields = ['title', 'description', 'link', 'picture']
class ChangelogAdmin(admin.ModelAdmin):
form = ChangelogForm
admin.site.register(Changelog, ChangelogAdmin)
As you can see it is a bit hacky. You also can create you own form field be subclassing forms.FileField, but code would be pretty much the same. It is working fine for me, but now I'm thinking is there are better/standard way to accomplish the same task?
A better and more standard way would be to create a Widget for this type of field.
class BinaryFileInput(forms.ClearableFileInput):
def is_initial(self, value):
"""
Return whether value is considered to be initial value.
"""
return bool(value)
def format_value(self, value):
"""Format the size of the value in the db.
We can't render it's name or url, but we'd like to give some information
as to wether this file is not empty/corrupt.
"""
if self.is_initial(value):
return f'{len(value)} bytes'
def value_from_datadict(self, data, files, name):
"""Return the file contents so they can be put in the db."""
upload = super().value_from_datadict(data, files, name)
if upload:
return upload.read()
So instead of subclassing the whole form you would just use the widget where it's needed, e.g. in the following way:
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.BinaryField: {'widget': BinaryFileInput()},
}
As you already noticed the code is much the same but this is the right place to put one field to be handled in a specific manner. Effectively you want to change one field's appearance and the way of handling when used in a form, while you don't need to change the whole form.
Update
Since writing that response Django has introduced an editable field on the models and in order to get this working you need to set the model field to editable=True which is false for BinaryField by default.
An alternative solution is to create a file storage backend that actually saves the binary data, along with file name and type, in a BinaryField in the database. This allows you to stay within the paradigm of FileField, and have the metadata if you need it.
This would be a lot more work if you had to do it yourself, but it is already done in the form of db_file_storage.
I actually followed #Ania's answer with some workaround since upload.read() was not saving image in the right encoding in Postrgres and image could not be rendered in an HTML template.
Furthermore, re-saving the object will clear the binary field due to None value in the uploading field (Change) [this is something that Django handles only for ImageField and FileField]
Finally, the clear checkbox was not properly working (data were deleted just because of the previous point, i.e. None in Change).
Here how I changed value_from_datadict() method to solve:
forms.py
class BinaryFileInput(forms.ClearableFileInput):
# omitted
def value_from_datadict(self, data, files, name):
"""Return the file contents so they can be put in the db."""
#print(data)
if 'image-clear' in data:
return None
else:
upload = super().value_from_datadict(data, files, name)
if upload:
binary_file_data = upload.read()
image_data = base64.b64encode(binary_file_data).decode('utf-8')
return image_data
else:
if YourObject.objects.filter(pk=data['pk']).exists():
return YourObject.objects.get(pk=data['pk']).get_image
else:
return None
Then I defined the field as a BinaryField() in models and retrieved the image data for the frontend with a #property:
models.py
image = models.BinaryField(verbose_name='Image', blank = True, null = True, editable=True) # editable in admin
#property
def get_image(self):
'''
store the image in Postgres as encoded string
then display that image in template using
<img src="data:image/<IMAGE_TYPE>;base64,<BASE64_ENCODED_IMAGE>">
'''
image_data = base64.b64encode(self.image).decode('utf-8')
return image_data
And finally is rendered in the template with:
yourtemplate.html
<img src="data:image/jpg;base64,{{object.get_image}}" alt="photo">
For modern Django, I found the following approach works best for me:
class BinaryField(forms.FileField):
def to_python(self, data):
data = super().to_python(data)
if data:
data = base64.b64encode(data.read()).decode('ascii')
return data
class BinaryFileInputAdmin(admin.ModelAdmin):
formfield_overrides = {
models.BinaryField: {'form_class': BinaryField},
}
The individual model field still needs editable=True, of course.

An efficient way to save parsed XML content to Django Model

This is my first question so I will do my best to conform to the question guidelines. I'm also learning how to code so please ELI5.
I'm working on a django project that parses XML to django models. Specifically Podcast XMLs.
I currently have this code in my model:
from django.db import models
import feedparser
class Channel(models.Model):
channel_title = models.CharField(max_length=100)
def __str__(self):
return self.channel_title
class Item(models.Model):
channel = models.ForeignKey(Channel, on_delete=models.CASCADE)
item_title = models.CharField(max_length=100)
def __str__(self):
return self.item_title
radiolab = feedparser.parse('radiolab.xml')
if Channel.objects.filter(channel_title = 'Radiolab').exists():
pass
else:
channel_title= radiolab.feed.title
a = Channel.objects.create(channel_title=channel_title)
a.save()
for episode in radiolab.entries:
item_title = episode.title
channel_title = Channel.objects.get(channel_title="Radiolab")
b = Item.objects.create(channel=channel_title, item_title=item_title)
b.save()
radiolab.xml is a feed I've saved locally from Radiolab Podcast Feed.
Because this code is run whenever I python manage.py runserver, the parsed xml content is sent to my database just like I want to but this happens every time I runserver, meaning duplicate records.
I'd love some help in finding a way to make this happen just once and also a DRY mechanism for adding different feeds so they're parsed and saved to database preferably with the feed url submitted via forms.
If you don't want it run every time, don't put it in models.py. The only thing that belongs there are the model definitions themselves.
Stuff that happens in response to a user action on the site goes in a view. Or, if you want this to be done from the admin site, it should go in the admin.py file.

how to handle imagefield update in django form

I have following models for category
class Category(models.Model):
name = models.CharField(max_length=30)
is_active=models.BooleanField()
photo=models.ImageField(upload_to='category')
def __unicode__(self):
name = str(self.name)
return name
def image(self):
return self.photo or 'DEFAULT_PIC.jpg'
class Meta:
permissions = (
('category','Category'),
('view_category', 'View category'),
)
My form class is as follows
class categoryForm(forms.Form):
name = forms.CharField(widget=forms.TextInput(attrs={'class':'box'}),max_length=32,label='Category',required=True)
is_active = forms.BooleanField(required=False)
id = forms.CharField(widget=forms.HiddenInput,required=False)
photo = forms.FileField(
required=False,
label='Select an Image',
help_text='max. 4 megabytes'
)
It is working perfect for insert and update both, now i am trying to load the image which has already been upload in the form (say i have already added one category with image , now i want to upload another image, so i am trying to show the preview of previously uploaded image)
I passed the data in the view and tried few hack in forms
formdata = categoryForm({'name':p.name,'is_active':p.is_active,'id':p.id,'photo':p.image()})
In form i made modification as below
def __init__(self, data=None, **kwargs):
# pass
super(categoryForm, self).__init__(data, **kwargs)
if self.data['photo']!='':
self.fields['uploaded_photo'] =forms.CharField(widget=forms.TextInput,required=False,label=mark_safe('<img src="/media/%s" height="100">'%(self.data['photo'])))
#self.fields['uploaded_photo'].widget.attrs['value']=self.data['photo']
Now it is perfectly showing previously uploaded image on label .
After that i tried to upload another image but it shows following error
TypeError at /update/category/
init() takes at most 2 arguments (3 given)
Script to handle image upload is as follows
formdata = categoryForm(request.POST,request.FILES)
if formdata.is_valid():
cd = formdata.cleaned_data
p1=Category()
p1.id=cd['id']
p1.name=cd['name']
p1.is_active=cd['is_active']
p1.photo=cd['photo']
p1.save()
Please let me know what is happening here
Try making life easy by using the ImageField
class ImageField(**kwargs)¶
Default widget: ClearableFileInput
Empty value: None
Normalizes to: An UploadedFile object that wraps the file content and file name into a single object.
Validates that file data has been bound to the form, and that the file is of an image format understood by PIL.
Error message keys: required, invalid, missing, empty, invalid_image
Using an ImageField requires that the Python Imaging Library is installed.
When you use an ImageField on a form, you must also remember to bind the file data to the form.
Check out the complete documentation here

Way to allow for duplicate many-to-many entries in Python/Django

I have the following Django model:
class Icon(models.Model):
name = models.CharField(max_length=200,null=False,blank=False)
class Post(models.Model):
icons = models.ManyToManyField(Icon)
When I write the following code:
post = Post()
icons = []
icon_id = form.cleaned_data['icon_1']
if (icon_id):
i = Icon.objects.get(id=icon_id)
icons.append(i)
icon_id = form.cleaned_data['icon_2']
if (icon_id):
i = Icon.objects.get(id=icon_id)
icons.append(i)
post.icons = icons
post.save()
It works fine for the most part, creating a Post object and the two Icon objects.
However, if the icon_id is, say, 1 in both cases, it only creates ONE entry in the database, not two.
So it seems like it checks for duplicates and removes them.
How do I make this work so I allow duplicates? (I want two of the SAME icon associated with a post.)
Thanks!
Define the model yourself, to have such non-unique many-to-many relations
class PostIcon(models.Model):
post = models.ForeignKey(Post)
icon = models.ForeignKey(Icon)
and than add them one by one
for icon in icons:
PostIcon(post=post, icon=icon).save()
or pass that model as through argument of ManyToManyField e.g.
class Post(models.Model):
icons = models.ManyToManyField(Icon, through=PostIcon)
or alternatively you can have a count associated with PostIcon instead of having multiple rows, if that serves the use-case e.g. you may want a badge to be displayed 10 times
class PostIcon(models.Model):
post = models.ForeignKey(Post)
icon = models.ForeignKey(Icon)
count = models.IntegerField()

Database modeling in django

I have a situation I don´t know how to model correctly. I want every child of a class to be associated with a media object (photo, video or music). I want to know which is the preffered approach to this problem. What I have right now is:
class Something(models.Model):
media = models.ForeignKey(Media)
class Media(models.Model):
title = models.CharField(max_lenght=100)
def get_tiny_object():
pass
def get_big_object():
pass
class Picture(Media):
picture = models.ImageField()
def get_tiny_object():
return ...
...
class Video(Media):
video = models.CharField(max_length=200) #youtube id
...
class Music(Media):
music = ....
You get the idea. ¿Does this work? Should I also record on "Something" what kind of media it is?
EDIT:
The idea behind having a Media class, is that I can render in the templates without knowing which kind of media I´m rendering. get_tiny_object() should return, if it is a picture:
"<img style="width:60px; height: 50px" ...>"
So if I have a foreign key to a media object lets say it's id=4, does django know that it should be fetched from music table, if the object I associated with is of Music kind? Because I´ll have 3 different id=4, one on each table (picture, video and music (and possibly more if the domain changes)).
I still think the question is a little hard to understand - the title is Database modelling in Django after all...However, this is a perfectly valid and reasonable thing to do, and seems to fit your intent:
The recommended way to do this is multi table inheritance - example:
class Media(models.Model):
pass
class Something(models.Model):
media = models.ForeignKey(Media)
class Picture(Media):
foo = models.CharField(max_length=100)
def get_tiny_object(self):
return self.foo
class Video(Media):
bar = models.CharField(max_length=100)
def get_tiny_object(self):
return self.bar
picture = Picture.objects.create(foo='some picture')
video = Video.objects.create(bar='some video')
something1 = Something.objects.create(media=picture)
something2 = Something.objects.create(media=video)
print something1.media.get_tiny_object() # this is a picture remember?
print something2.media.get_tiny_object() # and lo, here is a video
http://docs.djangoproject.com/en/dev/topics/db/models/#one-to-one-relationships
this says you should use a OneToOneField relating each Picture, Video, Music to a Media entry, and they all inherit directly from models.Model.
Also, you need to change your Something to
class Something(models.Model):
media = models.ForeignKey("Media")
because since Media isn't defined when you define Something (if you place it below it), it won't work, django provides you with the possibility of doing this with the classname instead of a reference to the class

Categories