user7349440
user7349440

Reputation:

Django 1.11.6: 'str' object has no attribute 'payment_method'

I'm writing valid inputs into a form so I can save it into my database, however, it will not save because of the following error:

Traceback

C:\Python34\lib\site-packages\django\core\handlers\exception.py in inner response = get_response(request) ...

C:\Python34\lib\site-packages\django\core\handlers\base.py in _get_response response = self.process_exception_by_middleware(e, request)

C:\Python34\lib\site-packages\django\core\handlers\base.py in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs)

G:\NEA Computer Science\mysite\finance\views.py in record_input if form.is_valid(): # check whether it's valid

C:\Python34\lib\site-packages\django\forms\forms.py in is_valid return self.is_bound and not self.errors

C:\Python34\lib\site-packages\django\forms\forms.py in errors self.full_clean()

C:\Python34\lib\site-packages\django\forms\forms.py in full_clean self._post_clean()

C:\Python34\lib\site-packages\django\forms\models.py in _post_clean self.instance.full_clean(exclude=exclude, validate_unique=False)

C:\Python34\lib\site-packages\django\db\models\base.py in full_clean self.clean_fields(exclude=exclude)

C:\Python34\lib\site-packages\django\db\models\base.py in clean_fields setattr(self, f.attname, f.clean(raw_value, self))

C:\Python34\lib\site-packages\django\db\models\fields__init__.py in clean self.run_validators(value)

C:\Python34\lib\site-packages\django\db\models\fields__init__.py in run_validators v(value)

G:\NEA Computer Science\mysite\finance\models.py in validate_pay_method if self.payment_method != "Cash" and self.list_price == self.deposit: AttributeError: 'str' object has no attribute 'payment_method'

I think this is something to do with PAYMENT_CHOICES and the type of value it is, but I cannot think about how to go about it. Thank you!

Code: views.py

from django.http import HttpResponse
from django.shortcuts import render, redirect
from .models import Customer, Dealer
from .forms import CustomerForm
import datetime

# Lists all dealers and links them to their welcome page
def index(request):
    dealer_list = Dealer.objects.order_by('name')
    html = "<ol>"
    for dealer in dealer_list:
        html += "<li><a href='"+str(dealer.id)+"/'>"+dealer.name+"</a></li>"
    html += "</ol>"
    return HttpResponse(html)


# Welcome page for dealer
def welcome(request, dealer_id):
    html = "Welcome!<br>"
    # Link to results page
    html += "<a href='/finance/"+str(dealer_id)+"/results/'>""See your performance""</a><br>"
    # Link to input page
    html += "<a href='/finance/"+str(dealer_id)+"/record_input/'>""Enter a new record""</a>"
    return HttpResponse(html)


# Outputs the results of targets and statistics for a specific dealer
def results(request, dealer_id):
    response = """Below are your results: 
                <input type='button' value='Print' onClick='javascript:window.print()' /><br>"""  # Print button
    response += "Finance deals made in the last 30 days: <br>"
    date_threshold = datetime.date.today() - datetime.timedelta(days=30)
    response += str(Customer.objects.filter(date_ordered=date_threshold))
    response += "All your customers: "+str(Customer.objects.filter(dealers=dealer_id))+"<br>"
    return HttpResponse(response)

# Input form for dealers needing to enter a record into the database
def record_input(request, dealer_id):
    if request.method == "POST":   # if this is a POST request, process the form data
        form = CustomerForm(request.POST)  # create a form instance and populate it with data from the request
        if form.is_valid():  # check whether it's valid
            form += Customer(finance_balance=Customer.validate_balance(), monthly_payment=Customer.validate_month_pay())
            form.save()  # Save it!
            return redirect(success)
    # if GET (or any other method), create a blank form
    else:
        form = CustomerForm()

    return render(request, '../templates/finance/form_input.html', {'form': form})

def success(request, dealer_id):
    submitted = "SUBMITTED - Thank you!"
    return HttpResponse(submitted)

models.py

from django.db import models
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from django.db.models import F

class Dealer(models.Model):
    REGION_CHOICES = (
        ("Northern", "Northern"),
        ("Central", "Central"),
        ("Southern", "Southern")
    )

    def __str__(self):
        return self.name

    def validate_commission(self):
        a = Customer().return_pay_method()
        if a != "Cash":
            self.commission = F("commission") + 200  # Dealer gains £200 for every financed deal
            self.achieved_deals = F("achieved_deals") + 1  # Add 1 to achieved_deals for the dealer
            if self.achieved_deals > self.target:
                self.commission = F("commission") + 200  # If the target is achieved, gain £400 per deal
        return int(self.commission), int(self.achieved_deals)

    name = models.CharField(max_length=50)
    region = models.CharField(max_length=8, choices=REGION_CHOICES)
    target = models.PositiveIntegerField()
    achieved_deals = models.PositiveIntegerField()
    commission_earned = models.PositiveIntegerField(default=0)


class Product(models.Model):
    def __str__(self):
        return self.last_order

    last_order = models.DateField(auto_now=True)
    number_of_orders = models.PositiveIntegerField()

class Login(models.Model):
    def __str__(self):
        return self.user

    def validate_password(self):
        if len(self.password) < 8:
            raise ValidationError(
                _("Password is not long enough."))

    dealer = models.OneToOneField(Dealer)
    user = models.CharField(max_length=30)
    password = models.CharField(max_length=50, validators=[validate_password])

