original source : https://stripe.com/docs/connect/collect-then-transfer-guide

Collect payments from customers and pay them out to sellers or service providers.

이글에서는 이해를 돕기 위해 집 renter와 rentee를 연결해주는 플랫폼을 만든다고 가정했다.

You can see a complete onboarding flow in action in our sample end-to-end Express integration.

Prerequisites

  1. Register your platform.
  2. Activate your account.
  3. Fill out your platform profile.

1. set up stripe library    -server side

# Install through pip
pip install --upgrade stripe

2. create connected account

When a user (seller or service provider 개발자가 만든 플랫폼을 이용해 영업할사람) signs up on your platform, create a user Account (referred to as a connected account) so you can accept payments and move funds to their bank account. Connected accounts represent your user in Stripe’s API and help facilitate the collection of onboarding requirements so Stripe can verify the user’s identity. In our home-rental example, the connected account represents the homeowner.

connected account를 만들수 있게 아래와 같은 버튼을 만들어 준다.

image

클리하면 아래와 같은 페이지로 이동하게 된다. 

image

2-1 Add an authentication button    -client side

Add an OAuth link to your website so the user can sign up for your platform:(위위 그림같은 버튼에 아래링크 넣는다.)

https://connect.stripe.com/express/oauth/authorize?client_id=ca_32D88BD1qLklliziD7gYQvctJIhWBSQ7&state={STATE_VALUE}&suggested_capabilities[]=transfers&stripe_user[email]=user@example.com
  • client_id     – This can be found in your platform settings. If you’re testing your integration, enable View test data in the Dashboard. (개발자만의 id라고 생각할수 있다. stripe가 connected account를 만들때 개발자가 누군지 알아야 하므로)
  • state     – Use this argument to prevent CSRF attacks. It should be a unique, not guessable value that’s generated and saved on your server. Stripe passes it back to your redirect after the user finishes the onboarding flow. (개발자가 임의로 만들어 제공하는 csrf token이며 stripe account생성작업을 마치고 돌아오는 user인지를 확인할때 사용한다.)
  • suggested_capabilities     – Use this argument only if you want to customize the capabilities for certain subsets of your connected accounts. Capabilities define what a connected account is allowed to do. In our home-rental example, we want the ability to process credit card and ACH payments directly from within the platform, then transfer some percentage of the funds to the homeowner. For this, we want the homeowner to have the transfers capability so they can receive transfers after fulfilling their onboarding requirements.
  • stripe_user     – You can use this argument to prefill any fields you already have so the user won’t need to provide that information again. In our home-rental example, we collected the homeowner’s email address when they signed up so this piece of information can be passed in the OAuth link. (위 그림 form 입력란에 미리 얻은 정보로 pre fill 할수 있게 한다.)

2-2 Save the account ID     -SERVER-SIDE

In platform settings, specify where the user should be redirected to after they’ve completed the OAuth flow with redirect_uri.

image

For example, if your redirect URI is https://www.example.com/connect/oauth, Stripe might redirect to https://www.example.com/connect/oauth?state=s_987654321&code=ac_123456789.

stripe connected account 를 만드는 작업을 한후 되돌아 올때 state (개발자가 만들어서 보낸 csrf token이 되돌아온다.)

If there’s an error, the URI contains the  error  and  error_description  parameters instead.

Write a  GET  handler to extract the parameters from the URI, verify the state against the state you saved on your server, and send the token to Stripe’s API:

import stripe
import json

# Using Flask.
from flask import (
    Flask,
    render_template,
    request,
)

app = Flask(__name__, static_folder=".",
            static_url_path="", template_folder=".")

@app.route('/', methods=['GET'])
def get_example():
  # Display landing page.
  return render_template('index.html')

# Set your secret key. Remember to switch to your live secret key in production!
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = 'sk_test_4G17rywGpaql6aqpnHVjEdFc'

@app.route("/connect/oauth", methods=["GET"])
def handle_oauth_redirect():
  # Assert the state matches the state you provided in the OAuth link (optional).
  state = request.args.get("state")

  if not state_matches(state):
    return json.dumps({"error": "Incorrect state parameter: " + state}), 403

  # Send the authorization code to Stripe's API.
  code = request.args.get("code")
  try:
    response = stripe.OAuth.token(grant_type="authorization_code", code=code,)
  except stripe.oauth_error.OAuthError as e:
    return json.dumps({"error": "Invalid authorization code: " + code}), 400
  except Exception as e:
    return json.dumps({"error": "An unknown error occurred."}), 500

  connected_account_id = response["stripe_user_id"]
  save_account_id(connected_account_id)

  # Render some HTML or redirect to a different page.
  return json.dumps({"success": True}), 200

def state_matches(state_parameter):
  # Load the same state value that you randomly generated for your OAuth link.
  saved_state = "{{ STATE }}"

  return saved_state == state_parameter

def save_account_id(id):
  # Save the connected account ID from the response to your database.
  print("Connected account ID: ", id)

if __name__ == "__main__":
    app.run(port=4242)

response = stripe.OAuth.token(grant_type=“authorization_code”, code=code,) 

response[“stripe_user_id”]

위 코드와 살펴본다. 위 과정을 통해 stripe_user_id 를 얻을수 있고 나중을 위해 이를 database에 저장한다. stripe_user_id 는 acct_ 로 시작한다.  stripe_user_id 는 나중에 account로 자금을 이동시킬때 사용된다.

Make sure to save the stripe_user_id parameter that’s returned to your database. This is your connected account’s ID and begins with acct_. You will need it later to transfer funds to the account.

Step 2.3: Customize your signup form

By default, your Express signup form might look like this:

image

위와 같은 기본 폼을 변경하기 위해서는 platform settings 에서 아래와 같은 폼을 통해 한다.

image

3. accept a payment

Stripe Elements is a set of prebuilt UI components, like inputs and buttons, for building your checkout flow. If you’d rather not build your own payment form, consider Checkout, a Stripe-hosted page to accept payments for one-time purchases and subscriptions. (두가지 방법이 documentation 에 있다. 간단하게 stripe의 checkout를 이용하는 방법만을 여기서는 살펴본다. 다른 방법도 본래 doc에는 설명하고 있다. 아래와 같은 모양이 된다. 버튼을 클릭하면 stripe가 제공하는 page로 이동하게 된다.)

image

Step 3.1: Create a PaymentIntent   – SERVER-SIDE

Stripe uses a   PaymentIntent   object to represent your intent to collect payment from a customer, tracking charge attempts and payment state changes throughout the process.

Create a PaymentIntent on your server with an amount and currency. Always decide how much to charge on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices. (  PaymentIntent 생성시에 명확하게 금액과 currency를 명시해야 한다.)

# Set your secret key. Remember to switch to your live secret key in production!
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = 'sk_test_4G17rywGpaql6aqpnHVjEdFc'

payment_intent = stripe.PaymentIntent.create(
  payment_method_types=['card'],
  amount=1000,
  currency='usd',
  application_fee_amount=123,
  transfer_data={
    'destination': '{{CONNECTED_STRIPE_ACCOUNT_ID}}',
  }
)

In our home-rental example, we want to build an experience where customers pay for rentals by using our platform, and where we pay homeowners for renting to customers. To set this experience up:

  • Indicate the rental is a destination charge with transfer_data[destination].
  • Specify how much of the rental amount will go to the platform with application_fee_amount.

