How to I bind User fields data and user profile data to a model form on my view, I only get user data rendered to a form when i use instance=user but it doesn't render any data of user profile on instance=profile, what i need is to render all user and its profile to a form.
models.py
class User(AbstractUser):
is_supervisor = models.BooleanField(default=False)
is_student = models.BooleanField(default=False)
class Supervisor(models.Model):
user = models.OneToOneField('User', on_delete=models.CASCADE, primary_key=True, related_name='supervisor')
su_mobile_number = models.CharField(max_length=200)
view.py
def supervisor_update(request, user_id):
# user = get_object_or_404(User, pk=user_id)
user = get_object_or_404(User, pk=user_id)
profile = get_object_or_404(Supervisor, pk=user_id)
if request.method == 'POST':
form = SupervisorSignUpForm(request.POST, instance=user)
else:
form = SupervisorSignUpForm(instance=user)
return save_user_form(request, form, 'partials/partial_supervisor_update.html')
form.py
class SupervisorSignUpForm(UserCreationForm):
su_mobile_number = forms.CharField(label="Mobile Number")
class Meta(UserCreationForm.Meta):
model = User
fields = ('username', 'email', 'password1', 'password2', 'first_name', 'last_name')
supervisor_update.html
{% load crispy_forms_tags %}
<form method="post" action="{% url 'supervisor_update' form.instance.pk %}" class="js-supervisor-update-form">
{% csrf_token %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">Update Supervisor</h4>
</div>
<div class="modal-body">
{% include 'partials/form_messages.html' %}
{{ form.username|as_crispy_field }}
{{ form.email|as_crispy_field }}
{{ form.first_name|as_crispy_field }}
{{ form.last_name|as_crispy_field }}
{{ form.su_mobile_number|as_crispy_field }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Update Supervisor</button>
</div>
</form>
To set the extra field passing in initial along with the user instance should work
initial = {'su_mobile_number': profile.su_mobile_number}
form = SupervisorSignUpForm(instance=user, initial=initial)
Or if you dont want to manually create the dict you could use model_to_dict
from django.forms.models import model_to_dict
initial = model_to_dict(profile, exclude=['user'])
form = SupervisorSignUpForm(instance=user, initial=initial)
Also at the top of your view you are assuming user and supervisor have the same id which could not be the case instead you could do something like this
user = user = get_object_or_404(User, pk=user_id)
profile = user.supervisor
if not profile:
raise Http404
Related
I want to share a project that currently can create user and each user can create N posts
The source is available on github
and I has two models users and post
and the template layers
Currently the feed for each post has a button that send an commenting the post I want to change that to put the comments of the post and not send and email each user should be able to comment a post and the comment should remain
{% block container %}
<body id="bg" img style="zoom: 85%; background-position: center center; background-attachment: fixed;background-repeat:no-repeat;padding:5px; background-image: url('{% static "/back.png"%}') ";>
<div style="background-image: url({% static 'static/img/back.png' %});">
<div class="row" style="align:center">
{% for post in posts %}
<div class="col-sm-12 col-md-8 offset-md-4 mt-5 p-0 post-container,width:50%;">
<div class="card" style="width: 32rem;width:50%;">
<div class="card-body">
<div class="media pt-3 pl-3 pb-1">
<a href="{% url " users:detail" post.user.username%}">
<img alt="{{ post.user.username }}" class="mr-3 rounded-circle" height="35"
src="{{ post.profile.picture.url }}">
</a>
<h3 class="card-title">{{ post.title }}</h3>
</div>
<p class="card-text">{{ post.desc }}</p>
</div>
</div>
<img alt="{{ post.title }}" src="{{ post.photo.url }}" style="width: 50%; heigth:60%">
<div class="media-body">
<b><p style="margin-top: 5px;">#{{ post.user.username }} - <small>{{ post.created }}</small>
<a href="" style="color: #000; font-size: 20px;">
<i class="far fa-heart"></i>
</a>
<br>
</p></b>
</div>
<!-- COMENT SECTION THAT I WANT TO IMPLEMENT MY FEATURE-->
<form action="{% url 'posts:comment_new' %}" enctype="multipart/form-data" method="POST">
{% csrf_token %}
<input
class="form-control {% if form.title.errors %}is-invalid{% endif %}"
name="title"
size="16"
type="hidden"
value="{{post.title}}"
>
<input
class="form-control {% if form.title.errors %}is-invalid{% endif %}"
name="first_name "
size="16"
type="hidden"
value="{{user.first_name}}"
>
<input
class="form-control {% if form.title.errors %}is-invalid{% endif %}"
name="last_name "
size="16"
type="hidden"
value="{{user.last_name}}"
>
<textarea class="form-control" cols="50" name="comment" rows="5"
style="width:50%;" value="{{ comments.comment }}"></textarea>
<button class="btn btn-outline-info btn-lg" style="width:35%; display:block;margin:auto;" type="submit">
Publish
</button>
</form>
</div>
<br>
{% endfor %}
</div>
</div>
{% endblock %}
As I said I want to replace this form function call to create a comment section instead sending a email with the comment
< form action = "{% url 'posts:comment_new' %}">
def comment_new(request):
if request.method == 'POST':
message = request.POST['comment']
subject = request.POST['title']
user = request.POST['first_name']
last_name = request.POST['last_name']
# lastname = request.POST['lastname']
send_mail("[MAIL] " + subject, user + " " + last_name + " said " + message + " on http://url.com:8000",
'guillermo.varelli#gmail.com',
['guillermo.varelli#gmail.com'], fail_silently=False)
posts = Post.objects.all().order_by('-created')
return render(request, os.path.join(BASE_DIR, 'templates', 'posts', 'feed.html'), {'posts': posts})
I think this maybe create a comment with user and post id with the comment detail
def comment_new(request):
if request.method == 'POST':
message = request.POST['comment']
subject = request.POST['title']
user = request.POST['first_name']
last_name = request.POST['last_name']
#lastname = request.POST['lastname']
form = PostForm(request.POST, request.FILES)
form.save()
One options its create a comment
class Comment(models.Model):
"""
#id= models.AutoField(max_length=1000, blank=True)
# post = models.ForeignKey(Post, related_name='',on_delete=models.CASCADE,default=0)
"""
#comment = models.ForeignKey('posts.Post', related_name='posts_rel', to_field="comments", db_column="comments",
# on_delete=models.CASCADE, null=True, default=1, blank=True)
post = models.IntegerField(blank=True,null=True,unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE,null=True)
username = models.CharField(blank=True, null=True, unique=True ,max_length=200)
comment = models.CharField(max_length=254, blank=True, null=True)
and then the form
class CommentForm(forms.ModelForm):
class Meta:
"""form settings"""
model = Comment
fields = ('user','username','post','comment',)
finally with the function I'm able to persist but not able to render
form = CommentForm(request.POST, request.FILES)
# print formset.errors
if form.is_valid():
form.save()
but I can't find the way to render the object on the html file
please feel free to suggest any solution or better create a pull request on the public git hub repo
In the book Django 2 by Example we can find a step by step guide to create a comment system, wherein the users will be able to comment on posts.
In order to do it, is as simple as the following four steps
Create a model to save the comments
Create a form to submit comments and validate the input data
Add a view that processes the form and saves the new comment to the database
Edit the post detail template to display the list of comments and the form to add a new comment
Create a model to save the comments
In your models.py file for the application, add the following code
class Comment(models.Model):
post = models.ForeignKey(Post,
on_delete=models.CASCADE,
related_name='comments')
name = models.CharField(max_length=80)
email = models.EmailField()
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
class Meta:
ordering = ('created',)
def __str__(self):
return 'Comment by {} on {}'.format(self.name, self.post)
The new Comment model you just created is not yet synchronized into the database. Run the following command to generate a new migration that reflects the creation of the new model:
python manage.py makemigrations APPNAME
and
python manage.py migrate
After this, the new table exists in the database. Now, open the admin.py file of the blog application, import the Comment model, and add the following ModelAdmin class:
from .models import Post, Comment
#admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'post', 'created', 'active')
list_filter = ('active', 'created', 'updated')
search_fields = ('name', 'email', 'body')
Create a form to submit comments and validate the input data
Edit the forms.py file of your blog application and add the following lines:
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('name', 'email', 'body')
Add a view that processes the form and saves the new comment to the database
Edit the views.py file, add imports for the Comment model and the CommentForm form, and modify the post detail view to make it look like the following:
from .models import Post, Comment
from .forms import EmailPostForm, CommentForm
def post_detail(request, year, month, day, post):
post = get_object_or_404(Post, slug=post,
status='published',
publish__year=year,
publish__month=month,
publish__day=day)
# List of active comments for this post
comments = post.comments.filter(active=True)
new_comment = None
if request.method == 'POST':
# A comment was posted
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
# Create Comment object but don't save to database yet
new_comment = comment_form.save(commit=False)
# Assign the current post to the comment
new_comment.post = post
# Save the comment to the database
new_comment.save()
else:
comment_form = CommentForm()
return render(request,
'blog/post/detail.html',
{'post': post,
'comments': comments,
'new_comment': new_comment,
'comment_form': comment_form})
Edit the post detail template to display the list of comments and the form to add a new comment
At this point we have created the functionality to manage comments for a post. Now, we will need to adapt our post/detail.html template to do the following things:
- Display the list of comments
- Display a form for users to add a new comment
Append the following lines to the post/detail.html template for the list of comments:
{% for comment in comments %}
<div class="comment">
<p class="info">
Comment {{ forloop.counter }} by {{ comment.name }}
{{ comment.created }}
</p>
{{ comment.body|linebreaks }}
</div>
{% empty %}
<p>There are no comments yet.</p>
{% endfor %}
Then, for the other point, add the following lines:
{% if new_comment %}
<h2>Your comment has been added.</h2>
{% else %}
<h2>Add a new comment</h2>
<form action="." method="post">
{{ comment_form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Add comment"></p>
</form>
{% endif %}
So I have this form which extends the User and I just want to allow the student to create and account and be able to select the courses from the course list.But when I try, I get the error:
__init__() takes 1 positional argument but 2 were given
I can't find any solution to this yet. I need some advice.
These are my files:
{% block body %}
<div class="row">
<div class="col-md-8 col-sm-10 col-12">
<h2>Sign up as a {{ user_type }}</h2>
<form method="post" novalidate>
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}">
{{ form }}
<button type="submit" class="btn btn-success">Sign up</button>
</form>
</div>
</div>
{% endblock %}
class User(AbstractUser):
is_student = models.BooleanField(default=False)
is_teacher = models.BooleanField(default=False)
path('signup/', views.StudentSignUpView, name='signup')
class StudentSignUpForm(UserCreationForm):
attended_courses = forms.ModelMultipleChoiceField(
queryset=Course.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=True
)
class Meta(UserCreationForm.Meta):
model = User
#transaction.atomic
def save(self):
user = super().save(commit=False)
user.is_student = True
user.save()
student = Student.objects.create(user=user)
student.attended_courses.add(*self.cleaned_data.get('attended_courses'))
return user
class StudentSignUpView(CreateView):
model = User
form_class = StudentSignUpForm
template_name = 'signup_form.html'
def get_context_data(self, **kwargs):
kwargs['user_type'] = 'student'
return super().get_context_data(**kwargs)
def form_valid(self, form):
user = form.save()
login(self.request, user)
return redirect('index')
Class-based views need to be referenced in urls.py via their as_view method.
path('signup/', views.StudentSignUpView.as_view(), name='signup')
I have html form which I want to send and save to django model. When I try to send message I get an error:
ValueError at /account/userinfo/akylson/
"<Mail: hhh>" needs to have a value for field "id" before this many-to-many relationship can be used.
Request Method: POST
Request URL: http://localhost:8000/account/userinfo/akylson/
Django Version: 1.11.3
Exception Type: ValueError
Exception Value:
"<Mail: hhh>" needs to have a value for field "id" before this many-to-many relationship can be used.
You can see my code below.
Here is my html form below:-
<form role="form" class="form-horizontal" method="post">
{% csrf_token %}
<div class="form-group">
<input type="checkbox" id="id_receiver" name="receiver" value="{{ user.username }}" checked hidden>
<label class="col-lg-2 control-label">Тема</label>
<div class="col-lg-10">
<input type="text" placeholder="" id="id_subject" name="subject" value="{{ subject }}" class="form-control">
</div>
</div>
<div class="form-group">
<label class="col-lg-2 control-label">Сообщение</label>
<div class="col-lg-10">
<textarea rows="10" cols="30" class="form-control" id="id_message" name="message"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<span class="btn green fileinput-button"><i class="fa fa-plus fa fa-white"></i>
<span>Приложение</span><input type="file" name="files[]" multiple=""></span>
<button class="btn btn-send" value="submit" type="submit">Send</button>
</div>
</div>
</form>
Here is my view.py:
#login_required()
def userinfo(request, username):
username = User.objects.get(username=username)
args = {}
args['user'] = username
if request.method == 'POST':
sender = request.user
receiver = request.POST['receiver']
subject = request.POST['subject']
message = request.POST['message']
b = Mail.objects.create(sender=sender, receiver=receiver, subject=subject, message=message)
b.save()
return render(request, 'account/userinfo.html', args)
Here is my models.py:
class Mail(models.Model):
sender = models.ForeignKey(User, related_name='mail_sender')
receiver = models.ManyToManyField(User, related_name='mail_receiver')
subject = models.CharField(max_length=200)
message = RichTextUploadingField()
date = models.DateTimeField(auto_now=True, null=False, blank=False)
class Meta():
ordering = ['-date']
def __str__(self):
return self.subject
Here is my forms.py:
class NewMailForm(forms.ModelForm):
class Meta:
model = Mail
fields = (
'sender',
'receiver',
'subject',
'message',
)
widgets = {'receiver': forms.CheckboxSelectMultiple()}
You have to pass user instances to your views.py.
Change your views.py as showed below,
views.py:
#login_required()
def userinfo(request):
user = request.user
form = NewMailForm(request.POST or None)
if request.method == 'POST':
if not form.is_valid():
print form.errors
return render(request,'')
else:
sender = user
receiver = form.cleaned_data.get("receiver")
subject = form.cleaned_data.get("subject")
message = form.cleaned_data.get("message")
b = Mail.objects.create_user(
sender=sender,
receiver=receiver,
subject=subject,
message=message)
b.save()
return render(request, 'account/userinfo.html')
and forms.py:
<form action="." method="POST">{% csrf_token %}
{{ form.as_p }}
</form>
This will create a new mail objects with requested user.
In your views.py create an instance of your model
for example m = Mail()
then post each of the field using the instance for example
m.receiver = request.POST.get('receiver')
then save with
m.save()
Before a Many2many field can be linked Django needs the id of the record on the other side of the relationship (in this case your Mail) model.
So you have to actually create it before setting the receiver like this:
b = Mail.objects.create(sender=sender, subject=subject, message=message)
b.receiver = receiver
b.save()
You have made several mistakes:
forms.py is not required if you have made an HTML form and linked to project.
You have not defined b. Just written b.save
Just debug these errors and you are Done!
I've spent a couple of days on this, read the docs, read some Two Scoops info, and I'm missing something. I'm trying to make a view to log in an AbstractUser. The AbstractUser model is in my_app and the view is in an app called mainsite, that I use to store project-wide views.
After the user logs in, they will have access to class-based views that they will use to add, edit, and delete database records. These users are not staff, so I am not giving them access to the admin.
Every time I try to log in as a user, authenticate(username, password) (in views.py) returns none.
What am I missing?
Here's the setup--I have a custom Person model that extends the AbstractUser model:
# my_app.models.py
class Person(AbstractUser):
date_left = models.DateField(blank=True, null=True)
phone_number = models.IntegerField(blank=True, null=True)
def _full_name(self):
return self.get_full_name()
full_name = property(_full_name)
def __str__(self):
return self.get_full_name()
class Meta:
verbose_name_plural = 'People'
I've added Person to settings.py:
# settings.py
...
AUTH_USER_MODEL = "my_app.Person"
...
I have the following URLconf:
# project/urls.py
from mainsite.views import login_view
...
url(r'^login/$', login_view, name='login'),
...
This view logs in the user:
# mainsite.views.py
def login_view(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
return render(request, 'logged_in.html')
else:
return render(request, 'login.html',
{'message': 'Bad username or password'}
)
else:
return render(request, 'login.html')
And finally, here's the template with the login fields:
#templates/login.html
<form action="{% url 'login' %}" method="post">
{% csrf_token %}
<div class="row column"><h1>Login</h1></div>
{% if message %}
<div class="row">
<div class="small-12 medium-6 columns">
<div class="alert callout">
<p>{{ message }}</p>
</div>
</div>
</div>
{% endif %}
<div class="row">
<div class="small-12 medium-6 columns">
<label>Username
<input type="text" id="username" name="username">
</label>
</div>
</div>
<div class="row">
<div class="small-12 medium-6 columns">
<label>Password
<input type="password" id="password" name="password">
</label>
</div>
</div>
<div class="row">
<div class="small-12 medium-6 columns">
<input type="submit" value="Log in" class="button">
</div>
</div>
</form>
I found an answer (with help from reddit) in the docs.
When you use AbstractUser, you need to create your own UserCreationForm and UserChangeForm. If you use AbstractBaseUser, then you will need to create additional forms.
I had not created these forms, and I created my users in the admin using forms automatically generated by Django. These forms did not set the password correctly. The automatically generated form probably used user.password = 'some_password'. The correct way to do it is user.set_password('some_password') .
here I am trying to allow users to make modifications to their user profile. There's a model called UserProfile that holds a one to one relationship to django user itself. Below is the code in views.py
#login_required
def edit_profile(request):
if request.method == 'POST':
profile = UserProfile.objects.get(user=request.user)
profile_form = UserProfileForm(data=request.POST, instance=profile)
if profile_form.is_valid():
profile = profile_form.save(commit=False)
profile.user = user
if 'picture' in request.FILES:
profile.picture = request.FILES['picture']
profile.save()
return HttpResponseRedirect("/me/login/")
else:
print user_form.errors, profile_form.errors
else:
user = request.user
profile = UserProfile.objects.get(user=user)
profile_form = UserProfileForm(initial={'website':profile.website,'address':profile.address, 'picture':profile.picture})
return render(request, 'member/edit_profile.html', {'profile_form': profile_form})
However, once the submit button is clicked from the template, I got an error saying that a password is needed.
[27/Apr/2015 14:25:07] "GET /me/edit_profile2/ HTTP/1.1" 200 5080
<ul class="errorlist"><li>username<ul class="errorlist"><li>This field is required.</li></ul></li><li>password<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
[27/Apr/2015 14:25:16] "POST /me/edit_profile/ HTTP/1.1" 200 6384
from the code, I thought that the UserProfile is already bound to a specific user already, and I am only allowing users to make changes on the UserProfile model without touching django's auth User model. I wonder why the username and password is still needed in this case. Would it be possible to allow editing on user profile without users' password?
Here is the UserProfile extended from the User model
class UserProfile(models.Model):
# link user profile to the user models
user = models.OneToOneField(User)
website = models.URLField(blank=True)
picture = models.ImageField(upload_to='avatar', blank=True)
address = models.TextField(blank=True)
#property
def stats(self):
"""get statistics for this profile"""
from tumboon.models import Donation
return Donation.statistics.for_user(self)
#property
def amount_donated(self):
__doc__ = """get the total amount donated """
return self.stats['total_amount_donated']
# Override the __unicode__ to return something meaningful
def __unicode__(self):
return self.user.username
class UserForm(forms.ModelForm):
"""Form for User Registration"""
class Meta:
model = User
fields = ('username', 'email', 'password', 'first_name', 'last_name')
widgets = {
'username' : forms.TextInput(attrs = {'placeholder': 'Username'}),
'email' : forms.TextInput(attrs = {'placeholder': 'Email'}),
'password' : forms.TextInput(attrs = {'placeholder': 'Password'}),
}
class UserProfileForm(forms.ModelForm):
"""Form for UserProfile"""
class Meta:
model = UserProfile
fields = ('website', 'picture', 'address')
And the UserProfileForm on the template is here:
{% if user.is_authenticated %}
<form id="user_profile" class="form-horizontal" method="POST" enctype="multipart/form-data" action="/me/edit_profile/">
{% csrf_token %}
<h3>User Info</h3>
<hr />
<div class="form-group">
<label class="col-xs-2" for="{{ user_form.picture.id_for_label }}">Picture: </label>
<div class="col-xs-10">
{{profile_form.picture|add_class:"form-control"}}
</div>
</div>
<div class="form-group">
<label class="col-xs-2" for="{{ user_form.website.id_for_label }}">Website: </label>
<div class="col-xs-10">
{{profile_form.website|add_class:"form-control"}}
</div>
</div>
<div class="form-group">
<label class="col-xs-2" for="{{ user_form.address.id_for_label }}">Address: </label>
<div class="col-xs-10">
{{profile_form.address|add_class:"form-control"}}</li>
</div>
</div>
<input class="btn btn-default" type="submit" name="save_button" value="Save Profile">
</form>
{% endif %}
Your UserForm class is extending Model.Form class which displays the Password field as required, hence the problem. Use instead the UserChangeForm:
from django.contrib.auth.forms import UserChangeForm
class UserForm(UserChangeForm):
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name', 'is_super_admin')
This form handles password as it should.