How to extend django UserCreationForm model to include phone number field - python

I cant seem to find any posts here regarding extending the Django UserCreationForm model to include a phone number field for users to enter their number and then validate the phone number using phonenumbers.parse in the backend to check if the number is in the respective format and whether it exists or not. I need to know what code I should include in my forms.py under my "users" app.
I've tried including normal html text field for the phonenumbers and it does not belong to the default UserCreationForm model in Django and neither can it be stored in the database. (I need it to be stored in the database for the phone numbers). I am only using forms.py, views.py and register.html to be rendered in views as shown below, currently I am not using models.py.
/* forms.py */
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
# from validate_email import validate_email
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
phone_number = forms.IntegerField(required=True)
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
/* views.py */
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import UserRegisterForm
def register(request):
if request.method == 'POST':
form = UserRegisterForm(request.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
messages.success(request, f'Account created for {username}!')
return redirect('blog-home')
else:
form = UserRegisterForm()
return render(request, 'users/register.html', {'form': form})
/* register.html */
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Join Today</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Sign
Up</button>
</div>
</form>
<div class="border-top pt-3">
<small class="text-muted">
ALready Have An Account? <a class="ml-2" href="#">Sign In</a>
</small>
</div>
</div>
{% endblock content %}
I need to include a phone number field as part of the UserCreationForm in django and validate the number to check if it exists or not and then save the number in the database.

I usually extend Django User Model using OneToOneLink
models.py
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone = models.CharField(max_length=11, blank=True) # change the field to watever works for you
# This will auto create a profile of user with blank phone number that can be updated later.
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
forms.py
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'email')
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('phone')
views.py
def create_user(request):
if request.method == 'POST':
user_form = UserForm(request.POST, instance=request.user)
profile_form = ProfileForm(request.POST, instance=request.user.user_profle)
if user_form.is_valid() and profile_form.is_valid():
user_form.save()
profile_form.save()
messages.success(request, _('New user created successfully'))
return redirect('settings:profile')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_form = UserForm(instance=request.user)
profile_form = ProfileForm(instance=request.user.user_profile)
return render(request, 'template_name.html', {
'user_form': user_form,
'profile_form': profile_form
})
template:
<form method="post">
{% csrf_token %}
{{ user_form.as_p }}
{{ profile_form.as_p }}
<button type="submit">Save changes</button>
</form>

UPDATE to initial answer. I decided while the solution below worked it was preferable to inherit the AbstractUser model and add my own requirements in my own bespoke user model. Once in place it's far more straight forward dealing with views and templates. I hesitated at first as I didn't feel confident enough to mess around with the default User model, but it is actually very simple. It also helped me understand abstracting models in general via.
class Meta:
abstract = True
The posts here were very helpful.
How to Extend Django User model using AbstractUser
Extending User Model
Previous post:
I've been struggling with this issue also. I've found a solution that works ok, but may have some pitfalls, which it would be good to get views on. My solution is a combination of the other answer to this question, with two modifications. Firstly the code above should be used to update a user rather than create one, because at registration no user profile exists so can't be called. Secondly, I removed the create_user_profile method on the model and used the answer posted here How to Extend UserCreateForm
to save the extended user information at registration. The reason for removing the create_user_profile was to prevent interference with the save() method on the form. The extended model i'm using is called Account.
I also found this article useful extending the django user model, and I'm still considering whether one of the other options might be more appropriate.
My code looks like this:
Views:
def register_view(request):
form = AccountRegisterForm(request.POST or None)
if form.is_valid():
form.save()
return redirect("accounts:login")
context = {"form": form}
return render(request, "accounts/register.html", context)
def user_update_view(request):
user_obj = User.objects.get(username=request.user)
account_obj = Account.objects.get(user=request.user)
user_form = UserForm(request.POST or None, instance=user_obj)
account_form = AccountForm(request.POST or None, instance=account_obj)
if user_form.is_valid() and account_form.is_valid():
user_form.save()
account_form.save()
return redirect(reverse("accounts:detail"))
context = {
"account_form": account_form,
"user_form": user_form,
}
return render(request, "accounts/account_update.html", context)
Forms
class AccountRegisterForm(UserCreationForm):
group = forms.ModelChoiceField(queryset=Group.objects)
dir = forms.ModelChoiceField(queryset=Directorate.objects)
class Meta:
model = User
fields = (
"username",
"first_name",
"last_name",
"group",
"dir",
)
def save(self, commit=True):
if not commit:
raise NotImplementedError(
"Can't create User and UserProfile without database save"
)
user = super(AccountRegisterForm, self).save(commit=True)
user_account = Account(
user=user,
group=self.cleaned_data["group"],
dir=self.cleaned_data["dir"],
)
user_account.save()
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ("username", "first_name", "last_name")
class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = (
"group",
"dir",
)