When a rental charge occurs, Stripe transfers the entire amount to the connected account’s pending balance (transfer_data[destination]). Afterward, Stripe transfers the fee amount (application_fee_amount) to the platform’s account, which is the share of the revenue for facilitating the rental. Then, Stripe deducts the Stripe fees from the platform’s fee amount. An illustration of this funds flow is below:

transfer_data[destination] 는 connected account를 말한다. 위의 2-2에서 말한 stripe_user_id를 기입한다.  destination 이라고는 하지만 실제 자금의 흐름은 connected account로 들어갔다가 개발자 account로 수수료가 오고 이 금액에서 다시 stripe가 본인들의 수수료를 가져간다.

image

included in the returned PaymentIntent is a   client secret  , which is used on the client side to securely complete the payment process instead of passing the entire PaymentIntent object. There are different approaches that you can use to pass the client secret to the client side.  

return 된 PaymentIntent 안에는  client secret 이 들어 있으며 이를 client side에서 사용 payment 과정을 마무리하게 된다. PaymentIntent_obj.client_secret 이와 같이 client_secret을 얻을수 있다.

single page application과 server side rendering을 이용하는 방법이 doc에는 나와 있는데 일단 server side rendering 방법을 사용하기로 정함

server side rendering

You can embed it in a data attribute or hidden HTML element and then extract it with JavaScript in order to use it to complete payment.

checkout.html

<input id="card-name" type="text">
<!-- placeholder for Elements -->
<div id="card-element"></div>
<button id="card-button" data-secret="{{ client_secret }}">
  Submit Payment
</button>

app.py

@app.route('/checkout')
def checkout():
  intent = # ... Fetch or create the PaymentIntent
  return render_template('checkout.html', client_secret=intent.client_secret)

Step 3.2: Collect card details    -CLIENT-SIDE

You’re ready to collect card information on the client with Stripe Elements. Elements is a set of prebuilt UI components for collecting and validating card number, ZIP code, and expiration date.

A Stripe Element contains an iframe that securely sends the payment information to Stripe over a HTTPS connection. The checkout page address must also start with https:// rather than http:// for your integration to work.

You can test your integration without using HTTPS. Enable it when you’re ready to accept live payments. (실제에서는 https에서만 stripe element가 작동하게 된다. 즉 사용자가 카드번호넣고 클릭하는 버튼은 https에서만 작동 단 test시에는 상관없음)

checkout.html

<head>
  <title>Checkout</title>
  https://js.stripe.com/v3/
</head>

js 화일을 다운받아서 사용하면 안된다. 링크를 이용해야 한다.

script.js

// Set your publishable key: remember to change this to your live publishable key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = Stripe('pk_test_내테스트키');
var elements = stripe.elements();

Add Elements to your payment page

payment button Element 는 payment form 안에 넣는다. Create empty DOM nodes (containers) with unique IDs in your payment form and then pass those IDs to Elements. js 에서 unique ID element를 찾아서 입력값에  접근 이를 stripe에 전송하게 된다.

checkout.html

<form id="payment-form">
  <div id="card-element">
    <!-- Elements will create input elements here -->
  </div>

  <!-- We'll put the error messages in this element -->
  <div id="card-errors" role="alert"></div>

  <button id="submit">Pay</button>
</form>

When the form above has loaded, create an instance of an Element and mount it to the Element container:      mount()를 통해 입력값에 접근

client.js

// Set up Stripe.js and Elements to use in checkout form
var style = {
  base: {
    color: "#32325d",
  }
};

var card = elements.create("card", { style: style });
card.mount("#card-element");

The  card  Element simplifies the form and minimizes the number of required fields by inserting a single, flexible input field that securely collects all necessary card and billing details. Otherwise, combine   cardNumber  ,   cardExpiry  , and   cardCvc   Elements for a flexible, multi-input card form.

Always collect a postal code to increase card acceptance rates and reduce fraud.

The single input card Element automatically collects and sends the customer’s postal code to Stripe. If you build your payment form with multi-input card Elements (cardNumber, cardExpiry, cardCvc), add a separate input field for the customer’s postal code.

간단하게 위와 같이 card만 생성하는 경우 자동으로 필요한 fields가 만들어지게 된다. 개발자가 각 항목을 정해서 만들수도 있다. card만 생성하는 경우 postal code를 입력하는 입력란이 자동으로 만들어 지는 데 개발자가 각 항목을 따로 만드는 경우 postal code입력란도 만들어야 한다.

For a full list of supported Element types, refer to our   Stripe.js reference   documentation. (다양한 입력 element를 제공하는데 doc을 확인하면 알수 있다.)

Elements validates user input as it is typed. To help your customers catch mistakes, listen to  change  events on the card Element and display any errors:

card.addEventListener('change', function(event) {
  var displayError = document.getElementById('card-errors');
  if (event.error) {
    displayError.textContent = event.error.message;
  } else {
    displayError.textContent = '';
  }
});

Postal code validation   depends on your customer’s billing country. Use our international test cards   to experiment with other postal code formats.

Step 3.3: Submit the payment to Stripe    -CLIENT-SIDE

Rather than sending the entire PaymentIntent object to the client, use its client secret from Step 3.1. This is different from your API keys that authenticate Stripe API requests.

The client secret should still be handled carefully because it can complete the charge. Do not log it, embed it in URLs, or expose it to anyone but the customer.

HTML + JS

To complete the payment when the user clicks, retrieve the client secret from the PaymentIntent you created in step 3.1 and call stripe.confirmCardPayment  with the client secret.

Pass additional billing details, such as the cardholder name and address, to the billing_details  hash. The card Element automatically sends the customer’s postal code information. However, combining cardNumber, cardCvc  , and   cardExpiry Elements requires you to pass the postal code to billing_details[address][postal_code].

client.js

var form = document.getElementById('payment-form');

form.addEventListener('submit', function(ev) {
  ev.preventDefault();
  stripe.confirmCardPayment(clientSecret, {
    payment_method: {
      card: card,
      billing_details: {
        name: 'Jenny Rosen'
      }
    }
  }).then(function(result) {
    if (result.error) {
      // Show error to your customer (e.g., insufficient funds)
      console.log(result.error.message);
    } else {
      // The payment has been processed!
      if (result.paymentIntent.status === 'succeeded') {
        // Show a success message to your customer
        // There's a risk of the customer closing the window before callback
        // execution. Set up a webhook or plugin to listen for the
        // payment_intent.succeeded event that handles any business critical
        // post-payment actions.
      }
    }
  });
});

stripe.confirmCardPayment may take several seconds to complete. During that time, disable your form from being resubmitted and show a waiting indicator like a spinner. If you receive an error, show it to the customer, re-enable the form, and hide the waiting indicator. 

stripe.confirmCardPayment 작업이 시간이 몇초 걸릴수 있으므로 기다리는 동안 payment클릭이 불가능하게 만들어서 사용자가 여러번 같은 payment를 하지 않게 한다

If the customer must authenticate the card, Stripe.js walks them through that process by showing them a modal. You can see an example of this modal experience by using the test card number   4000 0025 0000 3155   with any CVC, future expiration date, and postal code in the demo at the top of the page.

When the payment completes successfully, the value of the returned PaymentIntent’s   status   property is succeeded.   Check the status of a PaymentIntent in the Dashboard or by inspecting the status property on the object.   If the payment is not successful, inspect the returned error to determine the cause.

Step 3.4: Fulfillment     -SERVER-SIDE

