Multiple images in django form with multiupload - django

I need to add multiple images in django form to one model. I did a research and for form outside of django I try to setup django-multiupload.
My models.py:
class Profile(models.Model):
...
...
first = models.ImageField("first", upload_to='first')
second = models.ImageField("second", upload_to='second')
...
In forms.py:
class AddForm(forms.ModelForm):
first = MultiImageField(min_num=1, max_num=20)
second = MultiImageField(min_num=1, max_num=4)
In views.py:
class UploadView(FormView):
template_name = 'finder/submit.html'
form_class = AddForm
success_url = '/'
def form_valid(self, form):
for each in form.cleaned_data['first']:
Profile.objects.create(first=each)
for each in form.cleaned_data['second']:
Profile.objects.create(second=each)
return super(UploadView, self).form_valid(form)
And on submitting form this form creates multiple Profile objects with only first/second field filled.
How can I create only one model with remaining fields (other than first/second) and with multiple first/second fields?
It was my function-based view before adding multiupload but I couldn't make it work, maybe it's easier to change it somehow?
def add_form(request, *args, **kwargs):
if request.method == "POST":
form = AddForm(request.POST)
if form.is_valid():
profile = form.save(commit=False)
profile.save()
return redirect('/', pk=profile.pk)
else:
form = AddForm()
return render(request, 'finder/submit.html', {'form': form})

I have never used the Django-multiupload, but I happen to read some of the docs.
If you want to save multiple files for your user model, you may need to create another model for accommodating the files and add a Foreign Key towards the Profile model.
Remove the first and second fields from Profile model. It causes you to create multiple profiles with same data inorder to accomodate multiple images.
Simple example,
class Image(models.Model):
image = models.FileField()
profile = models.ForeignKey(Profile, related_name='images')
is_first = models.BooleanField(default=False)
is_second = models.BooleanField(default=False)
Then, edit the save method in form,
class AddForm(forms.ModelForm):
first = MultiImageField(min_num=1, max_num=20)
second = MultiImageField(min_num=1, max_num=4)
class Meta:
model = Profile
fields = (.......... 'first', 'second')
def save(self, commit=True):
first_images = self.cleaned_data.pop('first')
second_images = self.cleaned_data.pop('second')
instance = super(AddForm, self).save()
for each in first_images:
first = Image(image=each, profile=instance, is_first=True)
first.save()
for each in second_images:
second = Image(image=each, profile=instance, is_second=True)
second.save()
return instance
Then, on the views, edit the view,
class UploadView(FormView):
template_name = 'finder/submit.html'
form_class = AddForm
success_url = '/'
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
instance.save()
return super(UploadView, self).form_valid(form)
Or in function based view,
def add_form(request, *args, **kwargs):
if request.method == "POST":
form = AddForm(request.POST)
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save()
return redirect('/', pk=profile.pk)
else:
form = AddForm()
return render(request, 'finder/submit.html', {'form': form})

Related

Send the logged user Profile Model to a CreateView form

