Wagtail: Add image to custom user model - python

I'm following the Wagtail documentation to customize the user model. I want to add an image to the user model.
Django version: 2.0.8,
Wagtail version: 2.1
Problem
After choosing an image with the image chooser field and clicking 'Save', this error shows up:
'No file was submitted. Check the encoding type on the form.'
Code
models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
display_image = models.ForeignKey('wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+')
forms.py
from django import forms
from django.utils.translation import ugettext_lazy as _
from wagtail.users.forms import UserEditForm, UserCreationForm
from wagtail.images.widgets import AdminImageChooser
class CustomUserEditForm(UserEditForm):
display_image = forms.ImageField(
widget=AdminImageChooser(), label=_('Autorenbild'))
class CustomUserCreationForm(UserCreationForm):
display_image = forms.ImageField(
widget=AdminImageChooser(), label=_('Autorenbild'))}
edit.html
{% extends "wagtailusers/users/edit.html" %}
{% block extra_fields %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.display_image %}
{% endblock extra_fields %}
{% block extra_js %}
{{ block.super }}
{% include 'wagtailadmin/pages/_editor_js.html' %}
{% endblock extra_js %}
create.html similar
What I've tried so far
The idea to use the AdminImageChooser widget I found here. I had to adjust the forms by adding an forms.ImageField so that the User page displays without error.
Questions
Anyone know why the error occurs and how to fix it?
As stated in the above Google group thread, it seems as adding an image to the user model is 'a bit awkward'. What is a better approach to have an image connected to an user for repetitive usage in a site? A requirement is that the image can be easily changed in Wagtail admin.
Other problem with Wagtail version 2.2
In Settings > User in the admin interface, the window of the AdminImageChooser does not open.
Console shows following JS error:
Uncaught ReferenceError: createImageChooser is not defined

On Django 3.0.7 and wagtail wagtail 2.9, I could solve it like this:
models.py
class User(AbstractUser):
avatar = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
forms.py
It's important to use an ModelChoiceField and not an ImageField, so you are compliant with the AdminImageChooser widget.
from django import forms
from wagtail.images import get_image_model
from wagtail.images.widgets import AdminImageChooser
from wagtail.users.forms import UserCreationForm, UserEditForm
class CustomUserEditForm(UserEditForm):
avatar = forms.ModelChoiceField(
queryset=get_image_model().objects.all(), widget=AdminImageChooser(),
)
class CustomUserCreationForm(UserCreationForm):
avatar = forms.ModelChoiceField(
queryset=get_image_model().objects.all(), widget=AdminImageChooser(),
)
Templates
No need to load any Javascript manually. The widget do it for you.
Any-templates-dir/wagtailusers/users/create.html
{% extends "wagtailusers/users/create.html" %}
{% block extra_fields %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.avatar %}
{% endblock extra_fields %}
Any-templates-dir/wagtailusers/users/edit.html
{% extends "wagtailusers/users/edit.html" %}
{% block extra_fields %}
{% include "wagtailadmin/shared/field_as_li.html" with field=form.avatar %}
{% endblock extra_fields %}

Was using the last proposed solution in the same Google groups thread, also got the ReferenceError: createImageChooser is not defined js problem after updating from Wagtail 2.1 to 2.2
Here is how my research went:
Looks like this commit removed direct media js inclusion from _editor_js.html.
Okay, so the solution is to replicate the old behavour, and access the Media subclasses of widgets
First I tried adding the same removed lines to my own edit.html in {% block extra_js %}. Did not work.
Looks like some views return edit_handler, like here. User view does not.
What user view does provide, however, is form variable, in both create and edit views. Let's use it.
And the final solution for me was changing extra_js block in edit.html like so:
{% block extra_js %}
{{ block.super }}
{% include 'wagtailadmin/pages/_editor_js.html' %}
{{ form.media.js }}
{% endblock extra_js %}