이 단계에서는 payment가 완료된 상황에서 수행할 작업을 수행한다.

After the payment is completed, you’ll need to handle any fulfillment necessary on your end. A home-rental company that requires payment upfront, for instance, would connect the homeowner with the renter after a successful payment.

Do not rely on the redirect to the   success_url   param alone for fulfilling purchases as: (success_url parameters를 믿지 않는다.)

  • Malicious users could directly access the success_url without paying and gain access to your goods or services.
  • Customers may not always reach the success_url after a successful payment. It is possible they close their browser tab before the redirect occurs.

If you’re using Checkout, configure a   webhook endpoint  (for events from your account) in your dashboard.

server.py

import stripe
import json

# Using Flask.
from flask import (
    Flask,
    render_template,
    request,
    Response,
)

app = Flask(__name__, static_folder=".",
            static_url_path="", template_folder=".")

# Set your secret key. Remember to switch to your live secret key in production!
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = 'sk_test_4G17rywGpaql6aqpnHVjEdFc'

# Uncomment and replace with a real secret. You can find your endpoint's
# secret in your webhook settings.
# webhook_secret = 'whsec_...'

@app.route("/webhook", methods=["POST"])
def webhook_received():
  request_data = json.loads(request.data)
  signature = request.headers.get("stripe-signature")

  # Verify webhook signature and extract the event.
  # See https://stripe.com/docs/webhooks/signatures for more information.
  try:
    event = stripe.Webhook.construct_event(
        payload=request.data, sig_header=signature, secret=webhook_secret
    )
  except ValueError as e:
    # Invalid payload.
    return Response(status=400)
  except stripe.error.SignatureVerificationError as e:
    # Invalid Signature.
    return Response(status=400)

  if event["type"] == "payment_intent.succeeded":
    payment_intent = event["data"]["object"]
    handle_successful_payment_intent(payment_intent)

  return json.dumps({"success": True}), 200

def handle_successful_payment_intent(payment_intent):
  # Fulfill the purchase.
  print('PaymentIntent: ' + str(payment_intent))

if __name__ == "__main__":
  app.run(port=4242)

Learn more in our   fulfillment guide for payments.

Testing webhooks locally

Testing webhooks locally is easy with the Stripe CLI.

  1. First, install the Stripe CLI on your machine if you haven’t already.
  2. Then, to log in run stripe login in the command line, and follow the instructions.
  3. Finally, to allow your local host to receive a simulated event on your connected account run stripe listen --forward-to localhost:{PORT}/webhook in one terminal window, and run stripe trigger payment_intent.succeeded (or trigger any other supported event) in another.


Step 3.5: Disputes

As the settlement merchant on charges, your platform is responsible for disputes. Make sure you understand the best practices for responding to disputes.

4 Complete and customize your integration

You now have a working integration. From your account dashboard, you can view an account and its balance.

Payouts

By default, any charge that you transfer to a connected account accumulates in the connected account’s Stripe balance and is paid out on a daily rolling basis. But, you can change the payout schedule if needed.

Testing

The Connect-specific testing resource provides tokens to help simulate flows for accounts and onboarding, payouts, and top-ups. To test your payments and disputes flow there are a number of test cards available to simulate payment outcomes.

Other resources

You have completed the steps required to build a working Connect integration. Based on your business needs, you can also do the following:

Manage connected accounts

original source : https://stackoverflow.com/a/60215

python은 switch구문이 없는데 이에 대한 우회방법이다.

I’ve always liked doing it this way

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}[value](x)

From here

original source : https://georgexyz.com/django-model-form-validation.html

django modelform을 이해하기 위해서는 model, form 각각 따로 이해하고 나서 종합적으로 이해한다. 이를 이해하고 나서는 formset에 대해 이해하고 modelformset에 대해 이해할수 있다.

Django model and form validation is a somewhat complicated and confusing topic in Django app development. A developer needs to understand several basic concepts such as model, model field, form, model form, etc. to have a good understanding of validation. Most Django books and online tutorials do not have a good discussion on validation.

Django official documentation has detailed descriptions on validation. However, the contents are dispersed on several places. This post describes the materials I have read on this topic.

Validator Function

The validator official documentation page is a good starting point to study model and form validation.

The validator function is a callable that takes a value and raises a ValidationError if not validated. Here is an example from the page:

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

def validate_even(value):
    if value % 2 != 0:
        raise ValidationError(
            _('%(value)s is not an even number'),
            params={'value': value},
        )

from django.db import models

class MyModel(models.Model):
    even_field = models.IntegerField(validators=[validate_even])

The subsection how validators are run on the validator page has three links.

  • The second link validating objects is about model validation. The link points to a subsection on the model instance reference page.
  • The first link form validation points to a separate page about form validation.
  • The third link goes to the ModelForm page.

Model Validation

A model’s full_clean() method performs model validation. The method calls three other methods:

  • clean_fields() method
  • clean() method, as a whole
  • validate_unique() method

The model save() method does NOT call full_clean() method automatically. A programmer needs to call it manually to trigger model validation like the below code.

try:
    article.full_clean()
except ValidationError as e:
    ...
    # handle the error

A stack overflow answer shows a typical pattern to conduct custom model validation. The model class overrides the clean() method to provide custom model validation and the save() method to call full_clean method. The example code is shown below:

class BaseModel(models.Model):
    # model fields 

    def clean(self, *args, **kwargs):
        # add custom validation here
        super(BaseModel, self).clean(*args, **kwargs)

    def save(self, *args, **kwargs):
        self.full_clean()
        super(BaseModel, self).save(*args, **kwargs)

Another stack overflow answer shows how to use custom model validation or simply use model field’s built-in validator.

Model field’s validation will not kick in unless the full_clean() method is explicitly called. For example, the p2.save() below would not raise an exception when called.

class PageModel(models.Model):
    name = models.CharField(max_length=50)
    slug = models.SlugField(max_length=50)

>>> from page.models import PageModel #page app name
>>> p1 = PageModel(name='Page1', slug='page1')
>>> p1.save()
>>> p2 = PageModel(name='Page2', slug='page2#$%')
>>> p2.save()        # no error
>>> p2.full_clean()  # raise exception

Checking clean_fields() method source code, it has the following lines. The f.clean(...) method calls validation method on a model field.

try:
    setattr(self, f.attname, f.clean(raw_value, self))
except ValidationError as e:
    errors[f.name] = e.error_list

Form Validation

While model validation is a subsection on a Django documentation page, the form validation is on a separate page. Form validation is normally executed when the is_valid() method is called on a form. A programmer can also trigger form validation by accessing errors attribute or call full_clean() method of a form.

Form validation has a series of steps:

  • to_python() method on a field, correct data type
  • validation() method on a field
  • run_validators() method on a field
  • clean() method on a Field subclass, which calls above three methods and returns the clean data
  • clean_<fieldname>() method has access to cleaned_data Python object and returns value that replaces data in cleaned_data
  • clean() method of form, for multiple fields

The same documetation page has several nice examples, which are based on the model shown below:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    message = forms.CharField()
    sender = forms.EmailField()
    recipients = MultiEmailField()
    cc_myself = forms.BooleanField(required=False)

The same page points out that “there are special considerations when overriding the clean() method of a ModelForm subclass.”

Chapter 7 of Andrew Pinkham’s Django Unleashed book, titled allowing user input with forms, has good example on how to override clean_<fieldname> method. The discussion on model validation and form validation in this chapter is better than other Django books I have read.

