• Skip to main content
  • Skip to primary sidebar

学習記録

Django

customsrulingsdbのAWS配置状況

2022年8月4日 by 河副 太智 Leave a Comment

静的ファイル、全品目画像は
/usr/share/nginx/html/static
にある

 

アプリ本体は以下のディレクトリにある
/home/app_admin/venv_ruling/ruling/

Filed Under: AWS, Django

robots.txtの作り方

2021年6月30日 by 河副 太智 Leave a Comment

app_dir/urls.py

1
2
3
4
5
6
7
from django.views.generic import TemplateView
 
urlpatterns = [
    <span class="n">path</span><span class="p">(</span>
        <span class="s">"robots.txt"</span><span class="p">,</span>
        <span class="n">TemplateView</span><span class="p">.</span><span class="n">as_view</span><span class="p">(</span><span class="n">template_name</span><span class="o">=</span><span class="s">"robots.txt"</span><span class="p">,</span> <span class="n">content_type</span><span class="o">=</span><span class="s">"text/plain"</span><span class="p">),</span>
    <span class="p">),</span>

記述内容(google以外のクローラーを拒否)

1
2
3
4
5
User-agent: *
Disallow: /
 
User-agent: Googlebot
Allow: /

Filed Under: Django

.envファイル設定

2021年6月30日 by 河副 太智 Leave a Comment

設定の基本はこちらを参照

envファイル読み込み

1
2
3
4
5
6
7
8
9
10
11
#env設定
import environ
env = environ.Env(
    # set casting, default value
    DEBUG=(bool, False)
)
 
env.read_env('.env')
 
# False if not in os.environ
DEBUG = env('DEBUG')

settings.py設定

1
2
3
4
5
6
7
8
9
10
11
12
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
 
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': env('DATABASE_NAME'),
        'USER': env('USER'),
        'PASSWORD': env('DATABASE_PASS'),
        'HOST': '',
        'PORT': '',
    }
}

.envファイル設定

1
2
3
4
EMAIL_HOST_PASSWORD=SG.xxxxxxxxxxxxxxJ30Ame4ORjxiM880j0eL_3rgU
DATABASE_PASS=xxxxxxx
DATABASE_NAME=xxxxxxx
USER=xxxxxx

イコール(=)の両側にスペースがあるとエラーになるのでぴったりくっつける。
ローカルからaws等にgitを使用せずにenvファイルを移動する際、googledriveで
別PCに転送すると以下のようンエラーが出る。
django.core.exceptions.ImproperlyConfigured: Set the EMAIL_HOST_PASSWORD environment variable

USBメモリでenvファイルの移動をすれば問題ない。

 

Filed Under: Django

Djangoのviewからメール送信

2021年6月15日 by 河副 太智 Leave a Comment

SendGridの登録方法はLearnDjango様のページ下部Email Serviceを参考

view.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.core.mail import send_mail
#メール送信
subject = "Thank you for your subscription to xxx."
message = "Dear " + name + "\n\nThank you for your subscription to xxx. \nHere is your Subscriptiondetail " \
"\n\nInvoice.No: " + invoice +\
"\nSubscription ID: " + stripe_customer_id +\
"\nApplication date: " + date +\
"\nName: " + name +\
"\nMonthly Subscription Fee: " + currency.upper() + " " + price_new +\
"\n\nYou will be charged " + currency.upper() + " " +  price_new + " every month until you cancel your subscription."\
"\n\nif you have any question please reply this email.\n\nBest Regards \n\nxxx\nxxx."
 
from_email = 'xxx@xxx.com'
admin_email = ['xxx@xxx.com',customer_email ]
print("send")
try:
    send_mail(subject ,message, from_email, admin_email)
except:
    import traceback
    traceback.print_exc()

settings.py

1
2
3
4
5
6
7
DEFAULT_FROM_EMAIL = 'xxx@xxxx.com'
EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_HOST_USER = 'apikey'
EMAIL_HOST_PASSWORD = 'SG.j2IQBU_xxxxxxxxxxxxxxxxxxxxxxxxxx'
# EMAIL_HOST_PASSWORD = 'customsxxxxxx'
EMAIL_PORT = 587
EMAIL_USE_TLS = True

 

 

Filed Under: Django

Djangoでコンタクトフォームの作成

2021年6月10日 by 河副 太智 Leave a Comment

Djangoでコンタクトフォームの作成を行う際
Koji Mochizuki様の記事を参考にさせて頂きました。
リンクができないのでURLを以下に貼っておきます。
https://medium.com/@kjmczk/django-contact-form-5a35d43b00a6

