How do I upload multiple images and associate with a model - django

Errors when attempting to upload multiple images
Description of issue:
I have a model, Vehicle which has vehicle information as well as a cover photo. To add images for a gallery, I have a second model VehicleImage with a foreign key to Vehicle. I'm attempting to upload multiple images with a single file picker and save those all to a defined path (/media/vehicles/vehicle#/) I've tried different views to no avail. Right now i'm getting the following error.
Raised by: main.views.ImageFieldView
No vehicle image found matching the query
Thank you, any help would be greatly appreciated. I'm still learning but I work at it every day.
models.py
def vehicle_directory_path(instance, filename):
#! file will be uploaded to media_cdn/vehicle/<id>/<filename>.<ext>
ext = filename.split('.')[-1]
name = filename.split('.')[0]
vehicle = Vehicle.objects.get(id=instance.id)
return 'vehicles/{0}/{1}.{2}'.format(vehicle.id, name, ext)
class VehicleImage(models.Model):
vehicle = models.ForeignKey('Vehicle', on_delete=models.CASCADE, related_name='images')
image = models.ImageField(upload_to=vehicle_directory_path, null=True, blank=True)
forms.py
class ImageFieldForm(forms.ModelForm):
image_field = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
class Meta:
model = VehicleImage
exclude = ('image', 'vehicle')
views.py
class ImageFieldView(CreateView):
model = VehicleImage
form_class = ImageFieldForm
def get(self, request, *args, **kwargs):
context = {'form': ImageFieldForm}
return render(request, 'main/vehicle_upload.html', context)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('image_field')
if form.is_valid():
for file in files:
instance = VehicleImage(
vehicle=Vehicle.objects.get(pk=kwargs['pk']),
image=file)
instance.save()
return self.form_valid(form)
else:
return self.form_invalid(form)
urls.py
urlpatterns = [
path('', views.homepage, name='homepage'),
path('vehicles/', views.vehicles, name='vehicles'),
path('vehicles/detail/<int:pk>/', views.VehicleDetail.as_view(), name='vehicle_detail'),
path('vehicles/edit/<int:pk>/', views.VehicleEdit.as_view(), name='vehicle_edit'),
path('vehicles/images/upload/<int:pk>', views.ImageFieldView.as_view(),name='vehicle_image_upload'),
# path('vehicle/image/add/<int:pk>/', views.VehicleEdit.as_view(), name='vehicle_image'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Suggested Edit: Still getting the same error
vehicles = Vehicle.objects.get(pk=kwargs['pk'])
if form.is_valid():
for f in files:
VehicleImage.objects.create(vehicle=vehicles, image=f)
return self.form_valid(form)

Related

How Do I Access Data From a Multi-Table Inheritance Definition?

Straight from the Django Docs....
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
I can see the data in my database so I know it's working...but I can't figure out how to access the data. In the past I have used Class Based Views...this is my first go at using a multi-table inheritance structure beyond class based views.
I have tried to do...
def get_absolute_url(self):
return reverse("App:create_restaurant_detail",kwargs={'pk':self.object.place_ptr_id})
Here's my urls...
path("create_restaurant",views.CreateRestaurantView.as_view(), name='create_restaurant'),
path("create_restaurant_detail/<pk>/",views.CreateRestaurantDetailView.as_view(), name='create_restaurant_detail'),
And my Views....
class CreateRestaurantView(LoginRequiredMixin,CreateView):
model = Restaurant
form_class = CreateRestaurantForm
template_name = 'create_restaurant.html'
def get_success_url(self):
return redirect('App:create_restaurant_detail', kwargs={'pk':self.object.place_ptr_id})
class CreateRestaurantDetailView(LoginRequiredMixin,DetailView):
model = Restaurant
context_object_name = 'restaurant_detail'
template_name = 'create_restaurant_detail.html'
But the url lookup keeps saying not found.
In the log...I see....
django.urls.exceptions.NoReverseMatch: Reverse for 'create_restaurant_detail' with keyword arguments '{'kwargs': {'pk': 12}}' not found. 1 pattern(s
) tried: ['LevelSet/App/create_restaurant_detail/1bad5cba\\-a087\\-4f0a\\-9c3b\\-65ac096c3e42/(?P<pk>[^/]+)/$']
I'm trying to figure out how to access the data in the table. Thanks for any thoughts and help in advance.
class CreateRestaurantView(LoginRequiredMixin,CreateView):
model = Restaurant
form_class = CreateRestaurantForm
template_name = 'create_restaurant.html'
def form_valid(self, form):
instance = form.save()
return JsonResponse({ 'id': instance.pk, 'success_url': self.get_success_url() })
def post(self, request, *args, **kwargs):
if "cancel" in request.POST:
return HttpResponseRedirect(reverse('Main:main'))
else:
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
restaurant_instance = form.save()
self.object = restaurant_instance
return self.form_valid(form)
def get_success_url(self):
return reverse('Newapp:create_restaurant_detail', kwargs={ 'pk' : self.object.place_ptr_id })
My URL...
path("create_restaurant_detail/<int:pk>/",views.CreateRestaurantDetailView.as_view(), name='create_restaurant_detail'),
I misunderstood CreateView. This code is working at the moment, but I'm not sure JsonResponse is the best answer for form_valid. I couldn't figure out the proper alternative but my original problem has been solved. Thanks to Iain for helping me work through it.

Django Multiple Image Upload Using form

This can upload single image. But i want to upload multiple image like insta do. In instagram multiple images are stored in a slider. I don't understand files = request.FILES.getlist('image') how can i iterate this this list
Views.py file
#login_required
def index(request):
images = Image.objects.all()
users = User.objects.filter(is_superuser=False)
prof = Profile.objects.get(user=request.user)
actions = Action.objects.exclude(user=request.user)
following_ids = request.user.following.values_list('id', flat=True)
if request.method == "POST":
form = ImageCreateForm(request.POST, request.FILES)
files = request.FILES.getlist('image')
if form.is_valid():
description = form.cleaned_data["description"]
image = form.cleaned_data["image"]
new_item = form.save(commit=False)
new_item.user = request.user
new_item.save()
create_action(request.user, 'Uploaded Image', new_item)
messages.success(request, "Image Added Successfully")
return redirect(new_item.get_absolute_url())
else:
form = ImageCreateForm(data=request.GET)
if following_ids:
# If user is following others, retrieve only their actions
actions = actions.filter(user_id__in=following_ids)
actions = actions.select_related('user', 'user__profile').prefetch_related('target')[:10]
return render(request, "account/index.html", {
'section': 'index',
'images': images,
'prof': prof,
'actions': actions,
'users': users,
'form': form,
})
forms.py file
from django import forms
from urllib import request
from django.core.files.base import ContentFile
from django.utils.text import slugify
from .models import Image
class ImageCreateForm(forms.ModelForm):
image = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
class Meta:
model = Image
fields = ('description',)
def clean_url(self):
image = self.cleaned_data['image']
valid_extensions = ['jpg', 'jpeg']
extension = image.rsplit('.', 1)[1].lower()
if extension not in valid_extensions:
raise forms.ValidationError('The Given URL does not match valid image extensions.')
return image
def save(self, force_insert=False, force_update=False, commit=True):
image = super().save(commit=False)
url = self.cleaned_data['image']
name = slugify(image.description)
image_name = f'{name}'
image.image.save(image_name, ContentFile(url.read()), save=False)
if commit:
image.save()
return image
admin.py file
#admin.register(Image)
class ImageAdmin(admin.ModelAdmin):
list_display = ['slug', 'image', 'description', 'created']
list_filter = ['created']
You can just loop throught it:
for afile in request.FILES.getlist('image'):
mymodel = MyModel()
mymodel .pic = afile
mymodel .save()
Of course you need to make sure you can save the images in your model.

Key error django - passing additional stuffs with django form

I have my models like this:
class Photo(models.Model):
title = models.CharField(max_length=255, blank=True)
file = models.FileField(default='default.jpg')
page = models.ForeignKey(Page, on_delete=models.CASCADE)
class Page(Group):
name = models.IntegerField()
in my forms.py, to create a form for posting multiple images, I created this: I also override get_form_kwargs for passing the 'page' thing with the form as you can see that the Photo model has a page foreignkey.
class PhotoForm(forms.ModelForm):
def get_form_kwargs(self):
kwargs = super(PhotoForm, self).get_form_kwargs()
kwargs['page'] = Page.objects.get(pk=self.GET['pk'])
return kwargs
class Meta:
model = Photo
fields = ('file', )
In my main urls.py, I say like:
path('page/<int:pk>/photos/', include('photos.urls'))
and in photos apps urls.py, I say:
path('progress-bar-upload/', views.ProgressBarUploadView.as_view(), name='progress_bar_upload')
and in my photos apps views.py I say:
class ProgressBarUploadView(View):
model = Photo
def get(self, request, **kwargs):
photos_list = Photo.objects.all()
page = get_object_or_404(Page, page=self.kwargs['page'])
return render(self.request, 'photos/progress_bar_upload/index.html', {'photos': photos_list, 'page':page})
def post(self, request):
time.sleep(1)
form = PhotoForm(self.request.POST, self.request.FILES)
if form.is_valid():
photo = form.save()
data = {'is_valid': True, 'name': photo.file.name, 'url': photo.file.url}
else:
data = {'is_valid': False}
return JsonResponse(data)
Then when in my html I call the page,
{{page}}
Then when I go to 'localhost:/page/1/photos/progress-bar-upload/' I get this error:
KeyError at /page/7/photos/progress-bar-upload/
'page'
So what can I do? Can someone help me? Any help will be much much appreciated.

How do I thoroughly delete an old image file when a user updates the form's imagefield?

I have two modelforms--one includes a standard ImageField and the other is an inlineformset of ImageFields.
The normal standard ImageField renders on the page with the option for "Clear[ing]" while the inlineformset ImageFields render with an optional "Delete" tickbox.
Either Clear or Delete will remove the image from the User's profile, but the actual image file will remain in storage, as well as the URL to the image.
I am trying to remove all associations to the image once the User updates his Profile form by "clearing" or "deleting" the image.
I found FieldFile.delete which I believe just requires me to call .delete() on the instance, but I'm not sure how to conditionally check whether the user is updating the form with the "delete" box or "clear" box ticked.
Here are the two models containing the image fields:
class Profile(models.Model):
profile_photo = models.ImageField(
upload_to=profile_photo_upload_loc,
null=True,
blank=True,
verbose_name='Profile Image',
)
class CredentialImage(models.Model):
profile = models.ForeignKey(Profile, default=None,
related_name='credentialimage')
image = models.ImageField(
upload_to=credential_photo_upload_loc,
null=True,
verbose_name='Image Credentials',
)
The ModelForms:
class ProfileUpdateForm(ModelForm):
class Meta:
model = Profile
fields = [
"profile_photo",
]
class CredentialImageForm(ModelForm):
image = ImageField(required=False, widget=FileInput)
class Meta:
model = CredentialImage
fields = ['image', ]
CredentialImageFormSet = inlineformset_factory(Profile,
CredentialImage, fields=('image', ), extra=2)
The view:
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
form_class = ProfileUpdateForm
def get_context_data(self, **kwargs):
data = super(ProfileUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
data['credential_image'] = CredentialImageFormSet(self.request.POST, self.request.FILES, instance=self.object)
else:
data['credential_image'] = CredentialImageFormSet(instance=self.object)
return data
def get_object(self, *args, **kwargs):
user_profile = self.kwargs.get('username')
obj = get_object_or_404(Profile, user__username=user_profile)
return obj
def form_valid(self, form):
data = self.get_context_data()
formset = data['credential_image']
if formset.is_valid():
self.object = form.save()
formset.instance = self.object
formset.save()
return redirect(self.object.get_absolute_url())
else:
print("WTF")
instance = form.save(commit=False)
instance.user = self.request.user
return super(ProfileUpdateView, self).form_valid(form)
My View is all kinds of messed up but it seems to work. Now looking to completely remove the image files from storage upon update.
Use Django-cleanup:
pip install django-cleanup
settings.py
INSTALLED_APPS = (
...
'django_cleanup', # should go after your apps
...
)

How to populate a field in a django model

I have a Django project in which I have a view subclassed from the Django CreateView class. This view is used to upload a file to the server, and uses an UploadedFile model which I have created. The UploadedFile also needs to be associated with a project.
The project id is passed in as part of the URL: (r'^projects/(?P<proj_key>\d+)/$', UploadedFileCreateView.as_view(), {}, 'upload-new')
The problem is that I am not sure where the appropriate place is to associate this key with my model. Is there a method of CreateView or one of its ancestors that I should override that creates the model, or can this be done anywhere in my code in one of the methods I already override (this feels hacky though).
Furthermore, the project attribute of my UploadedFile is defined as a ForeignKey of type Project. How do I get the Project to associate with it?
Here is my model definition:
class Project(models.Model):
"""This is a project that is owned by a user and contains many UploadedFiles."""
name = models.CharField(max_length=200)
class UploadedFile(models.Model):
"""This represents a file that has been uploaded to the server."""
STATE_UPLOADED = 0
STATE_ANNOTATED = 1
STATE_PROCESSING = 2
STATE_PROCESSED = 4
STATES = (
(STATE_UPLOADED, "Uploaded"),
(STATE_ANNOTATED, "Annotated"),
(STATE_PROCESSING, "Processing"),
(STATE_PROCESSED, "Processed"),
)
status = models.SmallIntegerField(choices=STATES,
default=0, blank=True, null=True)
file = models.FileField(upload_to=settings.XML_ROOT)
project = models.ForeignKey(Project)
def __unicode__(self):
return self.file.name
def name(self):
return os.path.basename(self.file.name)
def save(self, *args, **kwargs):
if not self.status:
self.status = self.STATE_UPLOADED
super(UploadedFile, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
os.remove(self.file.path)
self.file.delete(False)
super(UploadedFile, self).delete(*args, **kwargs)
Here is my view definition:
class UploadedFileCreateView(CreateView):
model = UploadedFile
def form_valid(self, form):
logger.critical("Inside form_valid")
self.object = form.save()
f = self.request.FILES.get('file')
data = [{'name': f.name,
'url': settings.MEDIA_URL + "files/" + f.name.replace(" ", "_"),
'project': self.object.project.get().pk,
'delete_url': reverse('fileupload:upload-delete',
args=[self.object.id]),
'delete_type': "DELETE"}]
response = JSONResponse(data, {}, response_mimetype(self.request))
response['Content-Disposition'] = 'inline; filename=files.json'
return super(UploadedFileCreateView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(UploadedFileCreateView, self).get_context_data(**kwargs)
return context
You could do it right where you are calling form.save(). Just pass commit=False so that it won't save it to the db until you add the project id. For example:
self.object = form.save(commit=False)
self.object.project_id = self.kwargs['proj_key']
self.object.save()
Just make sure your form excludes the project field.
EDIT: to exclude the field, add an excludes variable to the form meta class:
class UploadedFileForm(forms.ModelForm):
class Meta:
model = UploadedFile
excludes = ('project',)