ModelForm Validation

The form validation steps described in the previous section also apply to ModelForm validation. In addition to that, Model.full_clean() method is triggered after the form’s clean() method is called. So, model validation methods are not triggered by model save() method, but model validation methods are triggered by ModelForm validation. This stack overflow question discusses this exact issue. The accepted answer also has code example on model validation.

Error messages at the form field level take precedence over the error messages defined at the model field level.

original source : http://www.deekras.com/django-validations-during-form-processing.html

form validation에 대한 documentation이 좀 이해하기 힘든데 이 블로그는 간단명료학 잘 정리했다.

This post is mostly based on the Django Docs on Form and Field Validation. I reformatted the information in a way that feels easier to use.

There are 3 types of cleaning methods that are run during form processing. These are normally executed when you call the is_valid() method on a form.  (is_valid() runs validation routines for all fields on the form. When this method is called, if all fields contain valid data, it will:

  • return True
  • place the form’s data in its cleaned_data attribute.)

In general, any cleaning method can raise a ValidationError if there is a problem with the data it is processing; it passes the relevant information to the ValidationError constructor.

Steps of validation

The methods below are run in the order given, one field at a time. That is, for each field in the form (in the order they are declared in the form definition). Then the form.clean(), or its override, is executed regardless if the previous methods have raised errors. If the Field.clean() method raises a ValidationError, its field-specific cleaning methods are not called. However, the cleaning methods for all remaining fields are still executed.

Normally, the clean() method will be run and it will take care of the first three validations (to_python(), validate(), run_validators()). But you can customize any of them, and when the clean() method is executed, it will run the customized method.

1. to_python() method on a Field

  • WHAT IT DOES: It coerces the value to correct datatype and raises ValidationError if that is not possible. This method accepts the raw value from the widget and returns the converted value.
  • EXAMPLE: a FloatField will turn the data into a Python float or raise a ValidationError.
  • HANDLES ERRORS: raises ValidationError on any error
  • RETURNS: returns the converted value.

2. validate() method on a Field

  • WHAT IT DOES: handles field-specific validation that is not suitable for a validator. It takes a value that has been coerced to correct datatype and raises ValidationError on any error.
  • HANDLES ERRORS: raises ValidationError on any error
  • RETURNS: This method does not return anything and shouldn’t alter the value.
  • NOTES: You should override it to handle validation logic that you can’t or don’t want to put in a validator.

3. run_validators() method on a Field

  • WHAT IT DOES: runs all of the field’s validators
  • HANDLES ERRORS: aggregates all the errors into a single ValidationError.
  • RETURNS:
  • NOTES: You shouldn’t need to override this method.

4. The clean() method on a Field subclass.

  • WHAT IT DOES: This is responsible for running to_python, validate and run_validators in the correct order and propagating their errors.
  • HANDLES ERRORS: If, at any time, any of the methods raise ValidationError, the validation stops and that error is raised.
  • RETURNS: This method returns the clean data, which is then inserted into the cleaned_data dictionary of the form.

5. The clean_<fieldname>() method in a form subclass – where <fieldname> is replaced with the name of the form field attribute.

  • WHAT IT DOES: This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is.
  • HOW TO USE: This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).
  • HANDLES ERRORS:
  • RETURNS: the cleaned value obtained from cleaned_data – regardless of whether it changed anything or not.

6. The Form subclass’s clean() method.

NOTES: Also note that there are special considerations when overriding the clean() method of a ModelForm subclass. (see the ModelForm documentation for more information)

  • WHAT IT DOES: This method can perform any validation that requires access to multiple fields from the form at once.
  • EXAMPLE: Checks that if field A is supplied, field B must contain a valid email address and the like.
  • HOW TO USE: Since the field validation methods have been run by the time clean() is called, you also have access to the form’s errors attribute which contains all the errors raised by cleaning of individual fields.
  • HANDLES ERRORS: Note that any errors raised by your Form.clean() override will not be associated with any field in particular. They go into a special “field” (called __all__), which you can access via the non_field_errors() method if you need to. If you want to attach errors to a specific field in the form, you need to call add_error().
  • RETURNS: This method can return a completely different dictionary if it wishes, which will be used as the cleaned_data.

Raising ValidationError examples:

if not flag:
    raise ValidationError('Please submit flag') –  a simple example
    raise ValidationError(_('text: %(flag)s'), 
                            code='no flag', 
                            params={'flag': '42'},)

multiple errors can be created as a list

    raise ValidationError([
        ValidationError(_('Error 1'), code='error1'),
        ValidationError(_('Error 2'), code='error2'),
    ])

Writing Validators