しかし、そのままではエラーが出てしまうため修正を行い、
SendGridを活用したSMTP送信の方法を備忘録として記録しておく。

SendGridの登録方法はLearnDjango様のページ下部Email Serviceを参考

forms.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from django import forms
from django.conf import settings
from django.core.mail import BadHeaderError, send_mail
from django.http import HttpResponse
 
class ContactForm(forms.Form):
    name = forms.CharField(
        label='',
        max_length=100,
        widget=forms.TextInput(attrs={
            'class': 'form-control',
            'placeholder': "お名前",
        }),
    )
    email = forms.EmailField(
        label='',
        widget=forms.EmailInput(attrs={
            'class': 'form-control',
            'placeholder': "メールアドレス",
        }),
    )
    message = forms.CharField(
        label='',
        widget=forms.Textarea(attrs={
            'class': 'form-control',
            'placeholder': "お問い合わせ内容",
        }),
    )
 
    def send_email(self):
        subject = "お問い合わせ"
        message = self.cleaned_data['message']
        name = self.cleaned_data['name']
        email = self.cleaned_data['email']
        from_email = 'xxxxxx@xxxxxx.com'
        admin_email = ['xxxxxx@xxxxxx.com']
 
        #メッセージと名前とメールを結合
        send_message = message + "\n" + name + "\n" + email
        try:
            send_mail(subject ,send_message, from_email, admin_email)
        except BadHeaderError:
            return HttpResponse("無効なヘッダが検出されました。")

from_emailとadmin_emailに代入するメールアドレスはそれぞれSendGridのSingle Sender VerificationにあるFROMとREPLYと一致させないとエラーになってしまう。

その為、コンタクトフォームから連絡をもらってもfromが自分のアドレスなので
そのまま直接返信ができないという奇妙な状態にあり、これは未解決。

また、admin_emailの部分はリストかタプルでないと受け付けてもらえない、
送信先アドレスをSendGridのSingle Sender Verificationにある

 

SendGridにSender設定したメールアドレスの確認はSingleSenderVerificationを確認。

Settings > Sender Authentication > Single Sender Verification > Veryfy a Single Senderでも
行ける。

view.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from django.views.generic import TemplateView
from django.views.generic.edit import FormView
from .forms import ContactForm
 
class ContactFormView(FormView):
    template_name = 'contact/contact_form.html'
    form_class = ContactForm
    success_url = 'result'
 
    def form_valid(self, form):
        form.send_email()
        return super().form_valid(form)
 
 
class ContactResultView(TemplateView):
    template_name = 'contact/contact_result.html'
 
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['success'] = "お問い合わせは正常に送信されました。"
        return context

urls.py

1
2
3
4
5
6
7
8
from django.urls import path,include
from .views import ContactFormView, ContactResultView
 
urlpatterns = [
    ...
    path('contact/', ContactFormView.as_view(), name='contact_form'),
    path('contact/result/', ContactResultView.as_view(), name='contact_result'),  
]

テンプレートの作成

プロジェクトルートのtemplates内にcontactフォルダを作り、その中にcontact_form.html、contact_result.htmlファイルを新規作成

1
2
3
4
templates/
    contact/
        contact_form.html
        contact_result.html

contact_form.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% block content %}
<div class="container">
  <div class="row">
    <div class="col-md-8">
      <h1>お問い合わせ</h1>
      <p>お問い合わせフォームです。</p>
      <form method="POST">{% csrf_token %}
        {{ form.as_p }}
        <button type="submit" class="btn btn-primary">送信</button>
      </form>
    </div>
  </div>
</div>
{% endblock %}

contact_result.html

1
2
3
{% block content %}
{{ success }}
{% endblock %}

settings.py

1
2
3
4
5
6
7
8
9
10
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
ACCOUNT_EMAIL_VERIFICATION = "none"
 
# sendgrid でメール送信する場合
DEFAULT_FROM_EMAIL = 'xxxxx@xxxxxx.com'
EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_HOST_USER = 'apikey' #ここは本当にapikeyと入力する個別の名称ではない
EMAIL_HOST_PASSWORD = 'SG.j2Ixxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
EMAIL_PORT = 587
EMAIL_USE_TLS = Trueこ

これでcontact/ページにいけばフォームが設置されており、送信が可能。

Filed Under: Django

DjangoでStripeを使用した月額課金方法

2021年5月29日 by 河副 太智 Leave a Comment

基本Django Stripe Subscriptionsを参考にする。

model設定で以下のエラーが出た場合