models.py
class User(AbstractUser):
first_name = models.CharField(max_length=40, blank=False)
image = models.ImageField(blank=False, null=False, upload_to='image/', default='img/user.jpg')
def __str__(self):
return self.username
'''here you can add a model and their requirements too u can set path to remember one thing that image folder will create in media folder..so don't forget to add that folder there. like if you find it helpful'''
forms.py
class UserRegistrationForm(forms.ModelForm):
confirm_password2 = forms.CharField(widget=forms.PasswordInput,required=False)
class Meta:
model = User
fields = ['first_name','username','password','confirm_password2','image']
def clean(self):
cleaned_data = super(UserRegistrationForm, self).clean()
password = cleaned_data.get("password")
confirm_password2 = cleaned_data.get("confirm_password2")
if password != confirm_password2:
self.add_error('confirm_password2', "Password does not match")
return cleaned_data
'''def clean for password validation and i directly added fields which field i want in it'''
views.py
def Register_view(request):
form = UserRegistrationForm()
if request.method == 'POST':
form = UserRegistrationForm(request.POST, request.FILES)
if form.is_valid():
user = form.save(commit=False)
password = form.cleaned_data.get("password")
user.set_password(password)
user.save()
return redirect('login')
return render(request, 'register.html', {'form': form})
''' use request.files to add imagein your form'''
register.html
<form action="{% url 'register' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="body bg-gray">
<div class="form-group">
{{ form.as_p }}
</div>
</div>
<div class="footer">
<button type="submit" class="btn bg-olive btn-block">Sign me up</button>
I already have Account
</div>
</form>
''' user multipart to add image in it'''

For me the solution was to make sure you initialise the image field widget on the create and edit form classes with the AdminImageChooser
from wagtail.users.forms import UserEditForm
from wagtail.images.widgets import AdminImageChooser
class CustomUserEditForm(UserEditForm):
...
mugshot = AdminImageChooser()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['mugshot'].widget = AdminImageChooser()
You need to do this when you subclass the UserCreationForm too.
Django 3.2.6, Wagtail 2.14

Related

Can't Submit ModelForm using CBVs

I am trying to use ModelForms and CBVs to handle them, but I am facing trouble especially while submitting my form. Here's my code.
forms.py
from django import forms
from .models import Volunteer
class NewVolunteerForm(forms.ModelForm):
class Meta:
model = Volunteer
fields = '__all__'
views.py
from django.http.response import HttpResponse
from django.views.generic.edit import CreateView
from .forms import NewVolunteerForm
class NewVolunteerView(CreateView):
template_name = 'website/join.html'
form_class = NewVolunteerForm
def form_valid(self, form):
print('Submitting')
form.save()
return HttpResponse('DONE')
join.html
{% extends 'website/_base.html' %}
{% block title %}Join Us{% endblock title %}
{% block content %}
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit">
</form>
{% endblock content %}
The form is getting displayed correctly with no issues at all, but when I fill it in and press the submit button it simply re-rendered the form and doesn't submit it at all.
I solved this by adding the enctype="multipart/form-data" attribute to my <form> element.
The reason was when you have ImageFields or FileFields this attribute should be used.

Accessing profile info through user model Django

Hello I am a beginner with the django python framework. I need to display a user's image and bio on a file named user_posts.html in my blog app. I have it where I can access the user's image and bio by looping over the posts for the user. However, I need it so it only displays the bio and the image once. I have a separate profile.html in a users app. In that file, I can do just src="{{ user.profile.image.url }}" and {{ user.profile.bio }} to access the users information but that does not seem to work in my user_posts.html because of the structure of my project. I can't figure out how to tell the for loop to just go over once to access the users information.
users_post.html
{% extends "blog/base.html" %}
{% block content %}
<hr size="30">
<div class="row">
<div class="column left">
{% for post in posts %}
<img style= "float:left" src="{{ post.author.profile.image.url}}" width="125" height="125">
<h5 style="text-align: left;">{{ post.author.profile.bio }}</h5>
{% endfor %}
</div>
views.py
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
models.py
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
bio = models.TextField(default='enter bio text here')
def __str__(self):
return f'{self.user.username} Profile'
This is what the problem looks like
Any help is appreciated
Here's how you might do it using get_context_data().
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
def get_context_data(self, **kwargs):
"""
Add User Profile to the template context.
"""
context = super().get_context_data(**kwargs)
profile_user = get_object_or_404(User, username=self.kwargs.get('username'))
context['profile_user'] = profile_user
return context
You'd then use {{ profile_user.profile.bio }} in your template instead of posts for the user profile info.
This is probably better than getting the first object in posts and getting the user profile information from that object in the case the user has no posts yet (but has a bio).
Note, we're fetching the User object both in get_queryset and in get_context_data so this is not super efficient. There are ways around this but I'll leave it to future editors or you to optimize :)
Probably Not Recommended, But Answering Question
Since you initially were just trying to get the first element, here's how I'd do it.
# Method 1
{% with first_post=posts|first %}
{{ posts.user.profile.bio }}
{{ posts.user.profile.image.url }}
{% endwith %}
# Method 2
{{ posts.0.profile.bio }}
{{ posts.0.profile.image.url }}