There are many builtin validators that match the field type (ex: EmailValidator for EmailField). Those validators can be customized too. (ex: class EmailValidator([message=None, code=None, whitelist=None])

Here’s a sample custom validator:

from django.core.exceptions import ValidationError

def validate_even(value):
   if value % 2 != 0:
      raise ValidationError('%s is not an even number' % value)

Then, this validator can be used for any fields when setting up the models:

class MyModel(models.Model):
   even_field = models.IntegerField(validators=[validate_even])

It can also be used for forms:

class MyForm(forms.Form):
   even_field = forms.IntegerField(validators=[validate_even])

Validators will not be run automatically when you save a model, but if you are using a ModelForm, it will run your validators on any fields that are included in your form.

>>> from django.forms import ModelForm
>>> from myapp.models import Article

# Create the form class.
>>> class ArticleForm(ModelForm):
...     class Meta:
...         model = Article
...         fields = ['pub_date', 'headline', 'content', 'reporter']

# Creating a form to add an article.
>>> form = ArticleForm()

# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)
image
  • ForeignKey is represented by django.forms.ModelChoiceField, which is a ChoiceField whose choices are a model QuerySet.
  • ManyToManyField is represented by django.forms.ModelMultipleChoiceField, which is a MultipleChoiceField whose choices are a model QuerySet.

  • model 에서  blank=True 설정 되어있으면 modelform에서는 required 값이 False되고 그외에는 required=True.
  • form 필드 label 는  model field의 verbose_name 의 값을 이용하되 첫글자는 대문자로 한다.
  • form 필드의 help_text 는 model의 help_text을 이용한다.
  • model field 가 만약 choices set 이면  form field의 widget은 Select가 된다.보통은 blank 가 선택되어있는데 blank=False가 설정되어있다면 blank는 없어진다. 만약 model field 가 default값이 설정되어 있다면 그것이 initially selected 가 된다.

modelform은 기본적으로 model의 field를 따라서 만들어지게 되는데 이를 override하고 싶다면 Overriding the default fields 를 따른다.

Validation on a ModelForm

There are two main steps involved in validating a ModelForm:

  1. Validating the form
  2. Validating the model instance

is_valid()를 호출하거나, errors에 접근하거나 full_clean()를 직접호출하면 validation 과정이 시작된다.

form validation을 원래대로 수행하고 model validation을 수행한다.

Overriding the clean() method

You can override the clean() method on a model form to provide additional validation in the same way you can on a normal form.

image

Interaction with model validation

As part of the validation process, ModelForm will call the clean() method of each field on your model that has a corresponding field on your form. If you have excluded any model fields, validation will not be run on those fields. 

model validation에서 full_clean()을 호출하면 차례로 clean_fields() , clean(), validate_unique()가 호출되면서 validatoin을 수행하게 된다. Model.full_clean(exclude=None, validate_unique=True) 에서 특정 fields를 validation에서 제외할수 있다. 또 Model.clean_fields(exclude=None)에서도 선택적으로 제외할수 있다. 이렇게 제외된 것은 modelform validation에서도 제외된다는 이야기 이다. 

See the form validation documentation for more on how field cleaning and validation work.

The model’s clean() method will be called before any uniqueness checks are made. See Validating objects for more information on the model’s clean() hook.

Considerations regarding model’s error_messages

form field level에서 정의 된 Error messages 나  form Meta level 에서 정의된 Error messages가 항상 model field level에서 정의된 것보다 우선한다. 다만 form level에서 error가 발생하지 않고 model validation에서 error가 발생한경우만  model field level의 error messages를 사용한다.

NON_FIELD_ERRORS 에 model validation에서 발생한 error_messages 를 dictionary 형태로 Meta class에 정의해 넣을수 있다.

from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm

class ArticleForm(ModelForm):
    class Meta:
        error_messages = {
            NON_FIELD_ERRORS: {
                'unique_together': "%(model_name)s's %(field_labels)s are not unique.",
            }
        }

The save()method

ModelForm 는 save() method를 가지며 이미 존재하는 model instance를 instance키워드 argument로 전달 받으면 update를 수행하고 아니면 create을 수행한다. 

>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm

# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)

# Save a new Article object from the form's data.
>>> new_article = f.save()

# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()

validation을 수행하지 않고 바로 save()를 수행하는 경우 form.errors를 save()과정에서 확인 하게 되는데 확인하면 바로 validation이 수행된다. 그래서 문제 있는 경우 form.errors는 True값으로 표현되며 ValueError가 raise된다.

꼭 입력되지 않아도 되는 model field에 어떤 값이 주어지지 않으면 model field의 default값이 자동으로 채워지게 된다. 단 이 작동방식은   CheckboxInput, CheckboxSelectMultiple, or SelectMultiple (or any custom widget whose value_omitted_from_data() method always returns False) 에는 적용되지 않는다. 

This save() method accepts an optional commit keyword argument, which accepts either True or False. If you call save() with commit=False, then it will return an object that hasn’t yet been saved to the database. 여기서 database에 저장되지 않은 상태에서 개발자가 추가로 data를 임의로 넣을수있다. 

commit=False를 사용한 경우 model에 many-to-many relation field가 있다면 Django 가  save_m2m() method 를 ModelForm class에 제공하는데  아래와 같이 instance 를 가진  form을 먼저 저장하고  save_m2m() 를 호출해주어야 한다.

# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)

# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)

# Modify the author in some way.
>>> new_author.some_field = 'some_value'

# Save the new instance.
>>> new_author.save()

# Now, save the many-to-many data for the form.
>>> f.save_m2m()

Calling save_m2m() is only required if you use save(commit=False). When you use a save() on a form, all data – including many-to-many data – is saved without the need for any additional method calls. For example:

# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)

# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()


is_multipart() method is used to determine whether a form requires multipart file upload (and hence whether request.FILES must be passed to the form), etc. See Binding uploaded files to a form for more information.

Selecting the fields to use

Meta에 fields를 설정해서 어떤 fields가 form에 포함될지를 결정한다.

'__all__' 모든 fields사용을 나타낸다.

from django.forms import ModelForm

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = '__all__'

exclude 는 몇몇을 제외한다.

class PartialAuthorForm(ModelForm):
    class Meta:
        model = Author
        exclude = ['title']

model 에 정의되어 있는 field의 순대로 form에 위치하게 된다. 단 ManyToManyField 는 마지막에 위치한다.

model field에 editable=False 설정했으며 form에는 포함되지 않는다.

Note

form Meta fields에 포함되어있지 않거나 exclude를 통해 제외된 field를 model단계에서저장할때 값을 요구하는 경우가 있다. 이런경우는 개발자가 손수 추가로 해당 data를 넣어 주어야 한다. 

author = Author(title='Mr')
form = PartialAuthorForm(request.POST, instance=author)
form.save()

Alternatively, you can use save(commit=False) and manually set any extra required fields:

form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = 'Mr'
author.save()

Overriding the default fields

기본적으로 model에 정의되어있는 field에 따라 modelform field도 결정된다. 다만 Meta에 widgets이 설정하면 설정에 따라 바뀌게 된다. 

from django.forms import ModelForm, Textarea
from myapp.models import Author

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        widgets = {
            'name': Textarea(attrs={'cols': 80, 'rows': 20}),
        }

widgets dictionary 에는 widget instances (e.g., Textarea(...)) 나 classes (e.g., Textarea)를 넣을수 있다.  non-empty choices attribute 성질을 가지는 field의 경우에는 widgets dictionary 를 이용해서 바꿀수 없고 Meta밖에 명시적으로 field성질을 명시 해줘야 한다. 

widgets을 이용해서 그 형태를 바꾼것과 같은 방법으로 labels, help_texts, error_messages 도 Meta class에서 바꿀수 있다. 

from django.utils.translation import gettext_lazy as _

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        labels = {
            'name': _('Writer'),
        }
        help_texts = {
            'name': _('Some useful help text.'),
        }
        error_messages = {
            'name': {
                'max_length': _("This writer's name is too long."),
            },
        }

field_classes 를 통해 custom field를 사용할수 있다.

예시)

from django.forms import ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
        field_classes = {
            'slug': MySlugFormField,
        }

field를 좀더 customize해서 사용하고싶은 경우 예를 들어  its type, validators, required 등을 바꾸고 싶은 경우 일반 Form에서 field를 작성하듯 명시적으로 field를 작성해 주면 된다. 

from django.forms import CharField, ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
    slug = CharField(validators=[validate_slug])

    class Meta:
        model = Article
        fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']

Note

ModelForm is a regular Form which can automatically generate certain fields. The fields that are automatically generated depend on the content of the Meta class and on which fields have already been defined declaratively. Basically, ModelForm will only generate fields that are missing from the form, or in other words, fields that weren’t defined declaratively.

아래와 같이 이해했다.

ModelForm 은 Meta class 에 정의된 fields 내용과 ModelForm에 명시적으로 표기된 fields 내용을 기반으로  form을 만든다. Meta fields에 기록된 fields중에 ModelForm에 명시적으로 표기된것이 아닌것은 model을 기반으로 자동으로 만든다.

field에 대한 속성이 Meta에도 있고 ModelForm에도 명시적으로 속성이 정의된경우(일반 Form에서 field 속성 정의하듯) 명시적으로 정의된것이 우선한다. 또 명시적으로 속성을 정의한 경우 model에 정의된 속성이 무시되므로 다시 정의해주어야 한다. 예를 들어 max_length가 model에 정의되어 있고 해당 field를 명시적으로 override한 경우 ModelForm에서도 정의해주어야 한다.

class Article(models.Model):
    headline = models.CharField(
        max_length=200,
        null=True,
        blank=True,
        help_text='Use puns liberally',
    )
    content = models.TextField()
class ArticleForm(ModelForm):
    headline = MyFormField(
        max_length=200,
        required=False,
        help_text='Use puns liberally',
    )

    class Meta:
        model = Article
        fields = ['headline', 'content']

ModeForm에서 Meta class를 이용하건 Form에서 명시적으로 override하건 두경우다 field가 기존 data를 받아들일수 있는 형식이어야 한다. 아닌 경우  