ERRORS:
subscriptions.StripeCustomer.user: (fields.E301) Field defines a relation with the model ‘auth.User’, which has been swapped out.
HINT: Update the relation to point at ‘settings.AUTH_USER_MODEL’.

参照サイトではDjango modelの仕様を前提としているのでエラーが出る
UserModelを使用する場合models.pyの記述方法を調整する必要がある。

subscriptions/models.pyに以下を記述する。

1
2
3
4
5
6
7
8
9
10
11
from django.conf import settings
from django.db import models
 
 
class StripeCustomer(models.Model):
    user = models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    stripeCustomerId = models.CharField(max_length=255)
    stripeSubscriptionId = models.CharField(max_length=255)
 
    def __str__(self):
        return self.user.username

 

 

支払い完了画面に移行しない場合

参照サイトではホームページ自体がStripeペイメントのページなので、
別のURLを設定する場合subscriptions/view.pyのurlを確認する

例えば以下の場合

success_url=domain_url + ‘success?session_id=
cancel_url=domain_url + ‘cancel/’,

以下に変更する

success_url=domain_url + ‘subscriptions/success?session_id=
cancel_url=domain_url + ‘subscriptions/cancel/’,

subscriptions_stripecustomerが無いというエラー

Stripe用のモデル(データベース)が設定されていない可能性がある。

python manage.py makemigrations subscriptions
python manage.py migrate subscriptions
を行う

stripe_webhookエラー

参照サイトではDjango modelの仕様を前提としているので
webhookでuser名の取得ができない。
user = User.objects.get(id=client_reference_id)にエラーが出る

カスタムユーザーモデルの場合上記のコードが動かないので
以下のモジュールをインポートしてコード内容をget_user_model()に変更する

from django.contrib.auth import get_user_model #インポートする

user = get_user_model().objects.get(id=client_reference_id)
ソース

NameError: name ‘request’ is not defined

ListViewにおいてStripeで支払った人にのみList内容を見せる場合で、
ListViewクラスの関数の引数がselfとなっている場合
stripe_customer = StripeCustomer.objects.get(user=request.user)で
上記エラーが出る。

その為”self.request.user”にする必要がある
stripe_customer = StripeCustomer.objects.get(user=self.request.user)

ソース

キャンセル処理

subscriptions/view.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@login_required
def cancel_subscription(request):
    if request.user.is_authenticated:
 
        stripe_customer = StripeCustomer.objects.get(user=request.user)
        stripe.api_key = settings.STRIPE_SECRET_KEY
        sub_id = stripe.Subscription.retrieve(stripe_customer.stripeSubscriptionId)
        print(sub_id)
        print(sub_id.id)
        user=request.user
        print("user is")
        print(user)
 
        try:
            #delete from stripeapi
            stripe.Subscription.delete(sub_id)
 
            #delete from StripeCustomer model
            StripeCustomer.objects.filter(stripeSubscriptionId=sub_id.id).delete()
            print('unsubscribed')
 
 
 
        except Exception as e:
            import traceback
            traceback.print_exc()
            return JsonResponse({'error': (e.args[0])}, status =403)
    
    return render(request, 'home.html')

メールアドレスの取得

1
2
3
4
5
session = event['data']['object']
stripe_customer_id = session.get('customer')
 
retrieve = stripe.Customer.retrieve(stripe_customer_id)
print(retrieve['email'])

登録者にお礼のメール

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        #メールアドレスの取得(その他名前などの追加情報も取得可)
        retrieve = stripe.Customer.retrieve(stripe_customer_id)
        print(retrieve['email'])
        customer_email = retrieve['email']
 
        subject = "Thank you for your subscription."
        message = "Here is your products"
        from_email = 'kawazoe@customslegaloffice.com'
        admin_email = ['kawazoe@customslegaloffice.com',customer_email ]
        print("send")
        try:
            send_mail(subject ,message, from_email, admin_email)
        except:
            import traceback
            traceback.print_exc()

StripeAPIから取得できるデータ一覧

event取得

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
    stripe.api_key = settings.STRIPE_SECRET_KEY
    endpoint_secret = settings.STRIPE_ENDPOINT_SECRET
    payload = request.body
    sig_header = request.META['HTTP_STRIPE_SIGNATURE']
    event = None
    
 
    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )
        print(event)
 
#以下取得できるデータ一覧
 