Django rendering a db field in the template

Could anyone correct my code?
Background:
The user, once on the 'start.html' template, will enter their name and press submit. Then on the next template, 'game.html', there should be a paragraph tab that contains that users name.
Problem:
I must be writting something incorrectly because the user's name does not render on the 'game.html' template. Or, I could also be storing it wrong. Any suggestions or corrections would be very appreciated!
models.py - fp
from django.db import models
class Player(models.Model):
#first player name
fp_name = models.CharField(max_length=30, default='')
forms.py - I'm not sure if this is actually needed...?
from django import forms
class PlayerInfo(forms.Form):
fp_name = forms.CharField(max_length=30, label='First player name')
views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, render_to_response
import os
from .forms import PlayerInfo
from .models import Player
def index(request):
return render(request, 'index.html')
def start(request):
if request.method == 'Post':
form = PlayerInfo(request.POST)
if form.is_valid():
obj = Player()
obj.fp_name = form.cleaned_data['fp_name']
return HttpResponseRedirect('/')
else:
form = PlayerInfo()
return render(request, 'start.html')
def game(request):
return render_to_response('game.html', {'obj': Player.objects.all()})
start.html - Meant to submit player one's name
{% extends 'base.html' %}
{% block botRow %}
<form method="post">
{% csrf_token %}
<label for="fp_name">First Player Name</label>
<input id="fp_name" type="text" name="fp_name" maxlength="30" required />
<input type="submit" value="Submit" />
</form>
{% endblock %}
game.html - Meant to render the player one's name
{% extends 'base.html' %}
{% block midRow %}
<p>{{ obj.fp_name }}</p>
{% endblock %}
in your game.html the obj is query set of all Users, so you should walk through the list, look on block for in the docs:
{% extends 'base.html' %}
{% block midRow %}
{% for user in obj %}
<p>{{ user.fp_name }}</p>
{% endfor %}
{% endblock %}
Using User.objects.all() you are getting a Collection of all site's users. It isn't current user. So, the collection doesn't have parameter fp_name. Use request.user to get current logged in user.
Also, there is some redundancy in your code:
Django contains User class with ability to store First Name out of the box. So, you don't need to declare it at all. https://docs.djangoproject.com/en/1.11/ref/contrib/auth/#django.contrib.auth.models.User
There is special class of forms - ModelForm. It helps you to map model's fields to form's fields as fast as possible. (https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/)
There is special class of views - CreateView. It helps you to realize basic logic of model creating. (https://docs.djangoproject.com/en/1.11/ref/class-based-views/generic-editing/#django.views.generic.edit.CreateView)
Forms intended to save your time. So, in templates it's better to use built-in rendering engine of forms, than to declare their fields manually. (https://docs.djangoproject.com/en/1.11/topics/forms/#the-template)
If game.html is permitted only for registered users it's a good idea to use #login_required decorator to restrict access to this part of the site.

Include template form to django admin

Is it possible to include model form template in django admin as follows?
models.py
class Customer(models.Model):
name = models.CharField(max_length=20)
designation = models.CharField(max_length=20)
gender = models.BooleanField()
forms.py
class CustomerForm(forms.ModelForm)
gender = forms.TypedChoiceField(
choices=GENDER_CHOICES, widget=forms.RadioSelect(renderer=HorizontalRadioRenderer), coerce=int, )
class Meta:
model = Customer
template.html
<form action="{% url 'verinc.views.customerView' %}" method="POST">{% csrf_token %}
{{ form.as_p }}
<input id="submit" type="button" value="Click" /></form>
views.py
def customerView(request):
if request.method == 'POST':
form = CustomerForm(request.POST)
else:
form = CustomerForm()
return render_to_response('myapp/template.html', {'form' : form,})
admin.py
class CustomerInline(admin.StackedInline)
model= Customer
form = CustomerForm
template = 'myapp/template.html
When I view the form in url (localhost/myapp/customer) it displays all the fields correctly. But when I view it in admin page it displays only the submit button in the block. My requirement is to view the form using templates in admin page, so that i could use some AJAX script for further process. Any help is most appreciated.
Well , Its not possible . But you can implement like this :
You should create file called admin_views.py in your app customer.
Then add url in your urls.py like
(r'^admin/customer/customerView/$', 'myapp.customer.admin_views.customerView'),
Write your view implementation inside admin_views.py like :
from myapp.customer.models import Customer
from django.template import RequestContext
from django.shortcuts import render_to_response
from django.contrib.admin.views.decorators import staff_member_required
def customerView(request):
return render_to_response(
"admin/customer/template.html",
{'custom_context' : {}},
RequestContext(request, {}),
)
customerView = staff_member_required(customerView)
And inside your admin template extend base_site.html like this one :
{% extends "admin/base_site.html" %}
{% block title %}Custmer admin view{% endblock %}
{% block content %}
<div id="content-main">
your content from context or static / dynamic...
</div>
{% endblock %}
Thats it . hit that new url after admin login . Note Not Tested :) .