ValueError 가 raise된다.

Enabling localization of fields

By default, the fields in a ModelForm will not localize their data. To enable localization for fields, you can use the localized_fields attribute on the Meta class.

>>> from django.forms import ModelForm
>>> from myapp.models import Author
>>> class AuthorForm(ModelForm):
...     class Meta:
...         model = Author
...         localized_fields = ('birth_date',)

If localized_fields is set to the special value '__all__', all fields will be localized.

Form inheritance

extra fields 또는 extra methods 를 추가 하고자 하는 경우 inheritance 하는 것도 좋은 방법이다.

>>> class EnhancedArticleForm(ArticleForm):
...     def clean_pub_date(self):

위의 경우 pub_date field 에 대한 extra validation 를 추가하는 것을 보여준다. 

ModelForm을 inherit하는 경우도 있지만 아래와 같이 ModelForm안의 Meta class를 inherit할수도 있다.  

>>> class RestrictedArticleForm(EnhancedArticleForm):
...     class Meta(ArticleForm.Meta):
...         exclude = ('body',)

This adds the extra method from the EnhancedArticleForm and modifies the original ArticleForm.Meta to remove one field.

There are a couple of things to note, however.

  • Normal Python name resolution rules apply. If you have multiple base classes that declare a Meta inner class, only the first one will be used. This means the child’s Meta, if it exists, otherwise the Meta of the first parent, etc.
  • Form 이나 ModelForm 를 inherit할수 있고 둘가지를 동시에 할수도 있는데 둘다 하는 경우 ModelForm MRO에서 맨우선할수 있도록 inherit class을 기입할때 가장 먼저 한다. 
  • parent class에 명시적으로 정의된 field의 경우 child class에서 field이름= None을 통해 하부 클래스에서 없앨수 있다. ModelForm metaclass 를 통해 생성된 fields는 이 방법으로 제거할수 없다.그런경우 이방법 Selecting the fields to use. 을 사용한다.

Providing initial values

일반 form과 마찬가지로 form 생성될때 initial parameter 를 이용  Initial values를 가지게 할수 있다. 이때 model field, form field 에서 정의된 initial값은 무시된다.  

>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article)
>>> form['headline'].value()
'Initial headline'

ModelForm factory function

>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=("author", "title"))

어떤 fields들이 들어갈지 위와 같이 지정할수 있다. (fields 와 exclude를 사용할수 있다)

>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm,
...                          widgets={"title": Textarea()})

아래와 같이 localization 을 설정할수 있다.

>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",))

Model formsets

class models.BaseModelFormSet

>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))

fields 와 exclude를 이용 어떤 form에 어떤 fields가 나오게 할지 지정할수 있다.

>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))

>>> formset = AuthorFormSet()
>>> print(formset)
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS"><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS">
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100"></td></tr>
<tr><th><label for="id_form-0-title">Title:</label></th><td><select name="form-0-title" id="id_form-0-title">
<option value="" selected>---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select><input type="hidden" name="form-0-id" id="id_form-0-id"></td></tr>

Note

When using multi-table inheritance, forms generated by a formset factory will contain a parent link field (by default <parent_model_name>_ptr) instead of an id field. (잘 이해가 되지 않음)

Changing the queryset

기본적으로 Modelformset의 경우 model의 모든 obj들 가지는 queryset을 사용 form들을 만들게 된다.  이를 바꾸기 위해 queryset argument를 아래와 같이 사용한다.

>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))

또 다른 방법으로는 queryset property값을 아래와 같이 지정해 주는 것이다.

from django.forms import BaseModelFormSet
from myapp.models import Author

class BaseAuthorFormSet(BaseModelFormSet):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.queryset = Author.objects.filter(name__startswith='O')

Then, pass your BaseAuthorFormSet class to the factory function:

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title'), formset=BaseAuthorFormSet)

기본 instance를 이용하지 않으려는 경우 아래 방법을 사용한다.

>>> AuthorFormSet(queryset=Author.objects.none())

Changing the form

modelformset_factory 는 기본적으로 modelform_factory()를 이용해 form을 만든다.그러나 아래와 같이 form을 만들어 사용할수도 있다. 아래의 경우 validation과정을 바꾼경우 이다. 

class AuthorForm(forms.ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title')

    def clean_name(self):
        # custom validation for the name field
        ...

아래와 model form 을 만들어서 factory에 전달한다.

AuthorFormSet = modelformset_factory(Author, form=AuthorForm)

꼭 위에와 같이 model form을 만들어서 사용할 필요는 없다. 

 modelformset_factory function의 몇몇 arguments 설정으로 통해 form을 조정할수 있기 때문이다. modelformset_factory 설정 arguments들은  modelform_factory를 거쳐 만들어질 form을 변경하게 된다. 




modelformset_factory function의 arguments 설정으로 통해 form을 조정하는 방법


Specifying widgets to use in the form with widgets

Using the widgets parameter. This works the same way as the widgets dictionary on the inner Meta class of a ModelForm works:

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title'),
...     widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})})



Enabling localization for fields with localized_fields

Using the localized_fields parameter, you can enable localization for fields in the form.

>>> AuthorFormSet = modelformset_factory(
...     Author, fields=('name', 'title', 'birth_date'),
...     localized_fields=('birth_date',))

If localized_fields is set to the special value '__all__', all fields will be localized.

Providing initial values

regular formsets과 마찬가지로 formset class를 만들때 사용하는  modelformset_factory()에 initial parameter를 이용해서 initial data 를 설정할수 있다. 다만 initial values 들은 이미 존재하는 model obj에 적용되는 것이 아니라 extra로 만들어지는 form에만 적용된다. extra에 initialized된 값들이 변경되지 않으면 사용자가 변경하지 않은 것으로 알고 해당 form은 validation, save를 거치지 않게 된다. 

Saving objects in the formset

ModelForm과 마찬가지로 save()를 통해 저장한다. save()호출하면 저절로 validation작업을수행하게 된다.

# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)

# Assuming all is valid, save the data.
>>> instances = formset.save()

 save() 는 save작업을 하며 저장된 instances를 리턴한다.  변화되지 않는 form은 validation, save작업도 하지 않으며 결과 instances에도 포함되지 않는다.

model에는 존재하나 ModelForm에는 fields, exclude 등의 방법으로 포함되지 않은 경우 값은 단순히 save()작업으로 값이 입력되지는 않는다. 입력이 필요하다면 개발자가 직접 입력하는 작업을 수행해야 한다. 

commit=False 를 이용 unsaved model instances 를 얻은 다음 data를 추가하는 작업을 아래와 같이 할수 있다.

# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
...     # do something with instance
...     instance.save()

If your formset contains a ManyToManyField, you’ll also need to call formset.save_m2m() to ensure the many-to-many relationships are saved properly.

save() 호출후에 formset obj는 아래 속성을 추가로 갖게 된다. 

  • models.BaseModelFormSet.changed_objects
  • models.BaseModelFormSet.deleted_objects
  • models.BaseModelFormSet.new_objects

Limiting the number of editable objects 

일반 formsets과 마찬가지로 max_num 과 extra parameters 를 사용할수 있다. 이를 통해 modelformset_factory()를 통해 extra forms 갯수를 조정할수 있다.

max_num 는 기존 model obj를 위한 form을 막지는 않는다.

>>> Author.objects.order_by('name')
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']