{
  "api_version": "2020-08-27",
  "created": 1623290218,
  "data": {
    "object": {
      "allow_promotion_codes": null,
      "amount_subtotal": 4,
      "amount_total": 4,
      "billing_address_collection": null,
      "cancel_url": "http://3.129.28.xxx/subscriptions/cancel/",
      "client_reference_id": "1",
      "currency": "jpy",
      "customer": "cus_Jduyyr093qeu2V",
      "customer_details": {
        "email": null,
        "tax_exempt": "none",
        "tax_ids": []
      },
      "customer_email": null,
      "id": "cs_test_a1eysaqK9Evwz4U85W30sPUo9COWcG96Akw5f3CnSKgmfT7qqVwijZixxx",
      "livemode": false,
      "locale": null,
      "metadata": {},
      "mode": "subscription",
      "object": "checkout.session",
      "payment_intent": null,
      "payment_method_options": {},
      "payment_method_types": [
        "card"
      ],
      "payment_status": "paid",
      "setup_intent": "seti_1J0d8eJvSJBZN02Kwwzupxxx",
      "shipping": null,
      "shipping_address_collection": null,
      "submit_type": null,
      "subscription": "sub_JduyaYhZ5UrRn1",
      "success_url": "http://3.129.28.xxx/subscriptions/success?session_id={CHECKOUT_SESSION_ID}",
      "total_details": {
        "amount_discount": 0,
        "amount_shipping": 0,
        "amount_tax": 0
      }
    }
  },
  "id": "evt_1J0d8gJvSJBZN02KkHPwAxxx",
  "livemode": false,
  "object": "event",
  "pending_webhooks": 3,
  "request": {
    "id": null,
    "idempotency_key": null
  },
  "type": "checkout.session.completed"
}

例:event取得後各種IDを取得

client_reference_id,customer,subscriptionの3つのデータが欲しい場合

1
2
3
4
5
6
session = event['data']['object']
 
# Fetch all the required data from session
client_reference_id = session.get('client_reference_id')
stripe_customer_id = session.get('customer')
stripe_subscription_id = session.get('subscription')

顧客データ詳細を取得

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
session = event['data']['object']
stripe_customer_id = session.get('customer')
retrieve = stripe.Customer.retrieve(stripe_customer_id)
 
#以下取得できるデータ一覧
{
"address": {
"city": null,
"country": "JP",
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"balance": 4,
"created": 1623314935,
"currency": "jpy",
"default_source": null,
"delinquent": false,
"description": null,
"discount": null,
"email": "xxxxxxxx@gmail.com",
"id": "cus_Je1cRQy1VsbQrb",
"invoice_prefix": "77146EAE",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": null,
"footer": null
},
"livemode": false,
"metadata": {},
"name": "xxxxxx KAWAZOE",
"next_invoice_sequence": 2,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
}

例:顧客データ詳細取得後各種情報を取得

1
2
3
4
5
6
#メールアドレスを取得したい場合
customer_email = retrieve['email']
 
#価格の取得
currency = retrieve['currency']
amount = retrieve['balance']

更に顧客データ詳細を表示

※但しview内にて取得する方法は不明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
stripe_customer = StripeCustomer.objects.get(user=request.user)
stripe.api_key = settings.STRIPE_SECRET_KEY
sub_id = stripe.Subscription.retrieve(stripe_customer.stripeSubscriptionId)
print(sub_id)
 
#以下取得できるデータ一覧
 
