Object has no attribute 'cleaned_data' after validation in Django view - python

I'm trying to make a registration page for my project. Standard model User is not enough for me, so I created another model named Profile:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
sex = models.CharField(max_length=1, choices=SEX_CHOICES, default='М',
verbose_name='Пол')
third_name = models.CharField(max_length=30, verbose_name='Отчество')
grade = models.CharField(max_length=2, choices=GRADE_CHOICES, default='1',
verbose_name='Класс обучения')
age = models.PositiveSmallIntegerField(default=7, verbose_name='Возраст',
validators=[
MaxValueValidator(100),
MinValueValidator(4)
]
)
school = models.ForeignKey(School, on_delete=models.PROTECT,
verbose_name='Учебное заведение',
blank=True, null=True, default=None)
interest = models.ManyToManyField(OlympiadSubject,
verbose_name='Интересующие предметы')
On both User and Profile I created a ModelForm:
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name')
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('third_name', 'sex', 'grade', 'age', 'school', 'interest')
And wrote a view for registration:
def create_profile(request):
if request.method == 'POST':
user_form = UserForm()
profile_form = ProfileForm()
if user_form.is_valid() and profile_form.is_valid():
User.objects.create(**user_form.cleaned_data)
Profile.objects.create(**profile_form.cleaned_data, user=User)
return redirect('/login')
else:
profile_form.add_error('__all__', 'Данные введены неверно!')
else:
user_form = UserForm()
profile_form = ProfileForm()
return render(request, 'users/profile_create.html', {
'user_form': user_form,
'profile_form': profile_form
})
and of course I have a template for that:
{% extends 'main/layout.html' %}
{% block title %} Some text {% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ user_form.as_p }}
{{ profile_form.as_p }}
<button type="submit">Сохранить изменения</button>
</form>
{% endblock %}
but i get the same error every time:
ProfileForm object has no attribute cleaned_data
Don't know how to fix it.
This is my first question on stackoverflow. Sorry, if I wrote something incorrect.

When you define the forms in case of POST request, you don't bind the data to the forms. Since the forms you have created are empty, there is no cleaned_data.
Try
if request.method == 'POST':
user_form = UserForm(request.POST) # the request.POST part binds the data to the form
profile_form = ProfileForm(request.POST)

You are missing to capture the form data that are coming with the POST request.
You may write:
if request.method == 'POST':
user_form = UserForm(request.POST)
profile_form = ProfileForm(request.POST)

Related

Form returning not valid in django

