Edit UserProfile using Django Forms - python

I am creating a web application using the Django web framework. As part of the application, I wanted to give users the ability to update their profile as they choose. When a user tries to update their profile however, the information remains the same even after the user has submitted new information. I've checked my database and the values are not being updated in there either. It is possible however, to change a Users information on the admin page. Below is how I have tried to implement the functionality.
Model
class UserProfileModel(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
age = models.PositiveIntegerField(blank=True, null=True)
email = models.EmailField(max_length=254, null=True, blank=True, unique=True)
height = models.PositiveIntegerField(blank=True, null=True)
weight = models.PositiveIntegerField(blank=True, null=True)
Form
class UpdateProfile(forms.ModelForm):
class Meta:
model = UserProfileModel
fields = ('email', 'age', 'height', 'weight')
Views
def update_profile(request):
args = {}
if request.method == 'POST':
form = UpdateProfile(request.POST, instance=request.user)
if form.is_valid():
form.save()
# return HttpResponseRedirect(reverse('account:profile'))
return render(request, 'account/profile.html')
else:
form = UpdateProfile()
if request.user.is_authenticated():
form = UpdateProfile(instance=request.user)
args['form'] = form
return render(request, 'account/edit_profile.html', args)
Url
app_name = 'account'
urlpatterns = [
url(r'^profile/$', views.profile, name='profile'),
url(r'^profile/edit/$', views.update_profile, name='edit_profile'),
url(r'^home/$', views.home, name='home'),
]
HTML
{% block body %}
<div class="container">
<form method="POST" action=".">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
<br>
</div>
{% endblock %}

Your form is not for a User, it is for a UserProfileModel (which by the way is an odd name; there's no need to include the word "model" there).
So you should pass the profile, not the user, to the form:
form = UpdateProfile(request.POST, instance=request.user.userprofilemodel)
Note, you probably also want to pass it in the else block, so that the user sees the existing data to edit on GET:
form = UpdateProfile(instance=request.user.userprofilemodel)
Also, you should redirect after successful POST, as you do in the commented-out line, rather than rendering a template.

Related

Select2 widget showing on Django Admin site but not on the form template in Django4

I have two object models, NewsObject and StockObject. The stock object is a foreign key in the news object.
class stockObject(models.Model):
stock_name = CharField(max_length=100, blank=True, null=True)
stock_tag = CharField(max_length=100, blank=True, null=True)
def __str__(self):
return self.stock_name
class newsObject(models.Model):
title = CharField(max_length=100, blank=True, null=True)
body = TextField(blank=True, null=True)
stock = ForeignKey(stockObject, on_delete=models.SET_NULL, blank=True, null=True)
I have used autocomplete_fields property in the ModelAdmin class as I want a searchable dropdown for stocks in news. I have also added search_fields in the stocks ModelAdmin as mentioned in the documentation.
This is what my admin.py looks like:
class stockAdmin(admin.ModelAdmin):
list_display = ['stock_name', 'stock_tag']
search_fields = ['stock_name']
class newsAdmin(admin.ModelAdmin):
list_display = ['title', 'body', 'stock']
search_fields = ['title', 'body', 'stock']
autocomplete_fields = ['stock']
Now, the issue is that I get a searchable dropdown on the Django Admin site for this field, but it is only a dropdown (not searchable) on the actual template screen. I have a basic view which calls the template, like so:
Views.py
def createNews(request):
form = NewsForm()
if request.method == 'POST':
form = NewsForm(request.POST)
if form.is_valid():
form.save()
return redirect('/backoffice/')
context = {'form' : form}
return render(request, 'NewsForm.html', context)
And NewsForm.html is:
{% extends "base.html" %}
{% load static %}
{% block content %}
<form action="" method="POST">
{% csrf_token %}
{{ form }}
<input type="submit" name="Submit">
</form>
{% endblock %}
I've been wondering what might be the cause of this behavior. Tried multiple things but none of them work. What might I be missing here?
Django Admin site image
Django Template Image
I think you have written all your models in camelCase so first changed them to PascalCase.
Second, you have missed models in all your models:
Write them like this add models before every datatype like:
from django.db import models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
Not only datatype of fields.

Django database relation

I am working on a forum using django, I have a problem accessing user fullname and bio, from a model class I have. I have no problem accessing the user.username or user.email, but not from the Author class..
This is from the models.py in the forum app
User = get_user_model()
class Author(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
fullname = models.CharField(max_length=40, blank=True)
slug = slug = models.SlugField(max_length=400, unique=True, blank=True)
bio = HTMLField()
points = models.IntegerField(default=0)
profile_pic = ResizedImageField(size=[50, 80], quality=100, upload_to="authors", default=None, null=True, blank=True)
def __str__(self):
return self.fullname
My form is in the user app, where i have a profile update site, and the form is like this
from forums.models import Author
class UpdateForm(forms.ModelForm):
class Meta:
model = Author
fields = ('fullname', 'bio', 'profile_pic')
Then here is some of the update site, however nothing let me get access to the bio or fullname, I've tried so many combos. and I am lost here..
{% block content %}
<section class="section" id="about">
<!-- Title -->
<div class="section-heading">
<h3 class="title is-2">Hey {{ user.username }}</h3>
<div class="container">
<p>{{ user.bio }}bio comes here</p>
</div>
</div>
Here is the view.py from the user app
from .forms import UpdateForm
def update_profile(request):
context = {}
user = request.user
instance = Author.objects.filter(user=user).first()
if request.method == 'POST':
form = UpdateForm(request.POST, request.FILES, instance=user)
if form.is_valid():
form.instance.user = user
form.save()
return redirect('/')
else:
form = UpdateForm(instance=user)
context.update({
'form': form,
'title': 'update_profile',
})
return render(request, 'register/update.html', context)
The form html
<form method="POST" action="." enctype="multipart/form-data">
{% csrf_token %}
{{form|crispy}}
<hr>
<button class="button is-block is-info is-large is-fullwidth">Update <i class="fa fa-sign-in" aria-hidden="true"></i></button>
</form>
If there is some relation i am missing please help
Your view currently creates a new Author record each time you "update" the model. I would advise to first clean up the database and remove all authors.
Then you can convert the ForeignKey into a OneToOneField here: that way we know that each user has at most one Author:
from django.conf import settings
class Author(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
# …
Now we can alter the view to create a record in case there is no such record, or update an existing record if there is one:
from .forms import UpdateForm
def update_profile(request):
context = {}
user = request.user
instance = Author.objects.filter(user=user).first()
if request.method == 'POST':
form = UpdateForm(request.POST, request.FILES, instance=instance)
if form.is_valid():
form.instance.user = user
form.save()
return redirect('/')
else:
form = UpdateForm(instance=instance)
context.update({
'form': form,
'title': 'update_profile',
})
return render(request, 'register/update.html', context)
In the template, you can render data of the related Author model for a user user with:
{{ user.author.fullname }}
{{ user.author.bio }}

unable to upload files in django through web forms

I am trying to create a django website similar to that of Udemy or Coursera. I am trying to implement a feature that lets users add courses.
Here is my view for adding the course:
def create_course(request):
form = CreateCourseForm()
if request.method == 'POST':
form = CreateCourseForm(request.POST)
if form.is_valid():
new_course = form.save(commit=False)
new_course.instructor = request.user
new_course.save()
return redirect('home')
return render(request, 'courses/create.html',{'form':form})
Here is my form:
class CreateCourseForm(forms.ModelForm):
class Meta:
model = Course
fields = ('name','video','description','category',)
My course model:
class Course(models.Model):
name = models.CharField(max_length=200)
instructor = models.ForeignKey(User, on_delete=models.CASCADE)
video = models.FileField(upload_to='videos/')
description = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
def __str__(self):
return self.name
And finally my web form:
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<h1>Create a Course</h1>
<hr>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-sm btn-outline-primary" type="submit">Create Course</button>
</form>
{% endblock %}
The issue i am facing is that when i try to create a course, the video field shows an error "This field is required" even when I have uploaded a video.
I tried researching a bit and found out that adding enctype="multipart/form-data" to the form was required, but even adding that didn't solve my problem
Can anyone help me here?
You need to pass request.POST and request.FILES to the form, so:
def create_course(request):
form = CreateCourseForm()
if request.method == 'POST':
# add request.FILES &downarrow;
form = CreateCourseForm(request.POST, request.FILES)
# …
return render(request, 'courses/create.html',{'form':form})
If you only pass request.POST, you are only passing the items of the form that are not file uploads. This then will indeed add an error to the form fields of these files, saying that the form requires data for these fields.
as willem told you have to request file in you form like this
def create_course(request):
form = CreateCourseForm()
if request.method == 'POST':
form = CreateCourseForm(request.POST, request.FILES)
if form.is_valid():
new_course = form.save(commit=False)
new_course.instructor = request.user
new_course.save()
return redirect('home')
return render(request, 'courses/create.html',{'form':form})
but i think you have to know one more you don't have to pass / in while telling django where to upload that file
class Course(models.Model):
name = models.CharField(max_length=200)
instructor = models.ForeignKey(User, on_delete=models.CASCADE)
video = models.FileField(upload_to='videos')
description = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
def __str__(self):
return self.name
offcourse willem answer will solve your problem but you will see in your media folder a new folder name videos/ got created where you file is storing instead of your actual folder becuase you are telling django to upload that file in folder name vedios/ and if django doesn't find that folder it will create that folder with that name and then stat uploading it

NoReverseMatch error when making update form containing category slug

I'm trying to make a form that updates an existing Category and then redirects back to the Category page. Here is my code:
forms.py
class CategoryForm(forms.ModelForm):
name = forms.CharField(max_length=128, help_text="Please enter the category name.")
description = forms.CharField(max_length=256, required=False, help_text="Please enter the description of the category.")
views = forms.IntegerField(widget=forms.HiddenInput(), initial=0)
slug = forms.CharField(widget=forms.HiddenInput(), required=False)
status = forms.BooleanField(label='Status',
required=False,
help_text="Is this category still active?")
# An inline class to provide additional information on the form.
class Meta:
# Provide an association between the ModelForm and a model
model = Category
fields = ('name', 'description', )
views.py
def update_category(request, category_name_slug):
obj = get_object_or_404(Category, slug=category_name_slug)
form = CategoryForm(request.POST or None, instance=obj)
if form.is_valid():
form.save()
return HttpResponseRedirect(
reverse('obj', kwargs={'category_name_slug':category_name_slug}) )
return render(request, "courses/update_category.html", {'form': form})
update_category.html
{% block main_content %}
<h1>Add a Category</h1>
<form id="update_category_form" method="post" action="">
{% csrf_token %}
{{ form.as_p }}
<br/>
<input type="submit" name="submit" value="Update Category" />
</form>
{% endblock %}
When I try to update using this form, I get a NoReverseMatch error. I have a feeling it has something to do with the views.py. How exactly do I write the first return? I managed to get another update form without a slug to work fine, so is it the slug that's causing the problem? Do I need pk/id? Thanks.
Edit: Here's the urls module:
urls.py
urlpatterns = [
url(r'^$', views.courses, name='courses'),
url(r'^about/', views.about, name='about'),
url(r'^add_category/$', views.add_category, name='add_category'),
url(r'^category/(?P<category_name_slug>[\w\-]+)/$', views.category, name='category'),
url(r'^category/(?P<category_name_slug>[\w\-]+)/add_course/$', views.add_course, name='add_course'),
url(r'^category/(?P<category_name_slug>[\w\-]+)/delete_category/$', views.delete_category, name='delete_category'),
url(r'^category/(?P<category_name_slug>[\w\-]+)/update_category/$', views.update_category, name='delete_category'),
]
You should give the url's pattern name of the Category page to reverse:
def update_category(request, category_name_slug):
...
if form.is_valid():
form.save()
return HttpResponseRedirect(
reverse('category', kwargs={'category_name_slug':category_name_slug}) )
The error lies in the line:
return HttpResponseRedirect(reverse('obj', ...)
You're passing to reverse method, the string 'obj' which is actually a Category object.
An elegant way to do it, is to add a get_absolute_url method inside your Category model, like this:
class Category(models.Model):
# model fields here
def get_absolute_url(self):
return reverse('category', kwargs={'category_name_slug': self.slug})
Then, in your update_category view, simply do:
return HttpResponseRedirect(obj.get_absolute_url())

Django form validation failing - pytz and choices

I am trying to let the user set which timezone they are in, however form validation .is_valid() is failing and cannot figure out why.
The timezone value for a user is stored in a Profile model.
Using ChoiceField and pytz.common_timezones to fill the form field
This would be appear to be quite simple to do, the only thing thats different to my usual way is that the data filling the combo/select box is the use of a ChoiceField and the data is coming from pytz.
I may switch to django-timezone-field to solve this, but I would like to understand why it is failing. I have included all relevant (I think) code below. Any suggestions?
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
timezone = models.CharField(
max_length=255,
blank=True,
)
forms.py
class ProfileEditForm(forms.Form):
profile_timezone = forms.ChoiceField(choices=[(x, x) for x in pytz.common_timezones])
views.py
#login_required
def userprofile_edit(request):
if request.method == "POST":
profile_edit_form = ProfileEditForm()
if profile_edit_form.is_valid():
cd = profile_edit_form.cleaned_data
user = User.objects.get(id=request.user.id)
user.profile.timezone = cd['timezone']
user.profile.save()
messages.success(request, "Profile updated successfully", fail_silently=True)
return redirect('coremgr:userprofile', request.user.id)
else:
messages.error(request, "Error occured. Contact your administrator", fail_silently=True)
print "error: form not valid"
else:
profile_edit_form = ProfileEditForm()
context = {
'profile_edit_form': profile_edit_form,
}
return render(request, 'apps/coremgr/userprofile_edit.html', context)
template
<form name="formprofile" method="POST" action="">
{% csrf_token %}
<p id="profile_timezone" class="form-inline">
{{ profile_edit_form.profile_timezone.errors }}
Timezone:
{{ profile_edit_form.profile_timezone }}
</p>
<button id="id_btn_profile_edit_save" type="submit" class="btn btn-default" tabindex=7>Save</button>
</form>
add request.POST in form
if request.method == "POST":
profile_edit_form = ProfileEditForm(request.POST)
I believe you would need to pass in request.POST when initializing the form in your if block.

Categories

Resources