Change text input size for fields called inside for loop

i have two text input fields - title and body

            {% for field in postForm %}
                {{ field }} 
            {% endfor %}

and I have formset with 3 images utilizing the modelformsetfactory

    {{ formset.management_form }}
            {% for form in formset %}
                {{ form }}
            {% endfor %}

Couple of questions:

  1. How to change the size of the text box of the body field? I want a bigger text input box. Not sure how to adjust the box size from inside the for loop. Should I use the _id ?

  2. In my views.py for the add post, I use postform.is_valid and formset.is_valid and I also check for formset.cleaneddata. I need help to figure out how to ignore if images are not uploaded or if i upload only 2 images instead of 3 images.

  1. You don’t make these sorts of changes from within the template. They’re made within your form by setting attributes on the field. (See Styling Widget Instances)

  2. There really shouldn’t be anything special you need to do here. Unused blank form instances within a formset don’t cause problems. If you’re having a problem with this, please post the code for your forms and view(s) and we can try to help.

Ken

There are two parts to the form: POSTFORM which has title field and body field & FORMSET which has the three images uploaded. The code is checking if both parts of the form are valid using the .is_valid() method with an AND operator. Should we instead check if each of the forms are valid separately independent from each other?

    if postform.is_valid() and formset.is_valid():
        post_form = postform.save(commit=False)
        post_form.user = request.user
        post_form.save()
        for form in formset.cleaned_data:
            image = form['image']
            photo = Images(post=post_form, image=image)
            photo.save()
        messages.success(request, "Posted!")
        return HttpResponseRedirect("http://127.0.0.1:8000/")
    else:
        print(postform.errors, formset.errors)

Thanks Ken. The widget “textarea” is useful. Thanks for the suggestion.

Using this code makes bigger text boxes:

class ExampleForm(forms.Form):
	name = forms.CharField(widget=forms.Textarea(attrs={'rows':8}))

From Formset Validation: There is an is_valid method on the formset to provide a convenient way to validate all forms in the formset

If the formset is a field in the form, the is_valid method of the form will call the is_valid method of the formset, there’s no need to call it separately.

I am getting an error at the line :

image = form[‘image’]

Below is the form required fields. If I dont upload any images to the post then error is generated.
Is it mandatory required to use the formset.cleaned_data?

POST

Variable Value
csrfmiddlewaretoken ‘xcsdfegfjktiyojplmxvxfrq’
title ‘sss’
body ‘sdfsfsfsssvsvs’
form-TOTAL_FORMS ‘3’
form-INITIAL_FORMS ‘0’
form-MIN_NUM_FORMS ‘0’
form-MAX_NUM_FORMS ‘1000’
form-0-image ‘’
form-0-id ‘’
form-1-image ‘’
form-1-id ‘’
form-2-image ‘’
form-2-id ‘’
submit ‘Submit’