{
  "application_fee_percent": null,
  "billing_cycle_anchor": 1623372697,
  "billing_thresholds": null,
  "cancel_at": null,
  "cancel_at_period_end": false,
  "canceled_at": null,
  "collection_method": "charge_automatically",
  "created": 1623372697,
  "current_period_end": 1625964697,
  "current_period_start": 1623372697,
  "customer": "cus_JeH97oQYVT2Ehd",
  "days_until_due": null,
  "default_payment_method": "pm_1J0yaxJvSJBZN02Kb7nL7O37",
  "default_source": null,
  "default_tax_rates": [],
  "discount": null,
  "ended_at": null,
  "id": "sub_JeH9mHHGs3SrM2",
  "items": {
    "data": [
      {
        "billing_thresholds": null,
        "created": 1623372697,
        "id": "si_JeH9r5JrZtzGZS",
        "metadata": {},
        "object": "subscription_item",
        "plan": {
          "active": true,
          "aggregate_usage": null,
          "amount": 4,
          "amount_decimal": "4",
          "billing_scheme": "per_unit",
          "created": 1623289653,
          "currency": "jpy",
          "id": "price_1J0czZJvSJBZN02KVdyeJljf",
          "interval": "month",
          "interval_count": 1,
          "livemode": false,
          "metadata": {},
          "nickname": null,
          "object": "plan",
          "product": "prod_Jb1LklwtRAVkQB",
          "tiers_mode": null,
          "transform_usage": null,
          "trial_period_days": null,
          "usage_type": "licensed"
        },
        "price": {
          "active": true,
          "billing_scheme": "per_unit",
          "created": 1623289653,
          "currency": "jpy",
          "id": "price_1J0czZJvSJBZN02KVdyeJljf",
          "livemode": false,
          "lookup_key": null,
          "metadata": {},
          "nickname": null,
          "object": "price",
          "product": "prod_Jb1LklwtRAVkQB",
          "recurring": {
            "aggregate_usage": null,
            "interval": "month",
            "interval_count": 1,
            "trial_period_days": null,
            "usage_type": "licensed"
          },
          "tiers_mode": null,
          "transform_quantity": null,
          "type": "recurring",
          "unit_amount": 4,
          "unit_amount_decimal": "4"
        },
        "quantity": 1,
        "subscription": "sub_JeH9mHHGs3SrM2",
        "tax_rates": []
      }
    ],
    "has_more": false,
    "object": "list",
    "total_count": 1,
    "url": "/v1/subscription_items?subscription=sub_JeH9mHHGs3SrM2"
  },
  "latest_invoice": "in_1J0yazJvSJBZN02K4zKW5kTn",
  "livemode": false,
  "metadata": {},
  "next_pending_invoice_item_invoice": null,
  "object": "subscription",
  "pause_collection": null,
  "pending_invoice_item_interval": null,
  "pending_setup_intent": null,
  "pending_update": null,
  "plan": {
    "active": true,
    "aggregate_usage": null,
    "amount": 4,
    "amount_decimal": "4",
    "billing_scheme": "per_unit",
    "created": 1623289653,
    "currency": "jpy",
    "id": "price_1J0czZJvSJBZN02KVdyeJljf",
    "interval": "month",
    "interval_count": 1,
    "livemode": false,
    "metadata": {},
    "nickname": null,
    "object": "plan",
    "product": "prod_Jb1LklwtRAVkQB",
    "tiers_mode": null,
    "transform_usage": null,
    "trial_period_days": null,
    "usage_type": "licensed"
  },
  "quantity": 1,
  "schedule": null,
  "start_date": 1623372697,
  "status": "active",
  "transfer_data": null,
  "trial_end": null,
  "trial_start": null
}

HTMLにて上記データを表示

view.py

1
2
3
4
5
6
7
8
stripe_customer = StripeCustomer.objects.get(user=request.user)
stripe.api_key = settings.STRIPE_SECRET_KEY
subscription = stripe.Subscription.retrieve(stripe_customer.stripeSubscriptionId)
 
return render(request, 'home.html',{
    'subscription': subscription,
    'product': product,
    })

html

1
2
ProductDescription:{ product.description}
Price:{ subscription.plan.currency}} {{ subscription.plan.amount}}

 

 

ローカルでStripe CLI でwebhookをテスト

exeファイルをクリックしてもThis is a command line tool.
You need to open cmd.exe and run it from there.と出て消えてしまうので
exeファイルをShiftを押しながら右クリックする事で開く事ができる。

↓コツはカーソルをstripeから離れた白地の部分でShiftを押しながら
右クリックする事。
Stripeを選んだまま右クリックしてもPowershellウィンドウを開く事は
できない

windowsの場合ログインのコマンドは以下の通り。(stripe loginだけではだめ)

1
./stripe login

1
./stripe listen --forward-to localhost:8000/subscriptions/webhook/

Filed Under: Django

  • Page 1
  • Page 2
  • Go to Next Page »

Primary Sidebar

カテゴリー

  • AWS
  • Bootstrap
  • Dash
  • Django
  • flask
  • GIT(sourcetree)
  • Plotly/Dash
  • VPS
  • その他tool
  • ブログ
  • プログラミング
    • Bokeh
    • css
    • HoloViews
    • Jupyter
    • Numpy
    • Pandas
    • PosgreSQL
    • Python 基本
    • python3
      • webアプリ
    • python3解説
    • scikit-learn
    • scipy
    • vps
    • Wordpress
    • グラフ
    • コマンド
    • スクレイピング
    • チートシート
    • データクレンジング
    • ブロックチェーン
    • 作成実績
    • 時系列分析
    • 機械学習
      • 分析手法
      • 教師有り
    • 異常値検知
    • 自然言語処理
  • 一太郎
  • 数学
    • sympy
      • 対数関数(log)
      • 累乗根(n乗根)
    • 暗号学

Copyright © 2025 · Genesis Sample on Genesis Framework · WordPress · Log in