Related

Does not reflect current user in comment

I'm creating an Instagram-style application with Django 3.2.5, where users have profiles to visit and upload photos. I am implementing a commenting system.
When generating the comment as a user with a registered profile, the comment is rendered as if it were anonymous.
I have investigated and it happens that in the model of the comment I made a relationship with the user and profile giving the null parameter as true.
When removing this parameter and resubmitting a comment on a post I get a Not Null Constraint Failed error for profile_id and for user_id.
The image shows the first comments made from the admin. And the last one with the null = true parameter in the model.
Sample picture
models.py
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
user = models.ForeignKey(User, on_delete=models.CASCADE)
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
text = models.TextField(max_length=200)
created_date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['created_date']
def __str__(self):
return self.text
views.py
def create_comment(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect('posts:feed')
else:
form = CommentForm()
return render(request, 'posts/feed.html', {
'form': form,
})
urls.py
urlpatterns = [
path(r'^post/(?P<pk>[0-9]+)/comment/$', create_comment, name='create'),]
forms.py
class CommentForm(forms.ModelForm):
pass
class Meta:
model = Comment
fields = ['text']
HTML
<form action="{% url 'comments:create' pk=post.pk %}" method="POST" >
{% csrf_token %}
<div class="input-group">
<input type="text" class="form-control" placeholder="Comment here" aria-label="text" name="text" value="{{ post.comment.pk}}" >
<div class="input-group-append">
<button class="btn btn-dark" type="submit" id="button-addon2"><i class="fas fa-comment"></i></button>
</div>
</div>
</form>
You should link the .user attribute to the logged in user, so:
from django.contrib.auth.decorators import login_required
#login_required
def create_comment(request, pk):
if request.method == 'POST':
form = CommentForm(request.POST, request.FILES)
if form.is_valid():
form.instance.post_id = pk
form.instance.user = request.user
form.save()
return redirect('posts:feed')
else:
form = CommentForm()
return render(request, 'posts/feed.html', {'form': form})
By making use of .post_id = pk, we no longer query the database to obtain the post first, validation is done when creating the Comment instance.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].

Attribute error: 'AnonymousUser' object has no attribute '_meta'

I'm quite new to python and django. Here is my little project and this is the error message I get when I'm trying to log in as a user to my account. I have tried all the solutions which I gained by searching. Please if you know the solution explain it to me quite simply.
My log in view
def login_view(request):
if request.method == 'POST':
form = AuthenticationForm(data=request.POST, request=request)
if form.is_valid:
user = form.user_cache
login(request, user)
return redirect('accounts:user-home')
else:
form = AuthenticationForm()
return render(request, 'login.html', {'form':form})\
My forms.py
# import form class from django
from django.forms import ModelForm
# import GeeksModel from models.py
from .models import *
from django.contrib.auth.models import User
# create a ModelForm
class ProfileForm(ModelForm):
# specify the name of model to use
class Meta:
model = Profile
exclude = [
'user',
]
class UsernameChangeForm(ModelForm):
class Meta:
model = User
fields = ['username', ]
My login.html
<h2>Log In</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Log In</button>
</form>
<p>Don't have an account yet? Register</p>

How to automatically link the users table to another table once a user gets registered in django