extra=0 라 하더라도 form이 생성되는 것을 막는 것은 아니다. 예를 들어 JavaScript 를 이용 새 form을 추가 할수 있다. 또한 추가된 form의 POST data를 막는 것도 아니다. 즉 extra=0라고 하더라도 create작업을 수행할수도 있다. 다만 formset이 extra form을 생성해 주는 것이 아닌 것이다.

If the value of max_num is greater than the number of existing related objects, up to extra additional blank forms will be added to the formset, so long as the total number of forms does not exceed max_num:

>>> AuthorFormSet = modelformset_factory(Author, fields=('name',), max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by('name'))
>>> for form in formset:
...     print(form.as_table())
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100"><input type="hidden" name="form-3-id" id="id_form-3-id"></td></tr>

A max_num value of None (the default) puts a high limit on the number of forms displayed (1000). In practice this is equivalent to no limit.

Using a model formset in a view

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == 'POST':
        formset = AuthorFormSet(request.POST, request.FILES)
        if formset.is_valid():
            formset.save()
            # do something.
    else:
        formset = AuthorFormSet()
    return render(request, 'manage_authors.html', {'formset': formset})

As you can see, the view logic of a model formset isn’t drastically different than that of a “normal” formset. The only difference is that we call formset.save() to save the data into the database. (This was described above, in Saving objects in the formset.)

Overriding clean() on a ModelFormSet

Just like with ModelForms, by default the clean() method of a ModelFormSet will validate that none of the items in the formset violate the unique constraints on your model (either unique, unique_together or unique_for_date|month|year). If you want to override the clean() method on a ModelFormSet and maintain this validation, you must call the parent class’s clean method:

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super().clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...

Also note that by the time you reach this step, individual model instances have already been created for each Form. Modifying a value in form.cleaned_data is not sufficient to affect the saved value. If you wish to modify a value in ModelFormSet.clean() you must modify form.instance:

from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super().clean()

        for form in self.forms:
            name = form.cleaned_data['name'].upper()
            form.cleaned_data['name'] = name
            # update the instance value.
            form.instance.name = name

Using a custom queryset

As stated earlier, you can override the default queryset used by the model formset:

from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
    if request.method == "POST":
        formset = AuthorFormSet(
            request.POST, request.FILES,
            queryset=Author.objects.filter(name__startswith='O'),
        )
        if formset.is_valid():
            formset.save()
            # Do something.
    else:
        formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
    return render(request, 'manage_authors.html', {'formset': formset})

Note that we pass the queryset argument in both the POST and GET cases in this example.

Using the formset in the template

formset을 template에 render하는 방법3가지 

1.

<form method="post">
    {{ formset }}
</form>

2.

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form }}
    {% endfor %}
</form>

When you manually render the forms yourself, be sure to render the management form as shown above. See the management form documentation.

3.

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {% for field in form %}
            {{ field.label_tag }} {{ field }}
        {% endfor %}
    {% endfor %}
</form>

위 방법은 form.id를 수동으로 만들지 않는 점 유의

아래방법은 form.id가 있어야 된다. 

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.id }}
        <ul>
            <li>{{ form.name }}</li>
            <li>{{ form.age }}</li>
        </ul>
    {% endfor %}
</form>

need to explicitly render {{ form.id }}

original source : http://www.deekras.com/django-validations-during-form-processing.html

There are 3 types of cleaning methods that are run during form processing. These are normally executed when you call the is_valid() method on a form.  (is_valid() runs validation routines for all fields on the form. When this method is called, if all fields contain valid data, it will:

  • return True
  • place the form’s data in its cleaned_data attribute.)

In general, any cleaning method can raise a ValidationError if there is a problem with the data it is processing; it passes the relevant information to the ValidationError constructor.

Steps of validation

The methods below are run in the order given, one field at a time. That is, for each field in the form (in the order they are declared in the form definition). Then the form.clean(), or its override, is executed regardless if the previous methods have raised errors. If the Field.clean() method raises a ValidationError, its field-specific cleaning methods are not called. However, the cleaning methods for all remaining fields are still executed.

Normally, the clean() method will be run and it will take care of the first three validations (to_python(), validate(), run_validators()). But you can customize any of them, and when the clean() method is executed, it will run the customized method.

1. to_python() method on a Field

  • WHAT IT DOES: It coerces the value to correct datatype and raises ValidationError if that is not possible. This method accepts the raw value from the widget and returns the converted value.
  • EXAMPLE: a FloatField will turn the data into a Python float or raise a ValidationError.
  • HANDLES ERRORS: raises ValidationError on any error
  • RETURNS: returns the converted value.

2. validate() method on a Field

  • WHAT IT DOES: handles field-specific validation that is not suitable for a validator. It takes a value that has been coerced to correct datatype and raises ValidationError on any error.
  • HANDLES ERRORS: raises ValidationError on any error
  • RETURNS: This method does not return anything and shouldn’t alter the value.
  • NOTES: You should override it to handle validation logic that you can’t or don’t want to put in a validator.

3. run_validators() method on a Field

  • WHAT IT DOES: runs all of the field’s validators
  • HANDLES ERRORS: aggregates all the errors into a single ValidationError.
  • RETURNS:
  • NOTES: You shouldn’t need to override this method.

4. The clean() method on a Field subclass.

  • WHAT IT DOES: This is responsible for running to_python, validate and run_validators in the correct order and propagating their errors.
  • HANDLES ERRORS: If, at any time, any of the methods raise ValidationError, the validation stops and that error is raised.
  • RETURNS: This method returns the clean data, which is then inserted into the cleaned_data dictionary of the form.

5. The clean_<fieldname>() method in a form subclass – where <fieldname> is replaced with the name of the form field attribute.

  • WHAT IT DOES: This method does any cleaning that is specific to that particular attribute, unrelated to the type of field that it is.
  • HOW TO USE: This method is not passed any parameters. You will need to look up the value of the field in self.cleaned_data and remember that it will be a Python object at this point, not the original string submitted in the form (it will be in cleaned_data because the general field clean() method, above, has already cleaned the data once).
  • HANDLES ERRORS:
  • RETURNS: the cleaned value obtained from cleaned_data – regardless of whether it changed anything or not.

6. The Form subclass’s clean() method.

  • NOTES: Also note that there are special considerations when overriding the clean() method of a ModelForm subclass. (see the ModelForm documentation for more information)
  • WHAT IT DOES: This method can perform any validation that requires access to multiple fields from the form at once.
  • EXAMPLE: Checks that if field A is supplied, field B must contain a valid email address and the like.
  • HOW TO USE: Since the field validation methods have been run by the time clean() is called, you also have access to the form’s errors attribute which contains all the errors raised by cleaning of individual fields.
  • HANDLES ERRORS: Note that any errors raised by your Form.clean() override will not be associated with any field in particular. They go into a special “field” (called __all__), which you can access via the non_field_errors() method if you need to. If you want to attach errors to a specific field in the form, you need to call add_error().
  • RETURNS: This method can return a completely different dictionary if it wishes, which will be used as the cleaned_data.

Raising ValidationError examples:

if not flag:
    raise ValidationError('Please submit flag') –  a simple example
    raise ValidationError(_('text: %(flag)s'), 
                            code='no flag', 
                            params={'flag': '42'},)

multiple errors can be created as a list

    raise ValidationError([
        ValidationError(_('Error 1'), code='error1'),
        ValidationError(_('Error 2'), code='error2'),
    ])