what I am trying to accomplish is to send the "requester" model, using the logged-in user to a form ...
Mainly the problem that I have is that the views.py "class CreateOrderView(CreateView)" does not have a parameter "request" , so I cannot get the request.user, and therefore get requester_obj and automatically select this requester_obj in the form field "requester", when entering this page.
models.py Order:
DEFAULT_REQUESTER_ID= 1
requester = models.ForeignKey(Profile, on_delete=models.CASCADE, default=DEFAULT_REQUESTER_ID, verbose_name="usuario")
forms.py:
class OrderCreateForm(BaseForm, forms.ModelForm):
date = forms.DateField(label="Fecha" , widget=forms.DateInput(attrs={'type': 'date'}))
class Meta:
model = Order
fields = ['requester','title' , 'date', ]
views.py:
#method_decorator(staff_member_required, name='dispatch')
class CreateOrderView(CreateView):
template_name = 'form.html'
form_class = OrderCreateForm
model = Order
def get_success_url(self):
self.new_object.refresh_from_db()
return reverse('update_order', kwargs={'pk': self.new_object.id})
def form_valid(self, form):
object = form.save()
object.refresh_from_db()
self.new_object = object
return super().form_valid(form)
I get the requester like this:
#login_required
def create(request):
#return render(request, 'app_client/create.html')
if request.method == 'POST':
if request.POST['value'] and request.POST['products']:
logged_user = request.user
user_obj = get_object_or_404(User, username=logged_user)
requestor_obj = get_object_or_404(Profile, user=user_obj)
....
I just found a solution for my issue...
What I did was to remove the "requester" field in forms.py, and send the requester obj to the form after user presses the submit form button
def form_valid(self, form):
logged_user = self.request.user
user_obj = get_object_or_404(User, username=logged_user)
requester_obj = get_object_or_404(Profile, user=user_obj)
form.instance.requestor = requestor_obj
object = form.save()
object.refresh_from_db()
self.new_object = object
return super().form_valid(form)
form.instance.requestor = requestor_obj was the line that I needed to send it to form before saving it.
ref: Django CreateView Foreign key

Why i create two records at once using django-bootstrap-modal-forms

I'm using django-bootstrap-modal-forms.
My forms.py:
class UserAppForm(BSModalForm):
class Meta:
model = UserApp
fields = ('app', 'app_type')
In view, in order to attach current user, i override form_valid():
class AppCreateView(BSModalCreateView):
template_name = 'apps/app_create.html'
form_class = UserAppForm
success_message = 'Success: App was created.'
success_url = reverse_lazy('dashboard')
def form_valid(self, form):
app = form.save(commit=False)
profile = Profile.objects.get(user=self.request.user)
app.profile = profile
app.save()
return redirect(self.success_url)
But, if i try to create UserApp, i get two instances at once.
Where is my mistake?
Correct way to override form_valid() is:
def form_valid(self, form):
if not self.request.is_ajax():
app = form.save(commit=False)
profile = Profile.objects.get(user=self.request.user)
app.profile = profile
app.save()
return HttpResponseRedirect(self.success_url)

Django inline formset validation

