0

I'm trying to build a form in Django 1.5 and Python 2.7 that manage payments from ether paypal or credit/debit card (it's a dummy project so no real payments occurs). I have two problems:

  • first, the conditions that check the lenght f the card number and the ccv don't work.
  • second, I have two form on the same page and I'd love to show errors only for the forms that was actualy compiled and ignore the empty one. Right now it show the errors for both of them, but I don'tknow if it's avoidable.

This are the forms:

class PaymentFormPaypal(forms.Form):
    paypal = forms.EmailField(required = True, label = 'Your PayPal address')

class PaymentFormCard(forms.Form):
    card = forms.IntegerField(required = True, label = 'Your card number')
    ccv = forms.IntegerField(required = True, label = 'Card secure code')
    expiration = forms.DateField(required = True, label = 'Card expiration date', widget=forms.extras.MonthYearWidget)
    name = forms.CharField(max_length = 30, required = True, label = 'Name of the card owner')
    surname = forms.CharField(max_length = 30, required = True, label = 'Surname of the card owner')

    def clean_card(self):
        card = self.cleaned_data['card']
        if len(str(card)) != 16:
            raise forms.ValidationError("The card number must be 16 digits long")

    def clean_ccv(self):
        ccv = self.cleaned_data['ccv']
        if len(str(ccv)) != 3:
            raise forms.ValidationError("The ccv must be 3 digits long")

    widgets = {
            'expiration': forms.extras.MonthYearWidget(),
    }

this the view:

def payment(request, type):
    if request.method == 'POST':
        form1 = PaymentFormPaypal(request.POST)
        form2 = PaymentFormCard(request.POST)
        if form1.is_valid() or form2.is_valid():
            profile = request.user.get_profile()
            profile.premiumstatus = True
            profile.save()
            form = Search()
            return render(request, 'home.html', {'form': form, 'request': request})
    else:
        form1=PaymentFormPaypal()
        form2=PaymentFormCard()
    return render(request, 'payment.html', {'form1': form1, 'form2': form2, 'request': request})

and this is the HTML page:

{% block content %}
<hr>

    {% if request.user.is_authenticated %}
        {% if form1.errors %}
        <p style="color: red;">
            Please correct the error{{ form1.errors|pluralize }} below.
        </p>
        {% endif %}
        <p>Pay with PayPal.</p>
        <form action="" method="post">
        {% csrf_token %}
            {{ form1.as_p }}
            <input type="submit" value="Buy">
            <input type="reset" value="Reset">
        </form>
        <p>Pay with your card.</p>
        {% if form2.errors %}
        <p style="color: red;">
            Please correct the error{{ form2.errors|pluralize }} below.
        </p>
        {% endif %}
        <form action="" method="post">
        {% csrf_token %}
            {{ form2.as_p }}
            <input type="submit" value="Buy">
            <input type="reset" value="Reset">
        </form>
    {% else %}
        <p>You must be logged to make a payment!</p>
    {% endif %}
<hr>
{% endblock %}      
3
  • What's the value of self.cleaned_data['ccv'] etc.? Commented Dec 24, 2013 at 14:11
  • 1
    can you please post your exact errors? Commented Dec 24, 2013 at 14:13
  • @kroolik: self.cleaned_data['card'] and self.cleaned_data['ccv'] are integers (they are inserted in a forms.IntegerField). @dm03514: any number i set for both card and ccv raise the custom error in clean_card(self) and clean_ccv(self). And if I insert data in only one form the other form complain about it's fields being required. Commented Dec 24, 2013 at 14:19

1 Answer 1

3

Credit cards numbers and CCV are not integers, they are strings containing only digits. Treating them as integers will yield quite a few problems, as you already noticed. Just as simple example:

>>> str(42)
'42'
>>> str(042)
'34'
>>> 

You can use a custom validator to make sure you do have only digits, or check this by yourself in the form's clean_xxx methods.

>>> "1234".isdigit()
True
>>> "A123".isdigit()
False
>>> 

wrt/ your second question: just add an hidden field in each form with the form's name, then check this in your view to know which form has been submitted, ie:

    <p>Pay with PayPal.</p>
    <form action="" method="post">
    {% csrf_token %}
        <input type="hidden" name="whichform" name="paypal" />
        {{ form1.as_p }}
        <input type="submit" value="Buy">
        <input type="reset" value="Reset">
    </form>
    <p>Pay with your card.</p>
    {% if form2.errors %}
    <p style="color: red;">
        Please correct the error{{ form2.errors|pluralize }} below.
    </p>
    {% endif %}
    <form action="" method="post">
    {% csrf_token %}
        <input type="hidden" name="whichform" name="card" />
        {{ form2.as_p }}
        <input type="submit" value="Buy">
        <input type="reset" value="Reset">
    </form>

then in you view:

def payment(request, type):
    if request.method == 'POST':
        whichform = request.POST.get("whichform")

        if whichform == "paypal":
            form = form1 = PaymentFormPaypal(request.POST)
            form2 =  PaymentFormCard()

        elif whichform == "card":
            form1 = PaymentFormPaypal()
            form = form2 =  PaymentFormCard(request.POST)

        else:
            # should not happen
            form1=PaymentFormPaypal()
            form2=PaymentFormCard()
            form = None

        if form and form.is_valid():
            profile = request.user.get_profile()
            profile.premiumstatus = True
            profile.save()
            # XXX fixed this, you want to REDIRECT here
            # Google for "post redirect get" for more infos
            return redirect(<your home url here>)

    else:
        form1=PaymentFormPaypal()
        form2=PaymentFormCard()

    return render(request, 'payment.html', {'form1': form1, 'form2': form2, 'request': request})
Sign up to request clarification or add additional context in comments.

6 Comments

Actually, the problem with treating values as integers is that for both input values 42 and 042 the integer value will be 42, which will lead to a string of value '42'. Note, that the input for IntegerField won't be treated as an octal number in any case, by default.
I thought about that but if i check only len(card) (or len(ccv)) Django raise an error saing that integers do not have len()
@kroolik useful precision thanks. I just quickly tested in a Python shell;)
@Higure : yes that why you want to use CharField, not IntegerField. These are not integers.
@Higure, yeah, go with CharField(min_length=3, max_length=3) and you are golden.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.