In my django app I have a User model that has a Boolean field of is_manager.
User model in models.py:
class User(AbstractUser):
name = models.CharField(max_length=15, null=True, blank=True)
last_name = models.CharField(max_length=15, null=True, blank=True)
title = models.CharField(max_length=50, null=True, blank=True)
email = models.EmailField(unique=True)
bio = models.TextField(null=True, blank=True)
company = models.ForeignKey(Company, on_delete=models.DO_NOTHING, null=True)
is_manager = models.BooleanField(default=False)
can_assign = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
I've been trying to create an edit page in order for managers and users to be able to change some of their fields.
Regular users should be able to change their bio and title, and the managers can change the can_assign Boolean.
I have a form that deals with the logic of that in forms.py:
class EditUserForm(ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
if self.user.is_manager:
super().__init__(**kwargs)
else:
super().__init__(**kwargs)
del self.fields['can_assign']
class Meta:
model = User
fields = ['title', 'can_assign', 'bio']
views.py:
#login_required
def editUser(request, pk):
user = User.objects.get(id=pk)
if request.user.is_manager or request.user == user:
#POST
if request.method == 'POST':
form = EditUserForm(request.POST, instance=user, user=request.user)
if form.is_valid():
form.save()
redirect('user-page', pk)
else:
print('nope')
#GET
form = EditUserForm(user=request.user, instance=user)
context = {'user': user, 'form': form}
return render(request, 'users/user_edit.html', context)
else:
return HttpResponse('<h1>Access Denied</h1>')
template:
{% extends 'main.html' %}
{% block content %}
<form method="POST" action="">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Submit">
</form>
{% endblock content %}
for some reason the form.is_valid() method returns False. I have no idea why.
I have tried to use the .errors method on the form and on the form fields. No errors shown.
Thanks for any help!
" Oh! I completely missed that. I think the *args is required because that's how you pass in the request.POST. if you would have an explicit key like myform(data=request.POST) it would have worked because it would be in the *kwargs .. So it was basically failing cause it was acting like you were just initiating a new form, not submitting one –
Nealium
"

Django formset creates multiple inputs for multiple image upload

I am trying to create a simple post sharing form like this one.
I'm using formset for image upload. But this gives me multiple input as you can see. Also each input can choose single image. But I'm trying to upload multiple image with single input.
views.py
def share(request):
ImageFormSet = modelformset_factory(Images,
form=ImageForm, extra=3)
# 'extra' means the number of photos that you can upload ^
if request.method == 'POST':
postForm = PostForm(request.POST)
formset = ImageFormSet(request.POST, request.FILES,
queryset=Images.objects.none())
if postForm.is_valid() and formset.is_valid():
post = postForm.save(commit=False)
post.author = request.user
post.save()
for form in formset.cleaned_data:
# this helps to not crash if the user
# do not upload all the photos
if form:
image = form['image']
photo = Images(post=post, image=image)
photo.save()
return redirect("index")
else:
print(postForm.errors, formset.errors)
else:
postForm = PostForm()
formset = ImageFormSet(queryset=Images.objects.none())
return render(request, "share.html", {"postForm": postForm, 'formset': formset})
share.html
<form method="POST" id="post-form" class="post-form js-post-form" enctype="multipart/form-data">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
if you need, forms.py
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title", "content"]
def __init__(self, *args, **kwargs):
super(PostForm, self).__init__(*args, **kwargs)
self.fields['title'].widget.attrs.update({'class': 'input'})
self.fields['content'].widget.attrs.update({'class': 'textarea'})
class ImageForm(forms.ModelForm):
image = forms.ImageField(label='Image')
class Meta:
model = Images
fields = ('image', )
def __init__(self, *args, **kwargs):
self.fields['image'].widget.attrs.update(
{'class': 'fileinput', 'multiple': True})
models.py
from django.db import models
from django.contrib.auth.models import User
from django.template.defaultfilters import slugify
class Post(models.Model):
# on_delete ile, bu kullanıcı silindiğinde bu kullanıcıya ait tüm postlar da silinecek.
author = models.ForeignKey(
"auth.User", on_delete=models.CASCADE, verbose_name="Yazar")
title = models.CharField(max_length=150, verbose_name="Başlık")
content = models.TextField(verbose_name="İçerik")
# auto_now_add = True ile veritabanına eklendiği tarihi otomatik alacak
created_date = models.DateTimeField(auto_now_add=True)
# admin panelinde Post Object 1 yazması yerine başlığı yazsın istersek...
def __str__(self):
return self.title
def get_image_filename(instance, filename):
title = instance.post.title
slug = slugify(title)
return "post_images/%s-%s" % (slug, filename)
class Images(models.Model):
post = models.ForeignKey(
Post, on_delete=models.DO_NOTHING, default=None)
image = models.ImageField(upload_to=get_image_filename,
verbose_name='Image', default="images/default_game_img.png")
If you need one filed for multiple image upload, try this:
views.py
from .forms import PostForm
from .models import Post, Images
def share(request):
form = PostForm()
if request.method == 'POST':
post = Post()
post.title = request.POST['title']
post.content = request.POST['content']
post.author = request.user
post.save()
for image in request.FILES.getlist('images'):
image_obj = Image()
image_obj.post_id = post.id
image_obj.image = image
image_obj.save()
return render(request, 'share.html', {'form': form})
forms.py
from django import forms
class PostForm(forms.Form):
title = forms.CharField(label='',
widget=forms.TextInput(attrs={
'class': 'input',
}
))
content = forms.CharField(label='',
widget=forms.Textarea(attrs={
'class': 'textarea',
}
))
images = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
share.html
<form method="POST" id="post-form" class="post-form js-post-form" enctype="multipart/form-data">
{% csrf_token %}
{% for elem in form %}
{{ elem }}
{% endfor %}
</form>
I think you are over thinking the implementation you should try lib like django-multiupload
And can find valid implementation for multiple image upload through this link
https://stackoverflow.com/a/44075555/10798048
Hope this will ease your implementation and solve your problem. Correct me if it doesn't work out for you.

How to update extended User Model using Django Forms?

I am new to Django and need a help.
I want to allow users to update their account data using form, but struggle with associating Django User model with my UserProfile model, which extends default model with some additional fields.
I found, that solution is to create my own model form, but unfortunately I'm not exactly sure how to implement it.
models.py:
class UserProfile(models.Model):
user = models.OneToOneField(User)
description = models.CharField(max_length=100, default='')
city = models.CharField(max_length=100, default='')
website = models.URLField(default='')
phone = models.IntegerField(default=0)
image = models.ImageField(upload_to='profile_image', blank=True)
def __str__(self):
return self.user.username
def create_profile(sender, **kwargs):
if kwargs['created']:
user_profile = UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_profile, sender=User)
forms.py:
class EditProfileForm(UserChangeForm):
image = forms.ImageField(required=False)
city = forms.CharField(required=False)
class Meta:
model = User
fields = (
'email',
'first_name',
'last_name',
'password',
'image',
'city'
)
views.py:
def edit_profile(request):
if request.method == 'POST':
form = EditProfileForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
return redirect(reverse('accounts:view_profile'))
else:
form = EditProfileForm(instance=request.user)
args = {'form': form}
return render(request, 'accounts/edit_profile.html', args)
edit_profile.html:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
You are looking for Inline formsets. Just read the docs, it's pretty simple.

Django model nullable field error makemigrations