I have the following models:
class CaseForm(ModelForm):
class Meta:
model = Case
fields = '__all__'
class ClientForm(ModelForm):
class Meta:
model = Client
fields = '_all__'
CaseClientFormset = inlineformset_factory(Case, Client, form=ClientForm,
extra=0, max_num=2, min_num=1,
validate_max=True,
validate_min=True)
When I fill in the top part of the form (caseform) it saves correctly. When I fill in the caseform and a clientform it saves correctly.
If I fill in the caseform but partially fill in the clientform no validation appears to take place, and a case is saved and the client information goes missing and is never saved.
class CaseCreateView(LoginRequiredMixin, AdviserExistenceMixin,
CreateView):
model = Case
form_class = CaseForm
def form_valid(self, form):
context = self.get_context_data()
clients = context['clients']
self.object = form.save()
if clients.is_valid():
clients.instance = self.object
clients.save()
return super(CaseCreateView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['clients'] = CaseClientFormset(self.request.POST)
else:
context['clients'] = CaseClientFormset()
context['navbar'] = str(self.model.__name__).lower()
return context
The other issue I have is that despite specifying min_num=1 and validate_min=True, I appear to be able to save a case without a clientform being filled in.
Any help would be appreciated.
Fixed replacing def form_valid with:
def form_valid(self, form):
context = self.get_context_data()
clients = context['clients']
if clients.is_valid():
self.object = form.save()
clients.instance = self.object
clients.save()
return super(CaseUpdateView, self).form_valid(form)
else:
return super(CaseUpdateView, self).form_invalid(form)

How do I prepopulate an inline formset that is purely ImageFields?

I have two models: Profile and CredentialImage.
I have implemented inlineformsets to allow the Profile to upload up to 5 maximum images(CredentialImage).
Currently, the images save to the database but they will not pre-populate when I revisit the update form, thus allowing the user to upload an unlimited amount of photos 5 at a time. Ideally, the same images saved to the database would pre-populate on the form as to limit the Profile to own and edit only 5 photos at a time.
From my understanding, I need to pass in the object to the page to pre-populate information, and I believe I'm doing just that by defining get_object.
Here are the two models:
class Profile(models.Model):
...
def get_absolute_url(self):
return reverse("profile:profile_detail",
kwargs={"username": self.user})
class CredentialImage(models.Model):
profile = models.ForeignKey(Profile, default=None,
related_name='credentialimage')
image = models.ImageField(upload_to=credential_photo_upload_loc)
The modelforms + initialization of the inlineformset_factory:
from django.forms.models import inlineformset_factory
class ProfileUpdateForm(ModelForm):
class Meta:
model = Profile
fields = [
"introduction",
"biography",
]
class CredentialImageForm(ModelForm):
image = ImageField()
class Meta:
model = CredentialImage
fields = ['image', ]
CredentialImageFormSet = inlineformset_factory(Profile,
CredentialImage, fields=('image', ), extra=4, max_num=4)
A class-based UpdateView for updating a Profile:
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
form_class = ProfileUpdateForm
template_name = 'profile/profile_edit.html'
def get_context_data(self, **kwargs):
context = super(ProfileUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
context['credential_image'] = CredentialImageFormSet(self.request.POST, self.request.FILES)
else:
context['credential_image'] = CredentialImageFormSet()
return context
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())
instance = form.save(commit=False)
instance.user = self.request.user
return super(ProfileUpdateView, self).form_valid(form)
You have to supply an instance in your get_context_data method.
def get_context_data(self, **kwargs):
context = super(ProfileUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
context['credential_image'] = CredentialImageFormSet(self.request.POST, self.request.FILES, instance=self.object)
else:
context['credential_image'] = CredentialImageFormSet(instance=self.object)
return context

Why aren't the images of an inlineformset_factory being saved?

I have two models: Profile and CredentialImage.
I am trying to allow each Profile to upload, optionally, up to 5 maximum images(CredentialImage).
I've decided to use an inlineformset_factory for the images because on the UpdateView users will be given the option of updating their general Profile information as well as their 5 select images.
The code goes without error, but the images do not save to the database.
Here are the two models:
class Profile(models.Model):
...
def get_absolute_url(self):
return reverse("profile:profile_detail",
kwargs={"username": self.user})
class CredentialImage(models.Model):
profile = models.ForeignKey(Profile, default=None)
image = models.ImageField(upload_to=credential_photo_upload_loc)
The modelforms + initialization of the inlineformset_factory:
from django.forms.models import inlineformset_factory
class ProfileUpdateForm(ModelForm):
class Meta:
model = Profile
fields = [
"introduction",
"biography",
]
class CredentialImageForm(ModelForm):
image = ImageField()
class Meta:
model = CredentialImage
fields = ['image', ]
CredentialImageFormSet = inlineformset_factory(Profile,
CredentialImage, fields=('image', ), extra=4)
A class-based UpdateView for updating a Profile:
class ProfileUpdateView(LoginRequiredMixin, UpdateView):
form_class = ProfileUpdateForm
template_name = 'profile/profile_edit.html'
def get_context_data(self, **kwargs):
context = super(ProfileUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
context['credential_image'] = CredentialImageFormSet(self.request.POST)
else:
context['credential_image'] = CredentialImageFormSet()
return context
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())
instance = form.save(commit=False)
instance.user = self.request.user
return super(ProfileUpdateView, self).form_valid(form)
I'm especially wary of the get_context_data and form_valid.
Is it correct to try and instantiate the formset using get_context_data and to save both within form_valid?
You need to pass request.FILES to the formset as well as request.POST when you are uploading files:
context['credential_image'] = CredentialImageFormSet(self.request.POST, self.request.FILES)
The get_context_data method is meant for getting the context for the data. You shouldn't be instantiating formsets there. You could have a look at the UpdateWithInlinesView from django-extra-views.