I have a listing model and a photo model:
class Listing(models.Model):
title = models.CharField(max_length=255)
<more fields>...
class Photo(models.Model):
image = models.ImageField(upload_to=create_file_path)
listing = models.ForeignKey(Listing, related_name='photos')
I am using a CBV, UpdateView, to edit a listing. I am using this form:
class ListingDetailForm(forms.ModelForm):
class Meta:
model = Listing
exclude = []
and the inline formset in forms.py to make deleting/changing the image possible:
PhotoFormset = inlineformset_factory(Listing, Photo, fields='__all__', extra=1)
here is my view:
class ListingDetailView(UpdateView):
model = Listing
template_name = 'listing/listing_detail.html'
form_class = ListingDetailForm
success_url = '/store/'
def get_context_data(self, **kwargs):
self.object = self.get_object()
context = super(ListingDetailView, self).get_context_data(**kwargs)
if self.request.POST:
context['form'] = ListingDetailForm(self.request.POST, instance=self.object)
context['photo_form'] = PhotoFormset(self.request.POST, self.request.FILES, instance=self.object)
else:
context['form'] = ListingDetailForm(instance=self.object)
context['photo_form'] = PhotoFormset(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)
photo_form = PhotoFormset(self.request.POST)
print photo_form.is_valid()
if form.is_valid() and photo_form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
print 'in form valid for update'
context = self.get_context_data()
base_form = context['form']
photo_form = context['photo_form']
# print base_form
# print photo_form
if base_form.is_valid() and photo_form.is_valid():
print 'forms are valid for update'
base_form.save()
photo_form.save()
return super(ListingDetailView, self).form_valid(form)
else:
return self.render_to_response(self)
and the relevant template section:
{% block body %}
<form action="" method="post">
{% csrf_token %}
{% for field in form %}
{{ field.errors }}
{{ field.label_tag }} {{ field }}<br><br>
{% endfor %}
{% for field in photo_form %}
{{ field.errors }}
{{ field.label_tag }} {{ field }}<br><br>
{% endfor %}
{{ photo_form.management_form }}
<input type="submit" value="Update" />
</form>
{% endblock %}
The issues I am having are:
1) If there is a photo attached to the listing, through the admin, the photo form does not pass validation if I do nothing with the photo form, e.g. change only fields from the listing model. The photo form displays no errors when the page reloads after invalid.
2) selecting a new photo does not change the current photo, the photo form does not validate and displays no errors.
3) if there is currently no photo related to the listing trying to add one validates through the form but does not actually save a photo related to that listing.
Deleting an image, if there is one attached to the listing, works just fine. Deleting the image and updating some other field from the listing works. If there is no image updating only a listing field works. Adding a second photo to the listing through the form does not work and displays no form errors.
There are a few issues I noticed with your form.
You need to include enctype="multipart/form-data" on your form attributes or else you won't be able to post file data to the server
I would use the Django methods for rendering the form (form.as_p, form.as_table or form.as_ul) if you absolutely need to use manual rendering then follow the official guide: model formsets
On the post method your formset is missing FILES and instance
Once you implement these changes your formset should work just fine.
Related
I used to send form as content in function-base-view and I could use for loop to simply write down fields and values like:
{% for x in field %}
<p>{{ x.label_tag }} : {{ x.value }} </p>
I don't remember whole the way so maybe I wrote it wrong but is there anyway to do this with class-based-views, because when I have many fields its really hard to write them 1by1
Not entirely sure if this is what you need. But still I will try to answer. I took an example with class AuthorDetail(FormMixin, DetailView) as a basis. In get_context_data saved the form itself. In the template, first I displayed the form, then the value from the bbs model and requested
form.message.label_tag. To get the tag, I looked at this documentation.
In the class, replace the Rubric model with your own. In the template path: bboard/templ.html replace bboard with the name of your application where your templates are located.
views.py
class Dw(FormMixin, DetailView):
model = Rubric
template_name = 'bboard/templ.html'
form_class = TestForm
context_object_name = 'bbs'
def get_context_data(self, **kwargs):
context = super(Dw, self).get_context_data(**kwargs)
context['form'] = self.get_form()
print(77777, context['form']['message'].label_tag())
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
forms.py
class TestForm(forms.Form):
message = forms.CharField()
templates
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="adding">
</form>
<h2>{{ bbs }}</h2>
<h2>{{ form.message.label_tag }}</h2>
urls.py
urlpatterns = [
path('<int:pk>/', Dw.as_view(), name='aaa'),
]
I'm trying to allow the user to upload images tied to a project. I'm doing this via an inlineformset_factory.
Im serving the form to the user trough a function based view. When the user fails to fill in one (or more) of the formsets correctly formset.is_valid() returns false and the bound formsets get returned to the user along with error messages. What i'm having trouble with is that the imagefield is not returned to the bound form. So if the user has filled in 2 out of 3 forms correctly then neither of the formsets have a bound imagefield after the failed validation. I can't manage to figure out if this is working as intended or if i'm doing something wrong. If i print out request.FILES i get the following:
<MultiValueDict: {'image_set-0-filename': [<InMemoryUploadedFile: IMG_0017.jpg (image/jpeg)>], 'image_set-1-filename': [<InMemoryUploadedFile: sprite.svg (image/svg+xml)>]}>
I would like the imagefield to be bound and show the user what value the field has.
before incorrect form submission
after incorrect form submission
Hopefully you can see where my mistake is or tell me if i'm thinking about this the wrong way.
Thank you in advance
forms.py
class EndUserImageForm(forms.ModelForm):
class Meta:
model = Image
fields = ["filename", "description"]
widgets = {
"filename": forms.FileInput(attrs={"class": "form__field no--outline"}),
"description": forms.Textarea(attrs={"class": "form__field no--outline", "placeholder": "Description"})
}
EndUserImageInlineFormset = inlineformset_factory(
Project, Image, form=EndUserImageForm, extra=2)
views.py
def image_formset(request, pk):
project = get_object_or_404(Project, pk=pk)
image_formset = EndUserImageInlineFormset(request.POST or None, request.FILES or None)
context = {
"image_formset": image_formset
}
if request.method == "POST":
if image_formset.is_valid():
images = image_formset.save(commit=False)
for image in images:
image.project = project
image.save()
return HttpResponseRedirect(reverse("projects:home"))
return render(request, "projects/test.html", context)
template
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<h4>Add Images</h4>
{{ image_formset.non_form_errors }}
{{ image_formset.management_form }}
<div id="image-form-list">
{% for form in image_formset %}
{{ form.errors }}
<div class="image_form">
{{ form.filename }}
{{ form.description }}
</div>
{% endfor %}
</div>
<input type="submit" value="Save" class="button button--main activate"/>
</form>
models.py
class Image(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
filename = models.ImageField(upload_to=project_directory_path, blank=False)
description = models.CharField(max_length=350, blank=True)
created = models.DateTimeField(auto_now_add=True)
def image_formset(request, pk):
project = get_object_or_404(Project, pk=pk)
image_formset = EndUserImageInlineFormset()
if request.method == "POST":
image_formset = EndUserImageInlineFormset(request.POST or None, request.FILES or None)
if image_formset.is_valid():h
images = image_formset.save(commit=False)
for image in images:
image.project = project
image.save()
views.py
def post(request):
if request.method == 'POST':
form = PostModelForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.user = request.user
post.save()
# using the for loop i am able to save the tags data.
# for tag in form.cleaned_data['tags']:
# post.tags.add(tag)
images = request.FILES.getlist('images')
for image in images:
ImagesPostModel.objects.create(post=post, images=image)
return redirect('/Blog/home/')
else:
form = PostModelForm(request.POST)
return render(request, 'post.html', {'form': form})
models.py
class PostModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
date_time = models.DateTimeField(auto_now_add=True)
title = models.TextField(null=True)
body = models.TextField(null=True)
tags = TaggableManager()
def __str__(self):
return str(self.user)
post.html
{% extends 'base.html' %}
{% block content %}
<form action="{% url 'post' %}" enctype="multipart/form-data" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="file" multiple name="images">
<input type="submit">
</form>
{% endblock %}
After giving the input the data is stored in the tags field but, not saving in the database.
I can manually insert data through the admin panel successfully but not as a non-staff user.
I have installed taggit and placed it in the installed_apps in settings.py.
Tags are being saved using post.tags.add(tag) inside for loop. What is the issue with the code?
This is because you use commit=False for the form: then the form has no means to save the many-to-many fields. It is also not necessary to do that, you can work with:
def post(request):
if request.method == 'POST':
form = PostModelForm(request.POST)
if form.is_valid():
form.instance.user = request.user # set the user
post = form.save() # save the form
ImagesPostModel.objects.bulk_create([
ImagesPostModel(post=post, images=image)
for image in request.FILES.getlist('images')
])
return redirect('/Blog/home/')
else:
form = PostModelForm()
return render(request, 'post.html', {'form': form})
Note: Models normally have no Model suffix. Therefore it might be better to rename PostModel to Post.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
I have created a OnetoOne model for some user preference checkboxes. This is mapped to the User, and using signals I create it when the user is created. Here is what I have so far:
Model:
class DateRegexes(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
prefix_1 = models.NullBooleanField()
prefix_2 = models.NullBooleanField()
prefix_3 = models.NullBooleanField()
prefix_4 = models.NullBooleanField()
prefix_5 = models.NullBooleanField()
prefix_6 = models.NullBooleanField()
prefix_7 = models.NullBooleanField()
prefix_8 = models.NullBooleanField()
prefix_9 = models.NullBooleanField()
#receiver(post_save, sender=User)
def create_date_regexes(sender, instance, created, **kwargs):
if created:
DateRegexes.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_date_regexes(sender, instance, **kwargs):
instance.date_prefs.save()
Form:
class DatePreferenceForm(forms.ModelForm):
class Meta:
model = DateRegexes`
View:
#login_required
def set_date_preferences(request):
if request.method == 'POST':
form = DatePreferenceForm(request.POST)
if form.is_valid():
form.save()
else:
date_prefs = get_object_or_404(DateRegexes, user=request.user)
form = DatePreferenceForm(instance = request.user.date_prefs)
return render(request, 'set_date.html', {'form': form})
Template:
{% extends 'base.html' %}
{% block title %}Date Preferences{% endblock %}
{% block content %}
{% if user.is_authenticated %}
<p>logout</p>
<form method="post">
{% csrf_token %}
{% for field in form.visible_fields %}
<div>here {{ field }}</div>
{% endfor %}
<button type="submit">Set</button>
</form>
</div>
{% else %}
<p>You are not logged in</p>
login
{% endif %}
{% endblock %}
When I execute this, the first thing that happens is that the expected checkboxes that are filled in according to their previously selected preferences, I get a bunch of dropboxes that each say:
"Unknown", "Yes", "No".
1) How can I get it show html checkboxes instead of the dropboxes?
2) When I submit the form I get:
IntegrityError at /db/set_date_preferences/
NOT NULL constraint failed: db_dateregexes.user_id
Which I understand that it is having trouble associating my form to the logged in user, but I'm not sure what the right way to associate this should be
You're using a NullBooleanField which can have 3 values: None, True or False. So the default widget to render such a field is a dropdown. If you want a checkbox you should decide what you want None to map to and override the fields in the DatePreferenceForm to be a BooleanField.
Second, since every user already has prefs you should initialise your form with the correct instance, even in the POST case. I don't really understand how your form can be valid since user is a required field (you should really exclude it from the form).
class DatePreferenceForm(forms.ModelForm):
class Meta:
model = DateRegexes
exclude = ['user']
# View
def set_date_preferences(request):
if request.method == 'POST':
form = DatePreferenceForm(request.POST, instance=request.user.date_prefs)
if form.is_valid():
form.save()
# would be good practice to redirect to a success page here
else:
date_prefs = get_object_or_404(DateRegexes, user=request.user)
form = DatePreferenceForm(instance = request.user.date_prefs)
return render(request, 'set_date.html', {'form': form})
Hey i am trying to use modelchoicefield to get a dropdown list in html. But the submission of form yields a invalid form. My code is given below.
views.py
class SubjectSelectFormView(View):
form_class = SubjectSelectForm
template_name = 'study/select_subject.html'
def get(self, request):
form = self.form_class(user=request.user)
return render(request, self.template_name, {'form':form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
subject = models.Subject.objects.get(name=form['name'])
return HttpResponseRedirect('study:quiz', subject.subject_id)
else:
return HttpResponse('<h1>Failed</h1>')
forms.py
class SubjectSelectForm(forms.Form):
name = forms.ModelChoiceField(queryset=Subject.objects.all().order_by('name'), widget=forms.Select())
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(SubjectSelectForm,self).__init__(*args, **kwargs)
self.fields['name'].queryset = Subject.objects.filter(user_id=user)
html
{% extends 'basic_home_app/base.html' %}
{% block content %}
<br>
<form class="form-horizontal" action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Start">
</form>
{% endblock %}
First you should always render the same template with the bound form when a posted form is found to not be valid, this way you can display errors to the user:
def post(self, request):
form = ...
if form.is_valid():
...
else:
return render(request, self.template_name, {'form':form})
Inside your template, you can display errors using either:
{{ form.errors }} # all form errors
{{ form.non_field_errors }} # form errors that aren't for one specific field, use this if you're displaying the field errors separately
or
{{ form.name.errors }} # just the errors for one specific field
Second, I assume you want to initialise your form the same way when it's posted as when it's first displayed (empty) to the user via the get() request:
def post(self, request):
form = self.form_class(request.POST, user=request.user) # note the user
Otherwise your form.__init__() method will set as queryset only Subject objects where user_id is None.