I am trying to create an e-commerce website, in which I have used the functionality of the Django authentication to allow users to register and login. However, in the registration form, I take the information from 2 tables at once, like this...
In my models.py...
class Profile (models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
mobileNumber = models.CharField(max_length=13)
address = models.ForeignKey(Shipping, on_delete=models.DO_NOTHING, default=False)
guest = models.BooleanField(default=False)
class Shipping(models.Model):
addressLine1 = models.CharField(max_length=100)
addressLine2 = models.CharField(max_length=100)
city = models.CharField(max_length=40)
postalCode = models.CharField(max_length=5)
landmark = models.CharField(blank=True, max_length=80)
And my forms.py...
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import Profile
from store.models import Shipping
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
first_name = forms.CharField()
last_name = forms.CharField()
mobile_number = forms.CharField()
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'mobile_number', 'email', 'password1', 'password2']
class AddressRegisterForm(forms.ModelForm):
class Meta:
model = Shipping
fields = ['addressLine1', 'addressLine2', 'city', 'postalCode', 'landmark']
My signals.py...
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile
#receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save()
My views.py...
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import UserRegisterForm
from .forms import AddressRegisterForm
from .models import Profile
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from . import signals
import django.dispatch
# Create your views here.
def register(request):
if not request.user.username :
if request.method == 'POST':
form = UserRegisterForm(request.POST)
form1 = AddressRegisterForm(request.POST)
if form.is_valid() and form1.is_valid():
email = form.cleaned_data.get('email')
if not User.objects.filter(email=email).exists():
username = form.cleaned_data.get('username')
messages.success(request, f"The Account created for {username}! You are now able to login.")
form1.save()
form.save()
return redirect('login')
else:
messages.warning(request, f"The email is either invalid or already used to create an account")
else:
form1 = AddressRegisterForm()
form = UserRegisterForm()
return render (request, 'register.html', {"form":form, "form1":form1})
else:
username = request.user.username
messages.warning(request, f"You are already logged in as {username}!")
return redirect ('home')
And finally my template...
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% load static %}
{% block title %}Register{% endblock %}
{% block content %}
<div class="content-section mb-5">
<form method="POST">{% csrf_token %}
<div style="display: flex; width: 100%; justify-content: space-around;">
<fieldset class="form-group mt-4" style="width: 48%;">
<legend class="border-bottom mb-4">Register</legend>
{{ form | crispy }}
</fieldset>
<fieldset class="form-group mt-3" style="width: 48%;">
<legend class="border-bottom mb-4">Shipping info</legend>
{{ form1 | crispy }}
</fieldset>
</div>
<div class="form-group mt-3">
<button class="btn btn-outline-info" type="submit">Sign up</button>
<small class="text-muted ml-3">Forgot Password?</small>
</div>
</form>
<div class="border-top pt-3">
<small class="text-muted">Already have an account? Sign in</small>
</div>
</div>
{% endblock %}
My information gets stored in the relevant tables, however, there seems to be no effect on the Profile table and remains to be empty...
Is there any way I can fire off django to link the user and the address in the Profile table once the user is registered.
Any help would be appreciated,
Thanks a lot!!
You need to load the signals in the ready() function of your app configuration:
Where should this code live?
Strictly speaking, signal handling and registration code can live anywhere you like, although it’s recommended to avoid the application’s root module and its models module to minimize side-effects of importing code.
In practice, signal handlers are usually defined in a signals submodule of the application they relate to. Signal receivers are connected in the ready() method of your application configuration class. If you’re using the receiver() decorator, import the signals submodule inside ready().
yourapp/apps.py:
class YourAppConfig(AppConfig):
name = "yourapp"
def ready(self):
from . import signals
That should be enough to get the signals registered at the correct time.
Regarding two issues:
Existing user models in your database won't get updated, only new ones. Either drop your db and create new users or add a data migration if your database is live or you want to keep the current.
Models connected by signals must be created without having any data for them. So you either must have nullable fields or fields with defaults.
Two forms
So you've posted 2 forms as one form in the HTML, this works because your forms don't share field names (except primary key, which isn't posted). For future reference, you can use a form prefix to deal with duplicate field names and even use the same form for different purposes (shipping and billing address for example).
On signals
Signals are used when an event happens (user is saved, user logs in), that is part of 3rd party code (in this case Django). But when you're the one creating the event, they are not needed as you can just do whatever you want right at where you create the event.
Logged in or not
The correct test is:
if not request.user.is_authenticated:
On to the solution
When saving related models with one or more forms, you should first create the models that can be saved without the other model of the relationship existing. These are:
The model a foreign key points to (Shipping).
The model of a OneToOneField, where the OneToOneField is not defined (User)
Due to the signal, our profile model will be created with empty values, but the form does not know that the mobile_number belongs on the Profile model, because it is a ModelForm bound to the User Model. It treats mobile_number as a field that you will handle yourself. So let's do that:
user = form.save() # save returns the model instance created
profile = user.profile
profile.mobile_number = form.cleaned_data['mobile_number']
As said, we can create the address without problems and then we can link it to the profile:
address = form1.save() # save returns the model instance created
profile.address = address
And now we can save the profile:
profile.save()
So putting it all together:
def register(request):
if not request.user.is_authenticated: # The correct way to test for logged in user
if request.method == "POST":
form = UserRegisterForm(request.POST)
form1 = AddressRegisterForm(request.POST)
if form.is_valid() and form1.is_valid():
email = form.cleaned_data.get("email")
if not User.objects.filter(email=email).exists():
username = form.cleaned_data.get("username")
messages.success(
request,
f"The Account created for {username}! You are now able to login.",
)
user = form.save()
profile = user.profile
profile.mobile_number = form.cleaned_data['mobile_number']
address = form1.save()
profile.address = address
profile.save()
return redirect("login")
else:
messages.warning(
request,
f"The email is either invalid or already used to create an account",
)
else:
form1 = AddressRegisterForm()
form = UserRegisterForm()
return render(request, "register.html", {"form": form, "form1": form1})
else:
username = request.user.username
messages.warning(request, f"You are already logged in as {username}!")
return redirect("home")
You can add all the fields related to the shipping address in the "profile" model.
When you want to access that data, you can simply use querysets.
In the models
class Profile (models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
mobileNumber = models.CharField(max_length=13)
address = models.ForeignKey(Shipping, on_delete=models.DO_NOTHING, default=False)
guest = models.BooleanField(default=False)
addressLine1 = models.CharField(max_length=100)
addressLine2 = models.CharField(max_length=100)
city = models.CharField(max_length=40)
postalCode = models.CharField(max_length=5)
landmark = models.CharField(blank=True, max_length=80)
and when you want to access the data, you can simply use querysets

Django form manytomany field not saving to database

I am new to Django and i am working on a website where user can submit a post. Django Form is not saving in database when i have manytomany field in model. I do not know if i can achieve this in Django, I want to attached other user names to the post so that when i submit the form the user name is selected automatically when i check on the post in admin. I will attach a screenshots for clarity.
This image below is my form, as you can see 'My post' is the image_caption while 'andy' is another user name, i want 'andy' selected automatically in manytomany field when form is submitted.
This is what i want when the form is submitted then i check in admin. The other user name (andy) is selected in manytomany field when the form is submitted. I did this manually
Model:
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,blank=True,null=True)
profile_pic = models.ImageField(upload_to='ProfilePicture/', default="ProfilePicture/user-img.png", blank=True)
class Post(models.Model):
poster_profile = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,blank=True,null=True)
image_caption = models.TextField(blank=True, null=True)
tag_someone = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='tagged_users', blank=True)
Forms:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = (
'image_caption',
'tag_someone',
)
Views:
def upload_view(request):
ImageFormset = modelformset_factory(File, fields=('files',), extra=20)
if request.method == "POST":
form = PostForm(request.POST)
formset = ImageFormset(request.POST, request.FILES)
if form.is_valid() and formset.is_valid():
post = form.save(commit=False)
post.poster_profile = request.user
post.save()
form.save_m2m()
for f in formset:
try:
photo = File(post=post, files=f.cleaned_data['files'])
photo.save()
except Exception as e:
break
return redirect('/')
else:
form = PostForm()
formset = ImageFormset(queryset=File.objects.none())
#User Name Auto-complete In Tag Form
all_users = User.objects.values_list('username', flat=True)
context = {
'form': form,
'formset': formset,
'all_users': all_users,
}
return render(request, 'upload.html', context)
Upload.html:
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
{{ formset }}
<button type="submit" class="btn btn-primary btn-sm btn-block w-25">Post</button>
</form>
I was able to get this working by changing widget to:
widget = {
forms.Select(),
}
You can change the widget here. Widgets are the thing that is responsible for outputting the inputs to HTML. So you just need:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = (
'image_caption',
'tag_someone',
)
widgets = {'tag_someone': forms.TextInput}

