I am building a web page where a blog author can write content and upload images.
Using an image field, I am allowing the author to upload multiple images and with some Javascript logic, I am displaying the images before upload. Under each image, I have a checkbox that indicates if that is the main image of the post (e.g. the first to show in the slideshow and used as thumbnail of the post itself).
On this page I am showing two forms with a shared submit button and it works fine.
My problems begin when I try to save the image and also indicate if it is the main one.
My images model has multiple helpful methods, thumbnail property for Django admin and a save method that will set all images for a post to false, before editing/adding a new main_image. I had to include a commit argument logic because of my experiments. Please ignore it. The model includes 3 fields: the image, the main flag and a foreign key to the post as follows:
class PostImages(models.Model):
"""
Post images for the blog app.
"""
image = models.ImageField(
verbose_name=_("Изображение"),
upload_to='blog/images/',
blank=False,
null=False,
default='blog/images/default.jpg'
)
post = models.ForeignKey(
to=Post,
on_delete=models.CASCADE,
verbose_name=_('Статия')
)
main_image = models.BooleanField(
default=False,
verbose_name=_('Основно изображение')
)
class Meta:
verbose_name = _('Изображение')
verbose_name_plural = _('Изображения')
permissions = [
('can_manage_post_image', _('Може да управлява изображенията на публикациите')),
('can_add_post_image', _('Може да добавя изображения към публикациите')),
]
def __str__(self):
return f"{self.post.title} - {self.image}"
def get_absolute_url(self):
return f"/blog/post/image/{self.id}"
def get_edit_url(self):
return f"/blog/post/image/{self.id}/edit"
def get_delete_url(self):
return f"/blog/post/image/{self.id}/delete"
def get_image(self):
return mark_safe(f'<img src="{self.image.url}" width="100" height="100" />')
def get_image_url(self):
return self.image.url
#property
def thumbnail_preview(self):
if self.image:
return mark_safe('<img src="{}" width="400" height="auto" />'.format(self.image.url))
return ''
def save(self, *args, **kwargs):
if self.main_image and kwargs.get("commit", True):
PostImages.objects.filter(post=self.post).update(main_image=False)
kwargs.pop("commit", None)
super().save(*args, **kwargs)
I am also using a Model form. In the model form I do not ask for the post, because it is not yet created. My idea was to fill the post in the save phase following a process of:
save post
save images
For me this is a logical move, but knowing how much time it costed me so far -> probably not the best move.
class PostImagesForm(forms.ModelForm):
"""
Form for uploading images for posts.
"""
image = forms.ImageField(
label='Снимка',
widget=forms.ClearableFileInput(
attrs={
'class': 'form-control',
'multiple': True,
}
)
)
main_image = forms.BooleanField(
label='Основна снимка',
required=False,
widget=forms.CheckboxInput(
# attrs={
# 'class': 'form-control',
# }
)
)
class Meta:
model = PostImages
fields = ['image', 'main_image']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['image'].required = False
def save(self, commit=True):
"""
Save the form and return the created or edited post.
"""
post_image = super().save(commit=False)
post_image.save(commit=commit)
return post_image
def clean(self):
"""
Clean the form and return the cleaned data.
"""
cleaned_data = super().clean()
return cleaned_data
So far, so .... good I assume.
So here are the questions:
How do I tell Django that I am uploading a pair of fields (file and checkbox)?
How to pass the post instance to the image?
Why is my request files empty?
How am I supposed to group the image-checkbox pairs? Is it something inside the html??
I tried to use the form itself, but it misses the post instance.
I tried to iterate through the files but they are empty.
I tried to iterate through the form fields, but I do not see all uploaded files.
If someone ever needs this:
I found multiple problems that I think might be useful to share.
I am using multiple forms under one submit button. At some point I played myself and deleted the enctype="multipart/form-data" from my form tag. (oops)
My javascript had a strange behaviour because it was pumping the image as "data:sjdfhsdkjbfksd" in my identifier for the checkbox. So again a rookie mistake. I detected it when I started slapping console.log() to everything an anything.
If you are like me (main language is Python and JS is something accidentally known), remember - there is a fantastic debugger for JS in the browser, under sources.
JS scopes are strange and they are not like in Python. While using the debugger, you will see that the loops doing stuff and calling event functions will always do everything outside event functions and then move to your event function. You can pass stuff by reassigning in var statements, but be ready to be screamed at by a JS guy.
Related
I have followed this tutorial to create a website. I have expanded my page beyond the tutorial and am now trying to create a view that only uses the images of the tag 'fav'. I am trying to use the tag as a sort of favourite button, and the only display those images in a bootstrap carousel.
All I have, to go by, is this different view that only uses the images of the tag you click on.
class PhotoTagListView(PhotoListView):
template_name = 'photoapp/taglist.html'
def get_tag(self):
return self.kwargs.get('tag')
def get_queryset(self):
return self.model.objects.filter(tags__slug=self.get_tag())
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["tag"] = self.get_tag()
return context
But since I don't really understand it, it isn't really any help.
How do I create a view for a specific tag? Or is there even a solution to this problem entirely feasible in html or js?
Tried to modify the other view, but since I don't know enough about django and python it naturally didn't work.
My modification:
class CarouselView(PhotoListView):
template_name = 'photoapp/carousel.html'
def get_queryset(self):
return self.model.objects.filter(tags__slug='fav')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["tag"] = 'fav'
return context
I have a Django/Wagtail/Puput site with this structure:
RootPage
|
|- BlogPage (Puput)
|- InformationPage
I'm trying to display summary info from the Puput blog on the InformationPage. This works with this code, as long as I only have one BlogPage:
class InformationPage(Page):
body = RichTextField(verbose_name=_("body"))
. . .
def get_context(self, request, *args, **kwargs):
context = super(InformationPage, self).get_context(
request, *args, **kwargs)
context['blog_page'] = BlogPage.objects.first()
context['information_page'] = self
return context
But I'm trying to make it work with more than one blog page. It seems like this should work:
class InformationPage(Page):
body = RichTextField(verbose_name=_("body"))
blog_page = models.ForeignKey('wagtailcore.Page', on_delete=models.PROTECT, related_name="information_blog")
content_panels = [
MultiFieldPanel(
[
FieldPanel("title", classname="title"),
FieldPanel("body", classname="full"),
PageChooserPanel('blog_page'),
],
heading=_("Content"),
)]
def get_context(self, request, *args, **kwargs):
context = super(InformationPage, self).get_context(
request, *args, **kwargs)
context['blog_page'] = self.blog_page
context['information_page'] = self
return context
But it doesn't. This was suggested by #gasman here. In other words, if I refer to the blog page properties using context['blog_page'] = BlogPage.objects.first(), everything works fine, but switching it out to use context['blog_page'] = self.blog_page (and selecting the correct blog page in the admin) does not work.
Without switching it out, I think I can only ever have a single instance of BlogPage, because all InformationPages will have to pull from the first instance.
Any thoughts?
You haven't given a description of the problem beyond "it doesn't work", so I'm only guessing here, but you're presumably trying to output fields of the blog page that are part of the BlogPage model. This doesn't work because blog_page is defined as a foreign key to wagtailcore.Page, and so accessing self.blog_page only gives you a basic Page object consisting of the core fields such as title. You can retrieve the full BlogPage object by accessing self.blog_page.specific:
context['blog_page'] = self.blog_page.specific
However, a smarter approach is to change your foreign key to point to BlogPage, since presumably choosing any other page type is not valid here:
blog_page = models.ForeignKey('my_blog_app.BlogPage', on_delete=models.PROTECT, related_name="information_blog")
With this change, self.blog_page will return a BlogPage instance directly, and there's no need for .specific.
This is my model and I want to limit the number of photos that a user can upload just 10. I want to do it one place so it works in the admin and user facing forms. Can someone help me out here?
class StarPhotos(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
PHOTO_CATEGORY = (
('HS', "Head Shot"),
('WP', "Western Party Wear"),
('IP', "Indian Party Wear"),
('SW', "Swim Wear"),
('CW', "Casual Wear"),
)
category = models.CharField(max_length=2, choices=PHOTO_CATEGORY, default='CW')
# This FileField should preferaby be changed to ImageField with pillow installed.
photos = models.FileField(max_length=200, upload_to='images/',)
def __str__(self):
return "Images for {0}".format(self.user)
You can use a checker function in your model to check whether User has uploaded 10 photos or not
def check_photo_count(self, user):
photo_count = self.object.filter(user=user).count()
return photo_count
This does not seem to be the best solution available but it should work.
Also remember to put a check for this in your views or admin. You can also return a Boolean value from this function saying that this user is allowed to upload more photos or not.
This same logic can be applied to manager if you don't want to put checks everywhere.
So you just have to put this check in the create method and if the check fails simply raise a error or return a false value saying that object is not created.
You can override save and bulk_create methods from StarPhotos, I don't check the code, but it's some like that:
class CheckPhotoModelManager(models.Manager):
def bulk_create(self, objs, batch_size=None):
photos = StarPhotos.object.filter(user=objs[0].user).count()
if photos < 10:
super(StarPhotos, self).bulk_create(...)
class StarPhotos(models.Model):
objects = CheckPhotoModelManager()
def save(self, *args, **kwargs):
photos = StarPhotos.object.filter(user=self.user).count()
if photos < 10:
super(StarPhotos, self).save(*args, **kwargs)
I've seen a similar question, alas it has not been answered.
I have an app that features Entries (like blog entries) which include a part called SubEntry. I want the users to be able to report SubEntries (i.e. press the button 'report', fill some fields and the application sends an email to admins, saving the report in db is nice to have):
The flow should be like that: at the view EntryDetails (url: /entry/entry-title/) the user may click on the SubEntry part. The modal opens and the subentry is visualized in the modal as enlarged, with a button/link underneath 'Report the SubEntry'. Then it's possible to click on the 'Report the SubEntry' button and two fields appear - reason of reporting and contact detail of the reporter (here I am just toggling the visibility of the fields). I manage to display the form (with get overriden - overriding get_form_kwargs causes the error No Entry with that title) but either the Entry or its attributes are not displayed...
My questions are:
1) is creating a model for Reporting (ReportSubEntry) a decent approach?
2) I can't seem to pass the needed variable (an Entry object that is to be a ForeignKey for a SubEntry object that is being created) from CreateReport view to the report_subentry.html.
any thoughts, advice? Python 3.5, Django 1.10
models.py:
class ReportSubentry(models.Model):
Entry = models.ForeignKey('Entry')
details = models.CharField(max_length=100)
contact = models.EmailField()
forms.py:
class ReportEntryForm(forms.ModelForm):
class Meta:
model = ReportSubEntry
fields = ['details', 'contact', 'project']
views.py:
class CreateReport(CreateView):
model = ReportSubEntry
form_class = ReportSubEntryForm
template_name = 'understand/report_subentry.html'
# tried two methods to pass the variables:
def get(self, request, *args, **kwargs):
self.object = None
title = kwargs.get('title')
kwargs['entry'] = get_object_or_404(Entry, title=title)
return super(CreateReport, self).get(request, **kwargs)
def get_form_kwargs(self, **kwargs):
title = kwargs.get('title')
kwargs['entry'] = get_object_or_404(Entry, title=title)
return kwargs
The current model that you are using ReportSubEntry is perfect and there is no need to change it.
In your forms.py ReportEntryForm you have to use relatedfields to be able to correctly serialize the data. There is no need to override anything. When user clicks on report the sub entry you have to pass the pk of Entry model as it is required to know which entry is reported. I am assuming that since you are successfully displaying the entries pk of those are present. When you receive the pk with other two fields you get the corresponding entry for pk and then pass the object to ReportSubentry.objects.create method.
The reportentry form should not contain foreign key. You have two choices for that. First is remove that field and pass the pk of entry from frontend using ajax calls or use javascript to add a disabled input field which contains pk of entry when user clicks on report subentry.
Ok, so I've solved this issue.
The only solution that worked for me was overriding the get method of the ReportSubentry without calling the get method of the superclass:
def get(self, request, *args, **kwargs):
self.object = None
title = kwargs.get('title')
entry = get_object_or_404(Entry, title=title)
context_data = self.get_context_data()
context_data.update(entry=entry)
return self.render_to_response(context_data)
Please feel free to discuss it.
This is my models.py:
class College(models.Model):
name = models.CharField(unique=True, max_length=50,
help_text='Name of the college.'
)
slug = models.SlugField(unique=True)
description = models.TextField(blank = True)
image = models.ImageField(upload_to='site-media/media/college_images/',
default = 'site-media/media/college_images/default.jpeg'
)
user = models.ForeignKey(User)
def get_absolute_url(self):
return "/%s/" % self.slug
def create_thumbnail(self):
if not self.image:
return
THUMBNAIL_SIZE = (250,193)
image = Image.open(StringIO(self.image.read()))
thumb = ImageOps.fit(image, THUMBNAIL_SIZE, Image.ANTIALIAS)
temp_handle = StringIO()
thumb.convert('RGB').save(temp_handle, 'jpeg')
temp_handle.seek(0)
suf = SimpleUploadedFile(os.path.split(self.image.name)[-1],
temp_handle.read(), content_type='image/jpeg')
self.image.save('%s_college_.%s'%(os.path.splitext(suf.name)[0],'jpeg'), suf, save=False)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
self.create_thumbnail()
super(College, self).save(*args, **kwargs)
I have presented the user with a form to edit just the description. When the description 'POST' is made the 'save()' method above is called. The problem with this is that the thumbnail is created over and over again with a bigger name every time. And, also the previous thumbnail is not deleted from the hard disk. Is it possible, that this 'thumbnail' method doesn't get called over and over again with each edit of the 'description'.
You can check whether you are sending image file in you request post or not. For this You need to call your save in view with one argument request like : college.save(request)
def save(self, request=False, *args, **kwargs):
self.slug = slugify(self.name)
if request and request.FILES.get('image',False):
self.create_thumbnail()
super(College, self).save(*args, **kwargs)
OR
you can differentiate your save and edit using
if self.pk is not None
But it can create problem if you edit your image.
So its your choice How you want to go with it.
There are two reasonable paths I see to handle this. Neither are ideal, so I'll be interested to see if anyone has a better option to offer.
One is to save the filename of the most recent image for which you created a thumbnail as a model field. Then in your save method you can check the filename of your image field against it and create a new thumbmail if it has changed. This has the disadvantage of requiring a new model field, but is more universal in its application.
The other is to override the save method of the form class. You can then check the old image filename by looking at self.instance.image and comparing that against self.cleaned_data['image']. This has the disadvantage of only affecting views that use that form class, but doesn't require changing your data model. You can pass a custom form to the admin, if you're using it, or override the admin class's save_model method.
The simple solution is to NOT try to create the thumbnail at this stage, but only when it's needed, ie (pseudocode example):
class Whatever(models.Model):
image = models.ImageField(...)
#property
def thumbnail(self):
thumb = do_i_have_a_thumbnail_yet(self.image)
if not thumb:
thumb = ok_then_make_me_a_thumbnail_and_store_it_please(self.image)
return thumb
Implementing do_i_have_a_thumbnail_yet() and ok_then_make_me_a_thumbnail_and_store_it_please() is left as an exercise to the reader, but there are librairies or django apps providing such services.
Another - and even better imho - solution is to delegate thumbnails handling to a templatetag or template filter. Why ? because
thumbnails are mostly presentation stuff and do not belong to the model
most of the time you'll need different thumbnails sizes from a same image
the front-end developer may want to be free to change the thumbnail's size without having to ask you to change your backend code and write a migration script to recompute all existing thumbnails.