Hello I'm building my own blog and I've been trying to add photos to my blog posts. For now, I can upload photos to the media/documents folder however I'm having problems assigning these photos to posts.
This is how my models.py looks like
class Document(models.Model):
post = models.ForeignKey('blog.Post', related_name='documents', null = True)
description = models.CharField(max_length=255, blank=True)
document = models.FileField(upload_to='documents/')
uploaded_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.text
forms.py
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
fields = ('description', 'document')
views.py
#login_required
def model_form_upload(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
form.post = post
form.save()
return redirect('/')
else:
form = DocumentForm()
return render(request, 'blog/model_form_upload.html', { 'form': form })
inside post_detail.html
{% for document in post.documents.all %}
<div style="padding: 10px;">
<p> {{ document.description }} </p>
<img src=""/>
</div>
{% endfor %}
...
{% if user.is_authenticated %}
<a class="btn btn-default" href="{% url 'model_form_upload' pk=post.pk %}">Add photo</a>
{% endif %}
For now, I'm trying to print out the description and then dealing with the urls will be easier. The photos are being uploaded, they just aren't connected to the posts. When I remove the null = True I can't pass makemigrations on the command line. Can anyone help me with what's wrong here?
edit: If it helps, the comments class has the same line of code, but still works just fine:
models.py
class Comment(models.Model):
post = models.ForeignKey('blog.Post', related_name='comments')
author = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('author', 'text')
Your DocumentForm has no post attribute, so it does not know how to save it.
Add to your DocumentForm all fields except post:
class DocumentForm(ModelForm):
class Meta:
model = Document
exclude = ['post']
Then, in the view, do the following:
#login_required
def model_form_upload(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
document = form.save(commit=False)
document.post = post
document.save()
return redirect('/')
else:
form = DocumentForm()
return render(request, 'blog/model_form_upload.html', { 'form': form })

Django model formset factory and forms

I'm trying to user Django model formset factory to render a template where a user can add images and change the images they have uploaded(very similar to what can be done in the admin). I currently can render the template and its correct fields to the template. What I cannot do is have the user preselected(want currently logged in) and when I refresh the page the image will be posted again(not sure if this is preventable). Below is my code. Thanks!
Model:
class Image(models.Model):
user = models.ForeignKey(User)
image = models.ImageField(upload_to=content_file_name, null=True, blank=True)
link = models.CharField(max_length=256, blank=True)
Form:
class ImageForm(forms.ModelForm):
image = forms.ImageField(label='Image')
class Meta:
model = Image
fields = ('image',
'link',
)
View:
#login_required
def register(request):
user_data = User.objects.get(id=request.user.id)
ImageFormSet = modelformset_factory(Image,
fields=('user', 'image', 'link'), extra=3)
if request.method == 'POST':
print '1'
formset = ImageFormSet(request.POST, request.FILES, queryset=Image.objects.all())
if formset.is_valid():
formset.user = request.user
formset.save()
return render(request, 'portal/register.html', {'formset': formset, 'user_data': user_data})
else:
print '2'
formset = ImageFormSet(queryset=Image.objects.all())
return render(request, 'portal/register.html', {'formset': formset, 'user_data': user_data})
Template:
<form id="" method="post" action=""
enctype="multipart/form-data">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
<input type="submit" name="submit" value="Submit" />
let me explain the way you can do it.
MODELS
from django.utils.text import slugify
from django.db import models
from custom_user.models import AbstractEmailUser
# User model
class UserModel(AbstractEmailUser):
full_name = models.CharField(max_length=255)
def __str__(self):
return str(self.id)
# Function for getting images from instance of user
def get_image_filename(instance, filename):
title = instance.id
slug = slugify(title)
return "user_images/%s-%s" % (slug, filename)
# Save images with user instance
class UserImages(models.Model):
user = models.ForeignKey('UserModel', db_index=True, default=None)
image = models.ImageField(upload_to=get_image_filename, verbose_name='Image', db_index=True, blank=True, null=True)
In forms it's a just a two form, one for model User, other for UserImages model.
# Images forms
class ImageForm(forms.ModelForm):
image = forms.ImageField(label='Image', required=False)
class Meta:
model = UserImages
fields = ('image',)
# User form
class UserForm(forms.ModelForm):
full_name = forms.CharField(required=True)
class Meta:
model = UserModel
fields = ('full_name','email','password',)
And in Views for post you can do something like this
# View
from models import *
from forms import *
#csrf_protect
def post_view(request):
template = 'some_template.html'
ImageFormSet = modelformset_factory(UserImages, form=ImageForm, extra=15)
if request.method == 'POST':
user_form = UserForm(request.POST, prefix='form1')
formset = ImageFormSet(request.POST, request.FILES, queryset=UserImages.objects.none(), prefix='form2')
if user_form.is_valid() and formset.is_valid():
# Save User form, and get user ID
a = user_form.save(commit=False)
a.save()
images = formset.save(commit=False)
for image in images:
image.user = a
image.save()
return HttpResponseRedirect('/success/')
else:
user_form = UserForm(prefix='form1')
formset = ImageFormSet(queryset=UserImages.objects.none(), prefix='form2')
return render(request, template, {'form_user':user_form,'formset':formset})
In template you are doing the right thing.

Categories

Resources