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

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

Related

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)

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.

Multiple images in django form with multiupload

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})

NOT NULL constraint failed when save ModelForm

I am new in Django (1.9)
I have a NOT NULL constraint failed when I save my ModelForm and i don't understand why ...
I propose to the user a form to post a comment and the only field include in the form is "text", i want to set the excluded fields in my view after the validation and before save in database
Models.py :
class Commentaire(models.Model):
text = RichTextField()
pub_date = models.DateTimeField()
author = models.ForeignKey(User)
post = models.ForeignKey(Post)
publish = models.BooleanField()
def __str__(self):
return "/%s/%s" % (self.pub_date,self.author.username)
class Meta:
ordering = ["-pub_date"]
Forms.py :
class CommentaireForm(forms.ModelForm):
class Meta:
model = Commentaire
fields = ['text']
Views.py :
class PostDetail(View):
def get(self, request, *args, **kwargs):
view = PostDisplay.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = PostComment.as_view()
return view(request, *args, **kwargs)
class PostDisplay(DetailView):
model = Post
def get_context_data(self, **kwargs):
context = super(PostDisplay, self).get_context_data(**kwargs)
context['form'] = CommentaireForm()
return context
class PostComment(SingleObjectMixin, FormView):
template_name = 'blogengine/post_detail.html'
form_class = CommentaireForm
model = Post
def post(self, request, *args, **kwargs):
#if not request.user.is_authenticated():
# return HttpResponseForbidden()
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
"""
If the form is valid, redirect to the supplied URL
"""
form.save(commit=False)
form.post = self.object
form.author = self.request.user
form.pub_date = datetime.now()
form.publish = True
form.save()
return HttpResponseRedirect(self.get_success_url())
When Django save the form, i have this exception :
NOT NULL constraint failed: blogengine_commentaire.pub_date
The values i set for the excluded fields (author, post, pub_date) in "form_valid" are not taken into account, they seem to stay NULL
Could you explain me why because i am lost ?
Thanks
You need to rewrite form_valid method like that
def form_valid(self, form):
"""
If the form is valid, redirect to the supplied URL
"""
model_instance = form.save(commit=False)
model_instance.post = self.object
model_instance.author = self.request.user
model_instance.pub_date = datetime.now()
model_instance.publish = True
model_instance.save()
return HttpResponseRedirect(self.get_success_url())
Because save(commit=False) will return you an Post instance that you then need to populate and save.

Django class based UpdateView with inline formsets

I have a model PSG which has a relation to another model SubPSG. PSGs can have several SubPSGs. I'm using inline formsets to create SubPSGs as I create the PSGs. The CreateView works okay but the UpdateView is not working as expected. The UpdateView is as follows:
class PSGUpdate(GroupRequiredMixin, UpdateView):
model = PSG
form_class = PSGForm
template_name = "management/psg_update_form.html"
success_url = reverse_lazy('dashboard')
group_required = [u"admin", "manager"]
def get_context_data(self, **kwargs):
context = super(PSGUpdate, self).get_context_data(**kwargs)
subpsgs = SubPSG.objects.filter(psg=self.object).values()
sub_formset = inlineformset_factory(PSG, SubPSG, fields=('name',), can_delete=True,
extra=len(subpsgs), formset=BaseSubPSGFormset,)
context['formset'] = sub_formset(instance=self.object, initial=subpsgs, prefix='psg')
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
subpsgs = SubPSG.objects.filter(psg=self.object).values()
psg_form = PSGForm(request.POST, instance=self.object)
sub_formset = inlineformset_factory(PSG, SubPSG, fields=('name',), can_delete=True, extra=len(subpsgs), formset=BaseSubPSGFormset)
formset = sub_formset(self.request.POST, instance=self.object, prefix='psg')
if all([psg_form.is_valid(), formset.is_valid()]):
return self.form_valid(psg_form, formset)
else:
return self.form_invalid(psg_form, formset)
def form_valid(self, form, formset):
if all([formset.is_valid() and form.is_valid()]):
self.object = form.save()
instances = formset.save(commit=False)
for form in instances:
form.psg = self.object
form.save()
return HttpResponseRedirect(self.get_success_url())
else:
return super(PSGUpdate, self).form_valid(form)
When I try updating a SubPSG formset, a new object is created instead of the edit taking place. Moreover, duplicates of the other formsets are also created.
What I'm I doing wrong??