Writing Validators

There are many builtin validators that match the field type (ex: EmailValidator for EmailField). Those validators can be customized too. (ex: class EmailValidator([message=None, code=None, whitelist=None])

Here’s a sample custom validator:

from django.core.exceptions import ValidationError

def validate_even(value):
   if value % 2 != 0:
      raise ValidationError('%s is not an even number' % value)

Then, this validator can be used for any fields when setting up the models:

class MyModel(models.Model):
   even_field = models.IntegerField(validators=[validate_even])

It can also be used for forms:

class MyForm(forms.Form):
   even_field = forms.IntegerField(validators=[validate_even])

Validators will not be run automatically when you save a model, but if you are using a ModelForm, it will run your validators on any fields that are included in your form.

class Model(**kwargs)

The keyword arguments are the names of the fields you’ve defined on your model. Note that instantiating a model in no way touches your database; for that, you need to save().

Model의 __init__을 이용해 Model이 생성될 때 어떤작업을 하고 싶은 경우 __init__을 override하는 것보다 . 아래와 같이 class method를 이용해 처리할것을 추천한다. 

1. Add a classmethod on the model class:

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)

    @classmethod
    def create(cls, title):
        book = cls(title=title)
        # do something with the book
        return book

book = Book.create("Pride and Prejudice")

2. Add a method on a custom manager (usually preferred):

class BookManager(models.Manager):
    def create_book(self, title):
        book = self.create(title=title)
        # do something with the book
        return book

class Book(models.Model):
    title = models.CharField(max_length=100)

    objects = BookManager()

book = Book.objects.create_book("Pride and Prejudice")

Customizing model loading

Model이 loading될때 추가 작업을 하고싶은 경우 from_db()를 override한다.classmethod

Model.from_db(db, field_names, values)

참고자료) https://www.webforefront.com/django/modelmethodsandmetaoptions.html 하단부

Refreshing objects from database

If you delete a field from a model instance, accessing it again reloads the value from the database:

>>> obj = MyModel.objects.first()
>>> del obj.field
>>> obj.field  # Loads the field from the database

Model.refresh_from_db(using=None, fields=None)

if you need to reload a model’s values from the database, you can use the refresh_from_db() method.

Validating objects

There are three steps involved in validating a model:

  1. Validate the model fields – Model.clean_fields()
  2. Validate the model as a whole – Model.clean()
  3. Validate the field uniqueness – Model.validate_unique()

All three steps are performed when you call a model’s full_clean() method.


Model.full_clean(exclude=None, validate_unique=True)

This method calls Model.clean_fields(), Model.clean(), and Model.validate_unique() (if validate_unique is True) 유효성 검사중에 어디서든 ValidationError raise되면 유효성검사가 실패한것이 된다.

The optional exclude argument can be used to provide a list of field names that can be excluded from validation and cleaning. ModelForm uses this argument to exclude fields that aren’t present on your form from being validated since any errors raised could not be corrected by the user.

Note that full_clean() will not be called automatically when you call your model’s save() method. 

full_clean()의 호출방법의 예는 아래와 같다.

from django.core.exceptions import ValidationError
try:
    article.full_clean()
except ValidationError as e:
    # Do something based on the errors contained in e.message_dict.
    # Display them to a user, or handle them programmatically.
    pass


Model.clean_fields(exclude=None)

This method will validate all fields on your model. The optional exclude argument lets you provide a list of field names to exclude from validation. It will raise a ValidationError if any fields fail validation.

The second step full_clean() performs is to call Model.clean(). This method should be overridden to perform custom validation on your model.

Model.clean()

This method should be used to provide custom model validation, and to modify attributes on your model if desired. For instance, you could use it to automatically provide a value for a field, or to do validation that requires access to more than a single field: 아래와 같이 여러 항목을 이용해서 validate하는 경우 clean()에서 작업한다.

import datetime
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

class Article(models.Model):
    ...
    def clean(self):
        # Don't allow draft entries to have a pub_date.
        if self.status == 'draft' and self.pub_date is not None:
            raise ValidationError(_('Draft entries may not have a publication date.'))
        # Set the pub_date for published items if it hasn't been set already.
        if self.status == 'published' and self.pub_date is None:
            self.pub_date = datetime.date.today()

To assign exceptions to a specific field, instantiate the ValidationError with a dictionary, where the keys are the field names. 예를 들어 pub_date field의 문제의 경우 아래와 같이 해준다.

class Article(models.Model):
    ...
    def clean(self):
        # Don't allow draft entries to have a pub_date.
        if self.status == 'draft' and self.pub_date is not None:
            raise ValidationError({'pub_date': _('Draft entries may not have a publication date.')})

Model.clean()에서 여러 에러가 발생하는 경우 아래와 같이  you can also pass a dictionary mapping field names to errors:

raise ValidationError({
    'title': ValidationError(_('Missing title.'), code='required'),
    'pub_date': ValidationError(_('Invalid date.'), code='invalid'),
})

ModelForm에서 Meta.fields or Meta.exclude를 이용해 제외된 field의 ValidationError를 raise해도작동이 되지 않고 ValueError가 발생하게 되는데 이에 대한 우회방법은 아래와 같다. 

To work around this dilemma, instead override Model.clean_fields() as it receives the list of fields that are excluded from validation. For example:

class Article(models.Model):
    ...
    def clean_fields(self, exclude=None): #마치 제외된 field가 없는척한다.
        super().clean_fields(exclude=exclude)
        if self.status == 'draft' and self.pub_date is not None:
            if exclude and 'status' in exclude:
                raise ValidationError(
                    _('Draft entries may not have a publication date.')
                )
            else:
                raise ValidationError({
                    'status': _(
                        'Set status to draft if there is not a '
                        'publication date.'
                     ),
                })

Model.validate_unique(exclude=None)

model의 field중에 unique, unique_together ,  unique_for_date|month|year 이 설정되있는 경우 호출되며 어떻게 unique를 결정할지에 대한 내용이 들어 간다.



Saving objects

To save an object back to the database, call save():

Model.save(force_insert=False, force_update=False,using=DEFAULT_DB_ALIAS, update_fields=None)



Auto-incrementing primary keys

AutoField — an auto-incrementing primary key — then that auto-incremented value will be calculated and saved as an attribute on your object the first time you call save():

>>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b2.id     # Returns None, because b2 doesn't have an ID yet.
>>> b2.save()
>>> b2.id     # Returns the ID of your new object.

There’s no way to tell what the value of an ID will be before you call save()

For convenience, each model has an AutoField named id by default unless you explicitly specify primary_key=True on a field in your model. See the documentation for AutoField for more details. 특별히 특정 field에  primary_key=True 를 설정하지 않으면 id라는 AutoField 가 만들어 지게 된다.



Model.pk

자동으로 만들어진 AutoField이건 개발자가 primary_key=True 설정해 지정된 field이건 간에 간단히 pk 이름으로 접근할수 있다.



AutoField 로 정의 되어있더라도 개발자가 임의로 값을 할당할수 있다.

AutoField 이더라도 아래와 같이 처음 obj가 생성될때 넣어주면 이미 있지 않은 id라면 만들어진다. 이미 있던 id라면 내용이 update가 된다.

>>> b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b3.id     # Returns 3.
>>> b3.save()
>>> b3.id     # Returns 3.
b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.')
b4.save()  # Overrides the previous blog with ID=3!