get_user_model doesn't return a value

as my first project in Django I am creating a todo app, that lets people log in and see their own tasks that they created. For that, I need to save author info in single task data.
From what I learned reading the documentation and doing lots of google-searching, the current approach is to use the get_user_model function from django.contrib.auth.
The problem is, that whenever I try to use it in my model, it seems to not get the username from the currently logged in user. While printing the form.errors to my console, the output is:
<ul class="errorlist"><li>added_by<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
Seems like the get_user_model is not returning any value. Can anyone recommend a better approach for me to do that? Or is there something obvious that I missed?
Here are the code snippets:
models.py
from django.db import models
from django.contrib.auth import get_user_model
class Task(models.Model):
title = models.CharField(max_length=35)
completed = models.BooleanField(default=False)
created_date = models.DateTimeField(auto_now_add=True)
added_by = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
def __str__(self):
return self.title
forms.py
from django import forms
from .models import *
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = '__all__'
widgets = {
'title': forms.TextInput(attrs={'class': 'new_task_text', 'placeholder': 'Add new task'}),
}
views.py
#login_required
def list_homepage(request):
tasks = Task.objects.all()
form = TaskForm()
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
print(form.cleaned_data)
else:
print(form.errors)
return redirect('/list/home')
context = {
'page_title': 'Todo list',
'tasks': tasks,
'form': form,
}
return render(request, 'tasks/list.html', context)
form in template:
<form method="POST", action="#"> {% csrf_token %}
{{ form.title }}
<input class="submit" type="submit", value="Create task">
</form>
Thank you in advance for any help!
Your form template only includes the title field, you either need to add the added_by field to this form, or add it in the view handling
{{ form.added_by }}
or
if request.method == 'POST':
form = TaskForm({**request.POST, **{"added_by": request.user}})

Categories

Resources