You are correct. The forms within a formset are forms. You work with the instances of them the same way you would work with any other form. (Although it’s in a different context, see the example in formset validation.

Thanks @KenWhitesell
Instead of for loop I used form.media and form.as_p and it worked fine. I do get an error page when I press the submit button, but the blog post form data is uploaded fine without issues.

Also, I have another question regarding User management and filtering posts by current logged in user.

Models.py
from django.contrib.auth.models import User

class Post(models.Model):
    title = models.CharField('Title', max_length=200)
    body = RichTextField(blank=True, null=True)
    author = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
    date = models.DateField(auto_now_add=True)

Using shell commands I get:

>>> User.objects.filter(id=6)
<QuerySet [<User: john>]>

>>> Post.objects.filter(author__id=6)
<QuerySet []>

My question is, if author is connected to User as foreignkey, then User instance with id=6 should match author instance with id=6, correct?

so is it acceptable to do the following views.py code?

def blog_index_view(request):
    current_user = request.user.id
    posts = Post.objects.filter(author_id=current_user)
    context = {"posts": posts}
    return render(request, "blog_index_html.html", context)

I tried filtering using both author_id or author__id but none of my blog posts are showing up in the blog_index page.

What am I doing wrong?

>>> posts=Post.objects.all()
>>> posts[0]
<Post: Post object (1)>


>>> posts[0].__dict__
{'_state': <django.db.models.base.ModelState object at 0x7f334rr3>, 
'id': 1, 
'title': '1', 
'body': '111111', 
'author_id': None, 
'date'**: datetime.date(2020, 9, 30)}

When I lookp properties of the first blog post, I find that author_id = None is displayed. Why wouldnt the author_id match with the currently logged user and match with the user_id who wrote this post?

That Post instance has no author value to filter on. Either it was created without a value, the value was deleted, or the author field was added after the instance of the post existed causing it to be null.

If your use case is that all posts need authors, then you should clean up the existing instances. You can find them via Post.objects.filter(author__isnull=True). Once all posts have an author value, you can change the author field on Post to be not null:

class Post(models.Model):
    ...
    author = models.ForeignKey(..., null=False, on_delete=models.CASCADE)

Please note that null above is optional as it defaults to False. The on_delete value is subjective, PROTECT may also work.

Please review the documentation of the form save() method. The form.save(commit=False) call doesn’t return the form.

>>> users=User.objects.all()
    
>>> users
    <QuerySet [<User: john>, <User: matt>]>


>>> users[0].__dict__
{ 'id': 6, 
'is_superuser': True,
 'username': 'john', 
'is_staff': True, 
'is_active': True}

Since ‘id’ for john is 6, can I do this below?
Can I go into each post and manually set the author_id to match john’s id?

`>>> posts[0].author_id = 6

>>> posts[0].__dict__
{'id': 1,
 'title': '1',
 'body': '111111',
 'author_id': None, 
'date': datetime.date(2020, 9, 30)}

If you’re looking to correct what has already been entered, take a look at the bulk_update method.

Thanks! @KenWhitesell @CodenameTim

I did the update using update() method.

>>> Post.objects.filter(author__isnull=True).update(author_id=6)

I have veriied that the author field now has an ID for every instance of the Post class.

>>> Post.objects.filter(author__isnull=False)[0].__dict__
{ 'id': 1, 
'title': '1',
 'body': '111111',
 'author_id': 6,
 'date': datetime.date(2020, 9, 30)}

Now as per the requirement I am able to view only posts made by the current logged in user. Thanks again for the help!

Hi @KenWhitesell I have a question regarding the following code in the views.py

I get this error below:

ValueError at /myblog/postnew/new/
Cannot assign ‘“PostForm bound=True, valid=True, fields=(title;body)”’: “Images.post” must be a “Post” instance.

when my views.py code is as below:

        if postform.is_valid() and formset.is_valid():
            postform.instance.author = request.user
            postform.save()
            for form in formset.cleaned_data:
                Images(post=postform, image=form['image']).save()
            messages.success(request, "Posted!")

But when I modify this code as shown below, the ValueError goes away.

    if postform.is_valid() and formset.is_valid():
        postform.instance.author = request.user
        post_form = postform.save()
        post_form.save()
        for form in formset.cleaned_data:
            Images(post=post_form, image=form['image']).save()
        messages.success(request, "Posted!")

Why do I have to add a temp buffer object “post_form” to avoid the above mentioned valueError?

Keep in mind the difference between a Form and a Model.

Post is a model. I’m going to guess that the post attribute of your Images model is an FK to an instance of Post.

postform is a Form, it’s not an instance of Post. Now, the most important thing to remember here is that the save() method of a form returns the instance of the model being saved, not the form. Your variable post_form isn’t a form. It’s an instance of Post - and that’s why it works.

Your first example should work if you changed the reference to postform in your Images constructor to postform.instance, just like you did with your reference to author 4 lines above it.

@KenWhitesell Thanks a lot for the detailed explanation! Thanks.