Django - importing form into template outside app

I have an app with name account which contain all the models, views, and forms to be used in registering and signing in users.
I have a template that is located outside the app folder that suppose to contain all the forms in account app.
I am having problem trying to get the forms showing in the template.
Can someone help me?
Here are some snippet of codes:
accounts/forms.py
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
# Safe from injection, etc.
class UserRegistrationForm(UserCreationForm):
email = forms.EmailField(required = True)
first_name = forms.CharField(max_length=30, required=True)
last_name = forms.CharField(max_length=30, required=True)
class Meta:
model = User
fields = ('username','email','password1', 'password2','first_name','last_name')
def save(self, commit=True):
user = super(UserRegistrationForm, self).save(commit=False)
user.email = self.cleaned_data['email']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
if commit:
user.save()
return user
accounts/view.py
from forms import UserRegistrationForm
def register_user(request):
if request.POST:
form = UserRegistrationForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/accounts/register_success')
else:
form = UserRegistrationForm()
args = {}
# prevent forgery
args.update(csrf(request))
# empty form
args['form'] = form
return render_to_response('signup.html', args)
def register_success(request):
return render_to_response('signup_success.html')
and finally the template, which is not in account folder. It's in the same folder as settings.py
signup.html
{% extends "base.html" %}
{% block content %}
<form action="" method="post"> {% csrf_token %}
<ul>
{{accounts.form.as_ul}}
</ul>
<input type="submit" name="submit" value="Register">
</form>
{% endblock %}
UPDATE
Upon obtaining permission to move the template from the project manager, I moved it to accounts/template, and changed the render to response address.
I have new problem of form not submitting now.
OMG what's going on??
The template should not be in the same directory as settings.py.
It should be in a directory within the accounts app: accounts/templates/signup/html.
If you've configured your Django project correctly then Django should pickup the template after restarting the web server.
as I see you are passing form variable to template,
but you are trying to use accounts.form.
Hope this helps.
Ok the problem lies on my signup.html file. It should have script for onclick and the form should have an id.
{% extends "base.html" %}
{% block content %}
<form action="" method="post" id="form"> {% csrf_token %}
<ul>
{{accounts.form.as_ul}}
</ul>
<input type="submit" name="submit" value="Register" onclick="submit()">
</form>
<script>
function submit() {document.forms["form"].submit();}
</script>
{% endblock %}

Categories