how to update formset - django

i've created web blog with django 2.2 each post has multiple images , but when i try to update the post the images wont updated
i use class based view
class Post(models.Model):
user= models.ForeignKey(Account,on_delete=models.CASCADE)
title= models.CharField(max_length=100)
#others
class PostImage(models.Model):
post= models.ForeignKey(Post,on_delete=models.CASCADE,related_name='images')
media_files = models.FileField(upload_to=random_url)
and this my forms.py
class PostImageForm(forms.ModelForm):
class Meta:
model = PostImage
fields = [
'media_files'
]
class PostUpdateForm(forms.ModelForm):
class Meta:
model = Post
fields = [
'title','description',#and others
]
my views.py
PostImageFormSet = inlineformset_factory(
Post,PostImage,form=PostImageForm,extra=1,can_delete=True,can_order=False
)
class PostUpdateView(LoginRequiredMixin,UserPassesTestMixin,UpdateView):
model = Post
form_class = PostUpdateForm
template_name = 'posts/update_post.html'
def get_context_data(self,**kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data['images'] = PostImageFormSet(self.request.POST or None,self.request.FILES,instance=self.object)
else:
data['images'] = PostImageFormSet(instance=self.object)
return data
def form_valid(self,form):
context = self.get_context_data()
images = context['images']
with transaction.atomic():
if form.is_valid() and images.is_valid():
self.object = form.save()
images.instance = self.object
images.save()
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user.username == post.user.username:
return True
return False
def get_success_url(self):
return reverse_lazy('post:post-detail',kwargs={'slug':self.object.slug})
my templates
<form enctype="multipart/form-data" method="post" action="">
{% csrf_token %}
{{images.management_form }}
{{ form|crispy }}
{% for img in images %}
<label>{{img.media_files.label}}</label>
{{img.media_files}}
{% endfor %}
<button type="submit" class="btn btn-block btn-primary">update</button>
</form>
it only save the post form not images , it doesnt affect images form
thanks

I think instead of writing:
images.instance = self.object
Please try this:
I noticed you are using the form is valid so it might be slightly different, but if you change it a bit it should work.
form = PostUpdateForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
images = cd.get('images')
Or using your current setup(not tested yet)
if form.is_valid() //Remove this part -> and images.is_valid():
if form.is_valid():
self.object = form.save()
instance = self.object
cd = form.cleaned_data
images = cd.get('images')
images.save()

Related

Django updateview (cbv and fbv) to upload multiple files not working

I am trying to upload files (single and multiple) with an UpdateView via an update template but I keep getting a 'This field is required' error when I submit, and the files aren't uploaded, of course. All required fields are filled with valid data types and the file upload fields are optional, so I don't understand where this error is coming from.
I strongly suspect there's an error in my view specifically in the if form.is_valid(): section. I tried using both function-based (FBV) and class-based (CBV) views, still getting the same error. I have even created a separate model for the multiple files upload field and a separate form class that extends the UpdateForm class, and I'm still getting the same error.
This is the model for the multiple files upload field:
class shareCapitalFiles(models.Model):
# Foreign key
coopform = models.ForeignKey(CoopForm, on_delete=models.CASCADE)
# Attach evidence of share capital
attach_share_capital = models.FileField('Attach Share Capital', upload_to="documents", blank=True)
This is my forms.py:
# Update Form
class updateCoopForm(forms.ModelForm):
nature_registration = forms.ChoiceField(widget=forms.RadioSelect(attrs={'class': 'flex inline-flex', 'cursor': 'pointer'}), choices=CoopForm.REGISTRATION_TYPE)
have_bye_laws = forms.ChoiceField(widget=forms.RadioSelect(attrs={'class': 'flex inline-flex', 'cursor': 'pointer'}), choices=CoopForm.BYE_LAWS_CHOICES)
attach_bye_laws = forms.FileField(required=False)
class Meta:
model = CoopForm
fields = '__all__'
widgets = {
'first_purpose': forms.Textarea(attrs={'rows':2}),
'second_purpose': forms.Textarea(attrs={'rows':2}),
'third_purpose': forms.Textarea(attrs={'rows':2}),
'first_origin_meeting_date': forms.DateInput(attrs={'type': 'date'}),
'second_origin_meeting_date': forms.DateInput(attrs={'type': 'date'})
}
class shareCapitalForm(updateCoopForm):
attach_share_capital = forms.FileField(label='Attach Share Capital',widget=forms.ClearableFileInput(attrs={'multiple': True}), required=False)
class Meta(updateCoopForm.Meta):
fields = updateCoopForm.Meta.fields
this is my CBV
class updateFormView(UpdateView):
model = CoopForm
form_class = shareCapitalForm
context_object_name = 'obj'
template_name = 'updateform.html'
def get_queryset(self):
return CoopForm.objects.all()
def get_success_url(self):
return reverse('formdetails', kwargs={'pk': self.object.id})
def get_context_data(self, **kwargs):
context = super(updateFormView, self).get_context_data(**kwargs)
if self.request.POST:
context['form'] = shareCapitalForm(self.request.POST, self.request.FILES, nstance=self.object)
else:
context['form'] = shareCapitalForm(instance=self.object)
return 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 = self.request.FILES.getlist('attach_share_capital')
if form.is_valid():
update = form.save(commit=False)
if files:
for f in files:
instance = shareCapitalFiles.objects.create(coopform=update, attach_share_capital=f)
instance.save()
messages.success(request, 'Files uploaded successfully')
#update.author = self.request.user
update.save()
return self.form_valid(form)
else:
return self.form_invalid(form)
this is my FBV
def updateformview(request, pk, *args, **kwargs):
# fetch the object related to passed id
obj = get_object_or_404(CoopForm, id=pk)
# pass the object as instance in form
form = shareCapitalForm(request.POST or None, request.FILES or None, instance=obj)
# get the template
template = loader.get_template('updateform.html')
# if the form is being updated
if request.method == 'POST':
# retrieve multiples files from model field
files = request.FILES.getlist('attach_share_capital')
# save the data from the form and
# redirect to detail_view
if form.is_valid():
update = form.save(commit=False)
update.save()
if files:
for f in files:
instance = shareCapitalFiles.objects.create(coopform=update, attach_share_capital=f)
instance.save()
return HttpResponseRedirect(reverse('formdetails', kwargs={'pk': id}))
context = {
'form': form,
'obj': obj
}
return HttpResponse(template.render(context, request))
this is the html code for the multiple files upload field
<div class="grid gap-6 mt-1 mx-auto md:grid-cols-2 lg:grid-cols-2 px-6 pt-4 pb-8 bg-stone-50 border border-1 border-gray-300 shadow-md">
<!-- Attach Share Capital -->
<div>
<label for="attach_share_capital" class="block mb-2 text-md font-bold text-gray-500">
{{ form.attach_share_capital.label }}
</label>
{% render_field form.attach_share_capital class+="cursor-pointer text-md md:text-md font-medium block rounded-sm w-full p-2 border border-2 border-gray-300 placeholder-gray-500 text-gray-600 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" %}
<p class="mt-1 text-sm text-gray-500">PDF, DOC (WORD), PNG OR JPG formats only. You can attach multiple files</p>
</div>
{% if form.errors.attach_share_capital %}
<span class="text-red-600 font-medium">{{ form.errors.attach_share_capital }}</span>
{% endif %}
</div>
I was able to figure out the problem. It was the ID field in my models.py. It was set to models.IntegerField instead of models.AutoField or models.BigAutoField, so it was expecting an input from the user instead of django automatically filling and incrementing it
# what caused the error
id = models.IntegerField(primary_key=True)
# solution
id = models.BigAutoField(primary_key=True)

django - iterate through model in create view & return inline formset

I'm trying to create a formset that will allow the user to set scores for each Priority when creating a new project with the CreateView. The Priority model already has data associated (3 values) foreach Priority that has been created I'm trying to return a char-field in the Project CreateView where the user can enter the score for the Priority.
Currently I have the three char-fields showing up in the Project CreateView but the priority isn't being saved. I have done some testing & it looks like the ProjectPriority is never having the Project or Priority values set only the score value.
I have been struggling for days trying to get this to work. I appreciate all the help.
Views.py
class ProjectCreateview(LoginRequiredMixin, CreateView):
model = Project
form_class = ProjectCreationForm
success_url = 'home/project/project_details.html'
def get_context_data(self, **kwargs):
ChildFormset = inlineformset_factory(
Project, ProjectPriority, fields=('priority', 'score'), can_delete=False, extra=Priority.objects.count(),
)
data = super().get_context_data(**kwargs)
if self.request.POST:
data['priorities'] = ChildFormset(self.request.POST, instance=self.object)
else:
data['priorities'] = ChildFormset(instance=self.object)
return data
def form_valid(self, form):
context = self.get_context_data()
priorities = context["priorities"]
self.object = form.save()
if priorities.is_valid():
priorities.instance = self.object
priorities.save()
return super().form_valid(form)
Models.py
class Priority(models.Model):
title = models.CharField(verbose_name="Title", max_length=250)
def __str__(self):
return self.title
class Project(models.Model):
name = models.CharField(verbose_name="Project Title", max_length=100)
details = models.TextField(verbose_name="Project Details/Description", blank=False)
priority = models.ManyToManyField(
Priority,
through='ProjectPriority',
related_name='priority'
)
creator = models.ForeignKey(User, on_delete=models.CASCADE)
class ProjectPriority(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
priority = models.ForeignKey(Priority, on_delete=models.CASCADE)
score = models.CharField(max_length=1000, choices=priority_choices)
class Meta:
verbose_name = "Priority"
verbose_name_plural = "Priorities"
def __str__(self):
return f"Priority: {self.priority.title}, Score: {self.score}, Project: {self.project.name}"
Template
{% block content %}
<div class="container-fluid" style="margin-top: 25px;">
<div class="row">
<div class="col-xl-8">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">New Project</legend>
{{ form|crispy }}
</fieldset>
<h2>Priority Criteria</h2>
{{ priorities|crispy }}
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Create
Project</button>
</div>
</form>
</div>
</div>
{% endblock content %}
In your form_valid method you have code like so:
def form_valid(self, form):
context = self.get_context_data()
priorities = context["priorities"]
self.object = form.save()
if priorities.is_valid():
priorities.instance = self.object
priorities.save()
return super().form_valid(form)
But trying to set the instance at this point (after calling is_valid) will not work because the forms have already been constructed when you call priorities.is_valid() which loops over the cached property forms of the formset which causes the forms to be constructed with the instance then being set to an empty one (because you didn't provide it yet). The solution would be simply to set the instance before calling is_valid. Also note the super().form_valid(form) will also save the object, you should instead return the response yourself there:
from django.http import HttpResponseRedirect
class ProjectCreateview(LoginRequiredMixin, CreateView):
# Other attributes and get_context_data here
def form_valid(self, form):
context = self.get_context_data()
priorities = context["priorities"]
self.object = form.save()
priorities.instance = self.object # Set the instance here
if priorities.is_valid():
priorities.save()
return HttpResponseRedirect(self.get_success_url())

ModelForm Upload Multiple Images: not saving images in static folder

I have been trying to code a Multi Upload for Images, my code only
uploads 1 image even though more than 1 is selected, I donĀ“t know how to iterate through, I did a print once the files were selected and my multiple images selected are printed, but when I save the form it only saves one image.
I basically trying to use the code that appear in the Django
documentation.
models.py
class Images(models.Model):
picture = models.ImageField(upload_to='media/photoadmin/pictures')
forms.py
class UploadImages(forms.ModelForm):
class Meta:
model = Images
fields = ('picture',)
widgets = {'picture': forms.ClearableFileInput(
attrs={'multiple': True})}
views.py
class Upload(FormView):
form_class = UploadImages
template_name = 'photoadmin/upload.html'
success_url = 'photoadmin/'
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('picture')
if form.is_valid():
form.save()
for f in files:
file_instance = Images(picture=f)
file_instance.save()
return render(request, 'photoadmin/index.html')
else:
return render(request, 'photoadmin/index.html')
html
{% extends 'base.html' %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Upload</button>
</form>
<p>Return to home</p>
{% endblock %}
This code write in the DB but do not uploads the file to the static
folder
You might need to save every file individually, using FileSystemStorage
from django.core.files.storage import FileSystemStorage
...
class Upload(FormView):
form_class = UploadImages
template_name = 'photoadmin/upload.html'
success_url = 'photoadmin/'
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('picture')
if form.is_valid():
fs = FileSystemStorage()
for file in files:
fs.save(file.name, file)
return render(request, 'photoadmin/index.html')
else:
return self.form_invalid(form)
So, this is finally the solution.
views.py
class UploadView(generic.CreateView):
form_class = UploadImages
model = PostSession
template_name = 'photoadmin/upload.html'
success_url = reverse_lazy('upload')
def form_valid(self, form):
object = form.save(commit=False)
form.save()
if self.request.FILES:
for afile in self.request.FILES.getlist('picture'):
img = object.images.create(picture=afile)
return super(UploadView, self).form_valid(form)
with that you are able to iterate
models.py
class PostSession(models.Model):
session_name = models.CharField(max_length=25)
def __str__(self):
return str(self.session_name)
class Images(models.Model):
name = models.ForeignKey(
PostSession, related_name='images', on_delete=models.CASCADE, null=True, blank=True)
picture = models.ImageField(upload_to='pictures')
Hope this helps the community!

How to render a POST and make it show up on another page? in Django form

I'm trying to create Car Rental website similar to Hyrecar. I created a form according to the Django tutorial "Working with forms", but I don't know how to render information I got from the POST forms. I want to make information(Booking Name,rental price ...etc) that I got from POST show up the car and its detail which is booked. Car is foreign key field in booking.i want to redirect it to the page showing that car which is booked .
for eg
Booking name : xyz
Rental Price : 123
CAr : carimage.jpg
4 .
I want to redirect it to the page pop. if the user booked a car and post the form . after that redirect it to the pop.html page and show the booking detail that the user posted now .
Forms.py
class BookingForm(ModelForm):
class Meta:
model = Booking
widgets = {
'times_pick': forms.TimeInput(attrs={'class':'timepicker'}),
}
fields = ('booking_name','rental_price','book_car','customer_name','times_pick',)
urls.py
[
url(r'^booking/',views.BookingView.as_view(),name='car_booking'),
url(r'^pop/$',views.PopView.as_view(),name='pop'),
]
views.py
class CarDetailView(DetailView):
context_object_name = 'car_details'
model = models.Car
template_name = 'buggy_app/car_detail.html'
class BookingView(FormView):
template_name = 'buggy_app/booking.html'
form_class = BookingForm
models = Booking
def form_valid(self, form):
form.save()
return super(BookingView, self).form_valid(form)
success_url = reverse_lazy('index')
def get_context_data(self, **kwargs):
# kwargs['car'] is the car booking now!
try:
kwargs['car'] = Car.objects.get(id=self.request.GET.get('car', ''))
except (Car.DoesNotExist, ValueError):
kwargs['car'] = None
return super(BookingView, self).get_context_data(**kwargs)
def get_initial(self):
initial = super(BookingView, self).get_initial()
if 'car' in self.request.GET:
try:
initial['book_car'] = Car.objects.get(id=self.request.GET['car'])
except (Car.DoesNotExist, ValueError):
pass
return initial
booking.html
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" class='btn btn-primary' value="Submit">
</form>
Try like this
def save_form(request):
args = {}
form = BookCarForm(request.POST)
if form.is_valid():
book = form.save(commit=False)
book.book_car_mark = request.POST.get('car_mark')
book.book_car_mmodel = request.POST.get('car_model')
book.book_car_year = request.POST.get('car_year')
book.book_car_mark = request.POST.get('car_mark')
form.save()
try:
args['book'] = Book.objects.get(id=book.id)
except:
args['book'] = None
if args['book'] is not None:
return render(request, 'your_template.html', args)
else:
return HttpResponseRedirect('/your/url/to-booking-form/')
Name of fields as name of models and templates are abstract, so it's just a working mechanism scheme

Forms from models

I have different models:
Cars
Pictures (models.ForeignKey(Cars))
CarsOptions(models.OneToOneField(Cars))
Then I want, that user can add new cars. Forms.py:
class NewCarsForm(ModelForm):
class Meta:
model = Cars
exclude = ('checked','user')
In views.py:
#login_required
def add_car(request):
form = NewCarsForm(request.POST or None)
if form.is_valid():
cmodel = form.save()
cmodel.save()
return redirect(profile)
return render(request, 'add_car.html', { 'form': form,})
I have a few questions (yes, I read this).
As you can see, user can add only "car", not pictures and options. How to do this on one page in one form?
exclude = ('checked','user'), it means, that I want to make all new positions with checked=0, so I can filter them. And the user-field - users cant choose user in a list. How to take user id (who adds car) in form.save()?
Thanks.
Update:
class Pictures(models.Model):
cars = models.ForeignKey(Cars, related_name='pictures')
width = models.PositiveIntegerField(editable=False, default=640)
height = models.PositiveIntegerField(editable=False, default=480)
image = models.ImageField(upload_to=lambda inst, fname: 'users_img/' + 'cars' + fname, height_field='height', width_field='width', max_length=100)
def __unicode__(self):
return str(self.id)
forms.py
class NewCarsForm(ModelForm):
class Meta:
model = Cars
exclude = ('checked','user')
PicturesFormset = inlineformset_factory(Cars, Pictures,
fields=('field_name', 'field_name'), can_delete=True)
CarsOptionsFormset = inlineformset_factory(Cars, CarsOptions,
fields=('field_name', 'field_name'), can_delete=True)
views.py
#login_required
def add_car(request):
cars = Cars(user=request.user)
if request.method == 'POST':
form = NewCarsForm(request.POST, instance=cars)
if form.is_valid():
cmodel = form.save()
picture_formset = PicturesFormset(request.POST, request.FILES,
instance=cmodel)
if picture_formset.is_valid():
picture_formset.save()
option_formset = CarsOptionsFormset(request.POST, instance=cmodel)
if option_formset.is_valid():
option_formset.save()
return redirect(profile)
form = NewCarsForm()
picture_formset = PicturesFormset(instance=Cars())
option_formset = CarsOptionsFormset(instance=Cars())
return render(request, 'add_car.html', {
'form': form,
'picture_formset': picture_formset,
'option_formset': option_formset,
})
template
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<h4>Car:</h4>
{{ form.as_p }}
<h4>Picture:</h4>
{{ picture_formset.as_p }}
<h4>Options:</h4>
{{ option_formset.as_p }}
<input type="submit" value="Submit">
</form>