I have found here on stackoverflow a method to extend django's built-in authentication using signals. My base User is defined by 'email' and passwords (so no username there). So I'm trying to modify it to my needs, but I'm geting a validation error for my form. Strange thing is that error is connected to the User.email field and I'm getting 'already in use' even though I'm just registering at the moment. Is it trying to save it 2 times or what ? I've discovered it when I was sending dictionary with data to form's contstructor in shell: form = MyForm(data={}). After this form was still invalid, but changing email to different value finally gave me True.
The user_created function connected to registration signal :
def user_created(sender, user, request, **kwargs):
form = CustomRegistrationForm(request.POST, request.FILES)
if form.is_valid():
data = UserProfile(user=user)
data.is_active = False
data.first_name = form.cleaned_data['first_name']
data.last_name = form.cleaned_data['last_name']
data.street = form.cleaned_data['street']
data.city = form.cleaned_data['city']
data.save()
else:
return render_to_response('user/data_operations/error.html', {'errors': form._errors}, context_instance=RequestContext(request))
user_registered.connect(user_created)
My form :
class CustomRegistrationForm(RegistrationForm):
first_name = forms.CharField(widget=forms.TextInput(attrs=attrs_dict), max_length=50)
last_name = forms.CharField(widget=forms.TextInput(attrs=attrs_dict), max_length=50)
street = forms.CharField(widget=forms.TextInput(attrs=attrs_dict), max_length=50)
city = forms.CharField(widget=forms.TextInput(attrs=attrs_dict), max_length=50)
My model :
class UserProfile(models.Model):
first_name = models.CharField(_("Name"), max_length=50, blank=False,)
last_name = models.CharField(_("Last name"), max_length=50, blank=False,)
street = models.CharField(_("Street"), max_length=50, blank=False,)
city = models.CharField(_("City"), max_length=50, blank=False,)
user = models.ForeignKey(User, unique=True, related_name='profile',)
Registration form :
class RegistrationForm(forms.Form):
email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
maxlength=75)),
label=_("Adres email"))
password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
label=_("Haslo"))
password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
label=_("Haslo powtorzone"))
def clean_email(self):
email = self.cleaned_data.get("email")
if email and User.objects.filter(email=email).count() > 0:
raise forms.ValidationError(
_(u"Already in use."))
return email
your 'user_registered' signal is sent after the User is saved. So it already has an 'email' field defined.
UPDATE
Using restless thinking :
form = CustomRegistrationForm(request.POST, request.FILES, notvalidateemail=True)
and in form :
def __init__(self, *args, **kwargs):
self.notvalidateemail = kwargs.pop('notvalidateemail',False)
super(CustomRegistrationForm, self).__init__(*args, **kwargs)
def clean_email(self):
if self.notvalidateemail:
return
else:
#your cleaning here
return email
Problem:
Your form is first saved by django-registration. Then you save it again in user_created.
Solutions:
Use a different form in user_created. One that won't have already saved fields (these from User model like email). You just want to save additional data in user_created, right?
Add some parameters to the form like:
in user_created:
form = CustomRegistrationForm(dontvalidateemail=True, request.POST, request.FILES)
and in form's init;
self.dontvalidateemail = dontvalidateemail
then just check it in clean_email.
Related
For some reason my UpdateView is not displaying the selected associated. The form is pre-populated with only the first form field data results, username, and none of the other field data. I suspect this is due to the ManytoMany field being queried. I've searched for and tried possible solutions but nothing has worked. Any ideas?
Models.py
class Company(models.Model):
"""All company specific details. For each main manager, a company is created. The main manager can then add company members, creating for them their user accounts."""
members = models.ManyToManyField(
settings.AUTH_USER_MODEL, related_name='teams', through='CompanyMembership'
)
...
class CompanyMembership(models.Model):
"""Acts as a gateway between User model and Company model"""
STAFF = "STAFF", "Staff"
MANAGERS = "MANAGERS", "Managers"
my_company = models.ForeignKey(Company, on_delete=models.CASCADE)
my_identity = models.ForeignKey(User, on_delete=models.CASCADE)
my_role = models.CharField(max_length=100, choices=MemberTypes.choices, default=None)
class User(models.Model):
"""Users can be main managers or staff. Each main manager has their own Company. Many staff can belong to a single Company associated with a main manager."""
username = models.CharField(_("Login Name"), blank=True, null=True, max_length=155, unique=True)
...
Views.py
class TeamMembersView(LoginRequiredMixin, CompanyMembershipCheckMixin, UserPassesTestMixin, ListView):
"""Lists all the company team members for the specific company"""
model = Company
template_name = 'users/dashboard/team.html'
def test_func(self):
user_obj = User.objects.get(id=self.request.user.id)
return user_obj.groups.filter(name='Company_Main_Manager').exists()
def get_context_data(self, **kwargs):
context = super(TeamMembers, self).get_context_data(**kwargs)
user_obj = User.objects.get(id=self.request.user.id)
companymembership_obj = CompanyMembership.objects.get(my_identity=user_obj)
company_obj = Company.objects.get(id=companymembership_obj.my_company.id)
slug = company_obj.slug
context['members'] = ecosystem_obj.members.all()
context['slug'] = slug
return context
class TeamMemberUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
"""Allows the main manager to change or update the specific staff member's user data"""
model = User
template_name = 'users/dashboard/user_create.html'
form_class = TeamMemberCreateForm
def test_func(self):
user_obj = User.objects.get(id=self.request.user.id)
return user_obj.groups.filter(name='Company_Main_Manager').exists()
def get_context_data(self, **kwargs):
data = super(TeamMemberUpdateView, self).get_context_data(**kwargs)
if self.request.POST:
data['member'] = TeamMemberCreateForm(self.request.POST, self.request.FILES)
else:
data['member'] = TeamMemberCreateForm()
return data
def form_valid(self, form):
context = self.get_context_data()
member = context['member']
with transaction.atomic():
form.instance.created_by = self.request.user
self.object = form.save()
if member.is_valid():
member.instance = self.object
member.save()
return super(TeamMemberUpdateView, self).form_valid(form)
Forms.py
class TeamMemberCreateForm(ModelForm):
"""Allows the main manager to create (and update) a user account for additional staff members."""
first_name = forms.CharField(max_length=200, help_text="Enter member's first name", required=True)
last_name = forms.CharField(max_length=200, help_text="Enter member's last name")
email = forms.EmailField(max_length=200, help_text="Member's primary email address")
phone = forms.CharField(max_length=100, help_text="Primary phone number")
position_title = forms.CharField(max_length=200, help_text="Member's job position or title for the company")
class Meta(UserCreationForm.Meta):
model = User
exclude = ['company_name']
#transaction.atomic
def save(self):
member_user = super().save(commit=False)
member_user.firstname = self.cleaned_data.get('first_name')
member_user.lastname = self.cleaned_data.get('last_name')
member_user.email = self.cleaned_data.get('email')
member_user.phone = self.cleaned_data.get('phone')
member_user.is_company_main_manager = False
member_user.is_active = True
member_user.user_type = User.UserTypes.STAFF
member_user.save()
CompanyMembership.objects.create(my_company=ecosystem_create, my_identity=user, my_role=CompanyMembership.MemberTypes.STAFF)
return member_user
As an example, if I am the main manager I can create a user account for my staff member through the above form. For the staff member account I enter username="username_example", first_name="pete", last_name="smith", phone="333-333-3333",... and so on. CreateView works as expected. The user account is created, saved, etc. UpdateView, though, display would show in this example:
username: username_example
first name:
last name:
phone:
...
# The first field of the form is pre-populated but none of the others... why?
You have to super init your form and rewrite the field with the
def init(self, *args, **kwargs):
super(TeamMemberCreateForm, self).__init__(*args, **kwargs)
self.fields[your_field] = forms.ModelMultipleChoiceField(
queryset=Company.objects.all(),
widget=forms.CheckboxSelectMultiple(attrs=
{'dir': 'ltr', 'type': "checkbox",'switch':"bool"}))
By using fields = ['username', 'firstname', 'lastname', 'email', 'phone'] instead of form_class = TeamMemberCreateForm in UpdateView, the problem was solved.
I've tried to add another details form that have address and birth date on my registration form page. Every time i tried to sign up i get the NOT NULL constraint failed error.
models.py
class Details(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
address = models.CharField(max_length=60, blank=True)
birth_date = models.DateField(null=True, blank=True)
views.py
def register(request):
if request.method == 'POST':
r_form = UserRegisterForm(request.POST)
o_form = DetailsForm(request.POST)
if r_form.is_valid and o_form.is_valid():
r_form.save()
o_form.save()
username = r_form.cleaned_data.get('username')
messages.success(request, f'Your account has been created!')
return redirect('login')
else:
r_form = UserRegisterForm()
o_form = DetailsForm()
context = {
'r_form' : r_form,
'o_form' : o_form
}
return render(request, 'users/register.html', context)
forms.py
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ['username', 'email','password1', 'password2']
class DetailsForm(forms.ModelForm):
address = forms.CharField()
birth_date = forms.DateField()
class Meta:
model = Details
fields = ['address', 'birth_date']
ERROR
While extraneous details/profile "sidecar" models are not the best practice since pluggable user models came along, this particular problem is caused by the two models not being associated.
Try
if r_form.is_valid and o_form.is_valid():
user = r_form.save()
o_form.instance.user = user
o_form.save()
ValueError: The Custom_User could not be changed because the data didn't validate.
I am trying to make a UserChangeForm to allow customers to edit their address, contact or password. However, for the field "postal code" I have set a restriction where there are only certain postal addresses I want to service.
#forms.py
#Extract list of postal codes which are valid and put into a list
valid_postal_code = []
postal_code_model = PostalCode.objects.all()
for code in postal_code_model:
valid_postal_code.append(code.postal_code)
#form
class EditAccountForm(UserChangeForm):
class Meta:
model = Custom_User
fields = (
'address',
'postal_code',
'unit_number',
'email',
'contact_number',
'password'
)
def clean_postal_code(self):
post_code = self.cleaned_data.get('postal_code')
if post_code not in valid_postal_code:
print('hello')
raise forms.ValidationError('Sorry we do not serve this postal code right now D:')
return post_code
If the user inputs a postal code that is not in the valid_postal_code list, I would like that the form be able to raise an Error message on the form.
However, I get the above error(which is to be expected), straight away without the raising for error.
#views.py
def edit_info_page(request):
if request.method == 'POST':
form = EditAccountForm(request.POST, instance=request.user)
if form.is_valid:
form.save()
print('form changed')
return redirect('home:edit_info_page')
else:
form = EditAccountForm(instance=request.user)
return render(request, 'sign/edit_info.html', {'form':form})
#models
class Custom_User(AbstractUser):
postal_code = models.IntegerField(null=True)
unit_number = models.CharField(max_length=10)
address = models.CharField(max_length=250)
contact_number = models.IntegerField(null=True)
order_count = models.PositiveIntegerField(default=0)
total_spending = models.DecimalField(max_digits=10, decimal_places=2, default=0)
def __str__(self):
return self.username
Above are my models and views for reference. IMO, I think I am definitely missing something here but Im just not too sure how to break into the UserChangeForm. I'm still a relative newbie (haven't sent anything into production yet). Any advice would be great!
In the view change:
if form.is_valid:
to
if form.is_valid():
The validation is not getting executed, which consequently does not validate your post data, which in turn does not allow you to save the user.
Suggestion:
Change
if post_code not in valid_postal_code:
to
if post_code and post_code not in valid_postal_code:
to ensure to raise the error only if user has entered something.
Try this
class EditAccountForm(UserChangeForm):
class Meta:
model = Custom_User
fields = (
'address',
'postal_code',
'unit_number',
'email',
'contact_number',
'password'
)
def clean_postal_code(self):
valid_postal_code = PostalCode.objects.all().values_list('postal_code', flat=True)
post_code = self.cleaned_data.get('postal_code')
if post_code not in valid_postal_code:
raise forms.ValidationError('Sorry we do not serve this postal code right now D:')
return post_code
Better way to write AJAX call for postal_code field and call ajax function from postal_code focus out/blur event,
Or Make the postal code field auto complete or selection field
What I am trying to achieve is having the senders-name, from the current logged in user with the association name, to show up in the receivers inbox like so:
'associaton-name'#domain.com
I have commented it down below where i tried to achieve it in views.py
Can't seem to find any related solutions after days and hours of work.
Really appreciate your help, folks!
Django: 1.10
Python: 3.6
views.py
class mailPost(FormView):
success_url = '.'
form_class = mailHandler
template_name = 'post/post.html'
def form_valid(self, form):
messages.add_message(self.request, messages.SUCCESS, 'Email Sent!')
return super(mailPost, self).form_valid(form)
def form_invalid(self, form):
messages.add_message(self.request, messages.WARNING,
'Email not sent. Please try again.')
return super(mailPost, self).form_invalid(form)
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
sender = "noreply#domain.com" # Instead of noreply I wish for current requested associaton name
receiver = form.cleaned_data.get('receiver')
cc = form.cleaned_data.get('cc')
bcc = form.cleaned_data.get('bcc')
subject = form.cleaned_data.get('subject')
message = form.cleaned_data.get('message')
time = datetime.now()
asoc_pk = Association.objects.filter(asoc_name=self.request.user.association)
asoc = Association.objects.get(id=asoc_pk)
Email.objects.create(
sender=sender,
receiver=receiver,
cc=cc,
bcc=bcc,
subject=subject,
message=message,
association=asoc,
sentTime=time
)
msg = EmailMultiAlternatives(subject, message, sender, [receiver], bcc=[bcc], cc=[cc])
msg.send()
return self.form_valid(form)
else:
return self.form_invalid(form)
models.py
class Email(models.Model):
sender = models.CharField(max_length=254)
sentTime = models.DateTimeField(auto_now_add=True, blank=False)
subject = models.CharField(max_length=254)
receiver = models.CharField(max_length=254)
cc = models.CharField(max_length=254)
bcc = models.CharField(max_length=254)
message = models.TextField()
association = models.ForeignKey(Association)
class Meta:
db_table = 'Email'
class Association(models.Model):
asoc_name = models.CharField(max_length=50, null=True, blank=True, unique=True)
class Meta:
db_table = 'Association'
class Administrator(AbstractUser):
...
association = models.ForeignKey(Association)
class Meta:
db_table = 'Administrator'
I'm not sure I understand your question correctly. You can access the authenticated user (given you are using the Django Authentication system) by calling self.request.user.
You have to create a relation between Association and the user:
class Association(models.Model):
asoc_name = models.CharField(max_length=50, null=True, blank=True, unique=True)
# Option 1 - if one user can be a member of several associations
members = models.ManyToMany(User)
class Meta:
db_table = 'Association'
or a new model instance if a user can only be a member of one association:
# Option 2
class Membership(Model):
association = models.ForeignKey(Association)
user = models.ForeignKey(User, unique=True)
You get the Association using a direct lookup (or a reverse relation).
# option 1
if form.is_valid():
sender = Association.objects.filter(members=self.request.user).first()
# sender might be None
# option 2
if form.is_valid():
membership = Membership.objects.filter(user=self.request.user).first()
if membership:
sender = membership.association
https://docs.djangoproject.com/en/1.11/topics/db/examples/many_to_many/
I have an app with 2 user levels; Superuser, and Staff. Superuser will login to this app and create a new user and assign the user either Superuser or Staff permissions, but they are not logging in through the default Django admin, they will be logging into a custom admin. How do I display the permission checkboxes in my form and how do I save them for that new user being created? Is it merely just a normal checkbox or is there something I need to override to be able to accomplish this?
Here is my CreateUserForm as it is now.
class CreateUserForm(forms.Form):
username = forms.EmailField(max_length=50)
email = forms.EmailField()
first_name = forms.CharField(max_length=150)
last_name = forms.CharField(max_length=150)
password1 = forms.CharField(max_length=30, widget=forms.PasswordInput(render_value=False), label='Password')
password2 = forms.CharField(max_length=30, widget=forms.PasswordInput(render_value=False), label='Password Confirmation')
address_1 = forms.CharField(max_length=50)
address_2 = forms.CharField(max_length=50)
city = forms.CharField(max_length=50)
province = forms.CharField(max_length=2)
country = forms.CharField(max_length=50)
postal_code = forms.CharField(max_length=10)
work_phone = forms.CharField(max_length=20)
mobile_phone = forms.CharField(max_length=20)
fax = forms.CharField(max_length=20)
url = forms.CharField()
comments = forms.CharField(widget=forms.Textarea)
def clean_username(self):
try:
User.objects.get(username=self.cleaned_data['username'])
except User.DoesNotExist:
return self.cleaned_data['username']
raise forms.ValidationError("Sorry, this username has already been taken. Please choose another.")
def clean_email(self):
try:
User.objects.get(email=self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data['email']
raise forms.ValidationError("Sorry, this email has already been taken. Please choose another.")
def clean(self):
if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
if self.cleaned_data['password1'] != self.cleaned_data['password2']:
raise forms.ValidationError("You must type the same password each time.")
if ' ' in self.cleaned_data['username']:
raise forms.ValidationError("username must not contain spaces")
return self.cleaned_data
def save(self):
new_user = User.objects.create_user(
username = self.cleaned_data['username'],
email = self.cleaned_data['email']
)
new_user.first_name = self.cleaned_data['first_name']
new_user.last_name = self.cleaned_data['last_name']
new_user.set_password(self.cleaned_data['password'])
new_user.is_active = True
new_user.save()
new_profile = UserProfile(
# TODO:
)
Thanks
If you mean the permission checkboxes for is_staff and is_superuser, then you're probably best off using a ModelForm w/ the User model. This will automatically take care of everything for you.
class UserForm(forms.ModelForm):
class Meta:
model = User
exclude = ('last_login', 'date_joined')
EDIT per OP update: You can just add 2 forms.BooleanField() fields to your form for is_superadmin and is_staff (or name them differently), and much like you're already doing in the save method you can do new_user.is_staff = self.cleaned_data['is_staff']. You may consider using a choice field instead, w/ a dropdown w/ 3 entries, "Normal User", "Staff User", "Admin User", and then set is_staff and is_superadmin according to the user's selection.
It's also still possible to use a ModelForm on the User model and just add the necessary extra fields in addition. It may or may not be worth it to you, depending how custom you're getting.
Another note about your form - when it comes to editing existing users, this won't be able to be used for that w/ the current clean methods on username/email. But if you tweak those to exclude the current instance (if set) from the lookup, you will be able to use this form for editing existing users (though since it's not a model form you'd need to populate the initial data manually).