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
Related
I have two models:
class Checklist(models.Model):
author = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
client_name = models.ForeignKey(Client, on_delete=models.CASCADE,
related_name='details')
fieldA = models.CharField(max_length=25, blank=True, null=True)
fieldA_Check= models.BooleanField(default=False)
fieldB = models.CharField(max_length=25, blank=True, null=True)
fieldB_Check= models.BooleanField(default=False)
class Client(models.Model):
client_fieldA = models.CharField(max_length=25)
client_fieldB = models.CharField(max_length=25)
client_fieldC = models.CharField(max_length=25)
Now I trying to change Client instance fields values via Checklist using following code:
#login_required
def create_order(request, pk):
instance = get_object_or_404(CheckList, id=pk)
form = myForm(request.POST or None, instance=instance)
if form.is_valid():
form.save()
messages.success(request, 'Test message')
return get_PDF_order(request, pk)
return render(request, 'templates/create_order.html', {'form': form})
here is my form:
class myForm(forms.ModelForm):
class Meta:
model = Client
fields = ('client_fieldA', 'client_fieldB', 'client_fieldC')
widgets = {
'client_fieldA': forms.TextInput(attrs={'class': 'form-control'}),
'client_fieldB': forms.TextInput(attrs={'class': 'form-control'}),
'client_fieldC': forms.TextInput(attrs={'class': 'form-control'}),
}
And it works but only once. Data saved and I get a pdf file with this data, but subsequent attempts do not change anything. The field value still the same. I'm guessing that it is because I working with the Checklist instance but not Client. So, my questions are: 1) How I can get an access to all Client fields (a queryset of all Client fileds) via Checklist? 2) Although my code is incorrect - why does it work (once)? I mean why does it save first time?
Thank you all in advance!
To formalize the answer, the instance is actually an instance of the model the form is based on. Thus, in your case, you cannot pass a CheckList instance to a form based on Client
Anyway, as far as you have the CheckList id, you can easily obtain the Client, and the code for your view will be:
#login_required
def create_order(request, pk):
instance = get_object_or_404(CheckList, id=pk)
form = myForm(request.POST or None, instance=instance.client_name)
if form.is_valid():
form.save()
messages.success(request, 'Test message')
return get_PDF_order(request, pk)
return render(request, 'templates/create_order.html', {'form': form})
You pass it always the same instance, so what happens is that you are probably overwriting the same record over and over.
I would recommend you to split this using the django-provided generic class-based views CreateView and UpdateView.
Stop reinventing the wheel, use class-based views. It'll pay off big time in the long term.
I am unsure how to tie a logged in user to a submitted form using regular Django forms. I see alot of examples using ModelForms but none (that I can tell) without using the ModelForms. In my forms.py im having a hard time figuring out how to add the author field. I cannot just add author = forms.ForeignKey or something like that. Then somehow in my view i need to call the author field to be saved into the database (my below example is my best guess and probably not right with the "tenant_form.author = request.user").
I have a model that looks like this and has a user Foreignkey setup:
class AppyModel(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
date_time_form_filled = models.DateTimeField(auto_now_add=True)
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
I have a forms.py:
class TenantForm(forms.Form):
first_name = forms.CharField(required=False, label='First Name')
last_name = forms.CharField(required=False, label='Last Name')
I have a views.py
#login_required
def tenant_create_form_view(request):
tenant_form = TenantForm()
if request.method == 'POST':
tenant_form.author = request.user
tenant_form = TenantForm(request.POST)
if tenant_form.is_valid():
print(tenant_form.cleaned_data)
AppyModel.objects.create(**tenant_form.cleaned_data)
else:
print(tenant_form.errors)
context = {
'form': tenant_form
}
return render(request, 'fill_appy.html', context)
You should add author when the form is valid,
tenant_form = TenantForm()
if request.method == 'POST':
tenant_form = TenantForm(request.POST)
if tenant_form.is_valid():
obj = tenant_form.save(commit=False)
obj.author = request.user #add author here
obj.save()
# .. rest of code
I am integrating Stripe payment processing into my Django app, and I can't figure out the 'correct' way to verify the customer's card information and insert a row into my Users table that contains the user's Stripe Customer ID.
Ideally, I'd love to do something along the lines of the following, in which my CheckoutForm verifies card details and raises a form ValidationError if they are incorrect. However, using this solution, I can't figure a way to get the customer.id that's generated out of the clean() function.
forms.py
class CheckoutForm(forms.Form):
email = forms.EmailField(label='E-mail address', max_length=128, widget=forms.EmailInput(attrs={'class': 'form-control'}))
stripe_token = forms.CharField(label='Stripe token', widget=forms.HiddenInput)
def clean(self):
cleaned_data = super().clean()
stripe_token = cleaned_data.get('stripe_token')
email = cleaned_data.get('email')
try:
customer = stripe.Customer.create(
email=email,
source=stripe_token,
)
// I can now get a customer.id from this 'customer' variable, which I want to insert into my database
except:
raise forms.ValidationError("It looks like your card details are incorrect!")
views.py
# If the form is valid...
if form.is_valid():
# Create a new user
user = get_user_model().objects.create_user(email=form.cleaned_data['email'], stripe_customer_id=<<<I want the customer.id generated in my form's clean() method to go here>>>)
user.save()
The only other solution I can think of is to run the stripe.Customer.create() function in views.py after the form is validated. That'll work, but it doesn't seem like the 'right' way to code things, since as I understand it all validation of form fields is supposed to be done within forms.py.
What's the proper Django coding practice in this situation? Should I just move my card validation code to views.py, or is there a cleaner way to keep the card validation code within forms.py and get the customer.id out of it?
I don't think that proper Django coding practice is any different from Python coding practice in this situation. Since Django form is just a class, you can define property for customer. Something like this:
class CheckoutForm(forms.Form):
email = forms.EmailField(label='E-mail address', max_length=128, widget=forms.EmailInput(attrs={'class': 'form-control'}))
stripe_token = forms.CharField(label='Stripe token', widget=forms.HiddenInput)
_customer = None
def clean(self):
cleaned_data = super().clean()
stripe_token = cleaned_data.get('stripe_token')
email = cleaned_data.get('email')
try:
self.customer = stripe.Customer.create(
email=email,
source=stripe_token,
)
except:
raise forms.ValidationError("It looks like your card details are incorrect!")
#property
def customer(self):
return self._customer
#customer.setter
def customer(self, value):
self._customer = value
Then it the views.py after form.is_valid(), you'd call this property.
if form.is_valid():
customer = form.customer
Or maybe #property is an overkill and you could simply do it like this:
class CheckoutForm(forms.Form):
email = forms.EmailField(label='E-mail address', max_length=128, widget=forms.EmailInput(attrs={'class': 'form-control'}))
stripe_token = forms.CharField(label='Stripe token', widget=forms.HiddenInput)
customer = None
def clean(self):
cleaned_data = super().clean()
stripe_token = cleaned_data.get('stripe_token')
email = cleaned_data.get('email')
try:
self.customer = stripe.Customer.create(
email=email,
source=stripe_token,
)
except:
raise forms.ValidationError("It looks like your card details are incorrect!")
... and still form.customer in views.py.
I guess both should work, but I haven't tested the code.
I've read every "InterityError" + "may no be NULL" post and still can't track down what's causing this error.
I've got a two-part signup form. First part is just selecting a product. That passes a product ID to the next page as part of the URL, where they input personal info. I can get the form to work fine until I start removing fields -- i'm using model forms -- because some fields don't need to be displayed.
Here's my model, and the modelForm:
class SimpleSubscriber(models.Model):
name = models.CharField(max_length=255)
address = models.CharField(max_length=200)
city = models.CharField(max_length=100)
state = models.CharField(max_length=2)
zipcode = models.CharField(max_length=9)
phone = models.CharField(max_length=10)
email = models.EmailField()
date_created = models.DateTimeField(null=True)
sub_type = models.ForeignKey(Product)
def __unicode__(self):
return self.name
class SubscriberForm(ModelForm):
class Meta:
model = SimpleSubscriber
fields = ('name', 'address', 'city', 'state', 'zipcode', 'phone', 'email', 'sub_type',)#'date_created',
And here's my views:
def select_product(request):
title = "get yourself an e-edition. wurd."
pform = Product.objects.order_by('product_active')
if request.method == 'POST': # If the form has been submitted...
pform = ProductForm(request.POST) # A form bound to the POST data
if pform.is_valid(): # All validation rules pass
# ...
return HttpResponseRedirect('signup/%i' % pform.id) # Redirect after POST
else:
form = ProductForm() # An unbound form
return render_to_response('signup/index.html', {'title': title, 'pform': pform}, context_instance=RequestContext(request))
def subscriber_signup(request, product_id):
productchoice = Product.objects.get(id=product_id)
now = datetime.datetime.now()
title = "We need some information."
if request.method == 'POST': # If the form has been submitted...
sform = SubscriberForm(request.POST) # A form bound to the POST data
if sform.is_valid(): # All validation rules pass
sform.date_created = now
sform.sub_type = productchoice
sform.save()
return HttpResponseRedirect('thankyou/') # Redirect after POST
else:
sform = SubscriberForm() # An unbound form
return render_to_response('signup/detail.html', {'title': title, 'sform': sform, 'productchoice': productchoice, 'now': now.date(),}, context_instance=RequestContext(request))
I think it has something to do with the modelForm, but I'm pretty new, so I really have no idea. If I add all the fields to SubscriberForm, then they get filled out and everything works fine. But I don't want users to have to say when they filled out the form, so i put sform.date_created = now and I want the product_id to be filled in automatically by what choice they picked on the previous page. but if I exclude these fields from the form it throws the IntegrityError, which isn't very helpful in explaining what to change.
Any hints on where I'm messing up?
Thanks,
Two things:
1) You may benefit from using exlude in your form definition:
class SubscriberForm(ModelForm):
class Meta:
model = SimpleSubscriber
exclude = ('date_created', )
2) To your question, heres how to fix it:
if sform.is_valid(): # All validation rules pass
suscriber = sform.save(commit=False)
suscriber.date_created = now
suscriber.sub_type = productchoice
suscriber.save()
Alternatively to #fceruti's suggestion, you can also add more kwarg tags null=True on the model's field where appropriate - only forcing a minimal set of fields to be completed in the form.
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.