class Customer(models.Model):
    New = "New"
    Used = "Used"
    CONDITION_CHOICES = (
        (New, "New"),
        (Used, "Used")
    )

    MANUFACTURER_CHOICES = (
        (1, "Citroën"),
        (2, "Peugeot"),
        (3, "DS")
    )

    PCP = "PCP"   # Personal Contract Purchase
    PCH = "PCH"   # Personal Contract Hire
    HP = "HP"     # Hire Purchase
    Cash = "Cash"
    PAYMENT_CHOICES = (
        (PCP, "PCP"),
        (PCH, "PCH"),
        (HP, "HP"),
        (Cash, "Cash")
    )

    def __str__(self):
        return "%s %s" % (self.first_name, self.last_name)

    def return_pay_method(self):
        return str(self.payment_method)

    def return_list_price(self):
        return int(self.list_price)

    def return_deposit(self):
        return int(self.deposit)

    def validate_price(self):  # List price cannot be less than the deposit
        a = Customer.return_list_price(self)
        b = Customer.return_deposit(self)
        if a < b:
            raise ValidationError(
                  _('List price cannot be greater than the deposit.'))

    def validate_pay_method(self):  # Ensures the correct payment method is used
        if self.payment_method != "Cash" and self.list_price == self.deposit:
            raise ValidationError(
                _('Payment method is not cash.'))

    def validate_month_pay(self):  # Validates the monthly payment amount
        numerator = self.list_price * ((self.interest_rate / 100) / 12)  # Formula for monthly payment calculation
        denominator = (1 - (1 + (self.interest_rate / 100) / 12)) ** (self.agreement_length * -1)
        actual_monthly_payment = round((numerator / denominator), 2)  # Rounds correct payments to 2 decimal places
        self.monthly_payment = actual_monthly_payment
        return self.monthly_payment

    def validate_balance(self):  # Validates the balance left to pay on finance
        self.finance_balance = self.list_price - self.deposit
        return int(self.finance_balance)

    def validate_interest(self):  # Validates to ensure the interest rate isn't negative
        if self.interest_rate < 0:
            raise ValidationError(
                _("Interest rate must be positive."))

    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    address_line_1 = models.CharField(max_length=100)
    address_line_2 = models.CharField(max_length=100)
    town = models.CharField(max_length=60)  # Validated up to the longest town name in Britain
    postcode = models.CharField(max_length=7)
    dealers = models.ManyToManyField(Dealer)
    date_ordered = models.DateField(auto_now_add=True)
    manufacturers = models.ManyToManyField(Product, max_length=7, choices=MANUFACTURER_CHOICES)
    vehicle_model = models.CharField(max_length=50, default="")
    vehicle_reg = models.CharField(max_length=7, unique=True)
    vehicle_condition = models.CharField(max_length=4, choices=CONDITION_CHOICES)
    payment_method = models.CharField(max_length=4, choices=PAYMENT_CHOICES, validators=[validate_pay_method])
    list_price = models.PositiveIntegerField(validators=[validate_price])
    deposit = models.PositiveIntegerField()
    agreement_length = models.PositiveIntegerField()
    finance_balance = models.PositiveIntegerField(validators=[validate_balance])
    monthly_payment = models.FloatField(validators=[validate_month_pay])
    interest_rate = models.FloatField(validators=[validate_interest])
    customer_informed = models.BooleanField()

forms.py

from django.forms import ModelForm
from .models import Customer
from django.core.exceptions import ValidationError

class CustomerForm(ModelForm):  # The form for dealers to enter their completed sales
    class Meta:
        model = Customer
        fields = ["first_name", "last_name", "address_line_1", "address_line_2", "town", "postcode", "dealers",
                  "manufacturers", "vehicle_model", "vehicle_reg", "vehicle_condition", "payment_method",
                  "list_price", "deposit", "agreement_length", "interest_rate", "customer_informed"]
        # Hide the balance and monthly payment fields as they are calculated in models.py

Upvotes: 0

Views: 628

Answers (3)

Christopher Davies
Christopher Davies

Reputation: 194

Something like this should work:

from django import forms
from .models import Customer
from django.core.exceptions import ValidationError


class CustomerForm(forms.ModelForm):
    def clean(self):
        super(CustomerForm, self).clean()

        if self.cleaned_data['payment_method'] != "Cash" and self.cleaned_data['list_price'] == self.cleaned_data['deposit']:
            raise forms.ValidationError({'payment_method': _('Payment method is not cash.')})

    class Meta:
        model = Customer
        fields = ["first_name", "last_name", "address_line_1", "address_line_2", "town", "postcode", "dealers",
              "manufacturers", "vehicle_model", "vehicle_reg", "vehicle_condition", "payment_method",
              "list_price", "deposit", "agreement_length", "interest_rate", "customer_informed"]

Upvotes: 0

Christopher Davies
Christopher Davies

Reputation: 194

The problem here is that Django passes the value of the field to a validator - so in this case, self is a string.

You should do this validation in the form instead, by implementing a clean_payment_method() method in your CustomerForm

Upvotes: 0

Daniel Roseman
Daniel Roseman

Reputation: 599550

You can't use an instance method as a validator. It isn't called as a bound method and is only passed the value to validate itself and not the instance, so you can't use it to compare against other values.

Rather than have these separate validator methods, you should have a single clean() method that does all the validation. You can pass a dictionary of field names to errors to raise multiple validation errors.

Upvotes: 0

Related Questions