چگونه در جنگو Stripe SaaS بسازیم
مقدمه
با سلام خدمت تمامی مخاطبین خوب وبسایت پایتونی ها با یک مقاله آموزشی از سری مقالات جنگو در خدمت شما بزرگواران هستیم تا در این مقاله با استفاده از جنگو و Strip یک قالب بر مبنای Saas طراحی کنیم.
بیایید با استفاده از Django و Stripe یک قالب SaaS بسازیم. با پروژه boilerplate، می توانید به سرعت حداقل محصول قابل دوام خود را به جای چند هفته در چند روز بسازید.
ما از Django برای Backend و Stripe برای جمع آوری پرداخت های ماهانه استفاده می کنیم.
مشکلات و راه حل ها را شناسایی کنید
اولین و مهم ترین قضیه این است که برای شروع، به یک ایده نیاز دارید.
ایده های تصادفی کاملا عالی است، اما هنگام یافتن یک ایده SaaS مفید نیست چون ممکن است شما را گمراه کند.
با شناسایی مشکلات در هر جامعه ای از مردم یا صنعت تجاری شروع کنید.
چرا باید مشکلات را شناسایی کنیم؟
زیرا حل مشکلات به این معنی است که می توانید برای ارائه راه حل پول دریافت کنید.
اگر نرم افزار شما مشکلی را در صنعت آنها حل کند، شرکت ها با کمال میل هزینه راه حل را به صورت مکرر پرداخت می کنند.
3 مطالعه موردی SaaS سریع: مشکلات و راه حل ها
- مخاطب : افراد، تیم های کوچک و شرکت ها
- مشکل : استفاده از برنامه های طراحی گرافیک ساده نیست و به فردی با تجربه در طراحی گرافیک نیاز دارد.
- راه حل : یک پلت فرم کشیدن و رها کردن برای طراحی گرافیکی که هر کسی می تواند از آن استفاده کند.
- مخاطب : توسعه دهندگان وب و غیر برنامه نویسان
- مشکل : کدنویسی یک وب سایت ماه ها طول می کشد.
- راه حل : یک پلت فرم بدون کد برای توسعه وب.
- مخاطب : مشاغل کوچک
- مشکل : اجرای یک کمپین بازاریابی ایمیلی زمان بر است.
- راه حل : ایمیل های خودکار را برنامه ریزی کنید و نتایج را پیگیری کنید.
Boilerplate را برای SaaS خود ایجاد کنید
مثال مشکل و راه حل SaaS
بنابراین، فرض کنید مشکلی را شناسایی کردهایم – کسبوکارهای کوچک پلتفرم سادهای برای نظارت بر هزینههای ماهانه خود ندارند. اکسل وجود دارد اما استفاده از آن نیاز به دانش قبلی دارد .
راه حل ما ارائه نرم افزاری است که در آن کاربران برای داشتن یک داشبورد ساده که به آنها امکان می دهد هزینه های ماهانه خود را مدیریت کنند، پول پرداخت می کنند.
حال به خاطر داشته باشید که این فقط یک مثال است. در حال حاضر شرکت هایی وجود دارند که نرم افزار حسابداری را برای مشاغل کوچک ارائه می دهند. اگر بتوانیم ارزش پیشنهادی بهتری ارائه دهیم، استفاده از آن را آسانتر کنیم و مخاطبان مناسبی را هدف قرار دهیم، میتوانیم متناسب با بازار محصول را پیدا کنیم.
ما اسکلت یک MVP را برای تأیید اعتبار محصول و جمعآوری بازخورد بدون صرف زمان یا هزینه زیاد برای محصول میسازیم.
به این ترتیب تنها با افزودن عملکرد اصلی می توانیم به سرعت برای توسعه حرکت کنیم.
اکنون بیایید شروع به ساخت یک محصول حداقل قابل دوام (MVP) کنیم.
جنگو را نصب کنید
خط فرمان ویندوز
C:\Users\Owner\desktop> py -m venv env C:\Users\Owner\desktop> cd env C:\Users\Owner\desktop\env> Scripts\activate (env)C:\Users\Owner\desktop\env>pip install django (env)C:\Users\Owner\desktop\env>django-admin startproject mysite (env)C:\Users\Owner\desktop\env> cd mysite (env)C:\Users\Owner\desktop\env\mysite> py manage.py startapp main (env) C:\Users\Owner\Desktop\Code\env\mysite>py manage.py runserver
یک محیط مجازی بسازید و جنگو را نصب کنید. محیط مجازی یک محیط ایزوله برای بسته های خاص پروژه ایجاد می کند. جنگو چارچوبی است که پایگاه داده، احراز هویت کاربر و موتورهای قالب را کنترل می کند.
Django Crispy Forms را نصب کنید
خط فرمان ویندوز
(env)C:\Users\Owner\desktop\env>pip install django-crispy-forms
Django Crispy Forms را نصب کنید . ما از این بسته برای استایل دادن به فرمهای جنگو که برای ثبت نام و ورود کاربر استفاده میشوند، استفاده میکنیم.
جنگو را پیکربندی کنید
env > mysite > mysite > settings.py
... # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'main', 'crispy_forms', ] CRISPY_TEMPLATE_PACK = 'bootstrap4' ... # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.2/howto/static-files/ STATIC_URL = '/static/' STATICFILES_DIRS = [ BASE_DIR / "static", '/var/www/static/', ] ... MESSAGE_TAGS = { messages.DEBUG: 'alert-secondary', messages.INFO: 'alert-info', messages.SUCCESS: 'alert-success', messages.WARNING: 'alert-warning', messages.ERROR: 'alert-danger', }
اکنون باید تنظیمات اولیه جنگو را به فایل تنظیمات خود اضافه کنید. main
به لیست برنامه های نصب شده اضافه crispy_forms
کنید، بسته قالب Crispy forms را اضافه کنید و پروژه را برای فایل های ثابت پیکربندی کنید .
همچنین، چارچوب پیام های جنگو را با هشدارهای بوت استرپ پیکربندی کنید .
یک صفحه StyleSheet اضافه کنید
env > mysite > (New Folder) static > (New Folder) main > (New Folder) css
/*client images */ .client { height:60px; width:60px; border-radius: 50%; object-fit:cover; } /*quote svgs*/ .bi-chat-square-quote{ color:rgb(108,99,255, 0.3); } /*badge color*/ .badge{ background-color:rgb(108,99,255, 0.2) !important; color:rgb(108,99,255) !important; } /*stars color */ .bi-star, .bi-star-fill, .bi-star-half{ color:#ffc107; } /* cta text*/ .cta{ font-size: 70px; line-height: 75px; font-family: 'Roboto Slab', serif; } .navbar-brand{ font-family: 'Roboto Slab', serif; } .cta-span{ color:rgb(108,99,255, 0.8) !important; } .bg-primary{ background:rgb(108,99,255) !important; } .border-primary { border: 1px solid rgb(108,99,255) !important; } .btn-primary, .btn-primary:visited { font-weight:600; background: rgb(255,86,120); border: 1px solid rgb(255,86,120); color: white !important; border-radius:0px; } .btn-primary:hover, .btn-primary:active, .btn-primary:focus, .btn-outline-primary:hover, .btn-outline-primary:active, .btn-outline-primary:focus { background: rgb(255,61,100) !important; border: 1px solid rgb(255,61,100) !important; border-radius:0px; color:white !important; } .btn-outline-primary, .btn-outline-primary:visited { font-weight:600; background: transparent; border: 1px solid rgb(255,86,120); color: rgb(255,61,100); border-radius:0px; } .form-control{ background:#F2F2F2; border-radius:0px; border:none; height:48px !important; } /*background circle for icons*/ .numberCircle { border-radius: 50%; width: 60px; height: 60px; padding: 8px; background:rgb(255,86,120, 0.2); border: none; color: rgb(255,86,120); text-align: center; font-size: 25px; } p{ line-height: 25px; } .nav-pills .nav-link.active, .nav-pills .show>.nav-link { color: #fff !important; background-color:rgb(138,131,255); } .nav-link:hover{ color: rgb(138,131,255) !important; } .footer-social-link:hover { color:rgb(137,130,255) !important; } @media only screen and (max-width: 600px) { .cta{ font-size: 50px; line-height: 55px; font-family: 'Roboto Slab', serif; } } @media only screen and (max-width:768px){ /*background circle for icons*/ .numberCircle { border-radius: 50%; width: 60px; height: 60px; padding: 8px; background:rgb(255,86,120, 0.2); border: none; color: rgb(255,86,120); text-align: center; font-size: 25px; margin-left: auto; margin-right: auto; } }
یک شیوه نامه سفارشی به پروژه اضافه کنید
تصاویر را اضافه کنید
env > mysite > (New Folder) static > (New Folder) main > (New Folder) img
تصاویر را به پروژه اضافه کنید. با خیال راحت از تصاویر خود استفاده کنید یا تصویر منبع باز را که در opendoodles.com استفاده کردم، بررسی کنید .
یک Menu ایجاد کنید
env > mysite > main > templates > main > (New Folder) شامل > (New File) navbar.html
<nav class="navbar navbar-expand-lg navbar-light bg-transparent"> <div class="container-fluid"> <a class="navbar-brand" href="/"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" style="color: rgb(137,130,255);" fill="currentColor" class="bi bi-bar-chart-fill" viewBox="0 0 16 16"> <path d="M1 11a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-3zm5-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7zm5-5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V2z"/> </svg> Track Smart </a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="False" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarText"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> {% if user.is_authenticated %} <li class="nav-item"> <a class="nav-link" href="/dashboard">Dashboard</a> </li> <li class="nav-item"> <a class="nav-link" href="/logout">Logout</a> </li> {% else %} <li class="nav-item"> <a class="nav-link" href="#">Features</a> </li> <li class="nav-item"> <a class="nav-link" href="/pricing">Pricing</a> </li> <li class="nav-item"> <a class="nav-link" href="/login">Login</a> </li> {% endif %} </ul> {% if user.is_authenticated %} <p>Welcome, {{user.username}}</p> {% else %} <a href="/register" class="btn btn-outline-primary" type="button">Get Started</a> {% endif %} </div> </div> </nav>
در اینجا یک نوار ناوبری با جنگو در صورت وجود شرایط موجود در قالب وجود دارد. من دو شرط if دارم که هر دوی آنها متن و پیوندهای متفاوتی را به کاربر تایید شده نسبت به کاربر غیرثبت شده نشان می دهد.
env > mysite > main > templates > main > (New Folder) شامل > (New File) footer.html
<div class="container-fluid text-center p-5"> <div class='row'> <div class='col-lg-6 col-md-12 text-center text-md-left'> <p>© Company</p> </div> <div class='col-lg-6 col-md-12 text-center text-md-left'> <a href="#" class="text-dark footer-social-link"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="mx-2" fill="currentColor" class="bi bi-facebook" viewBox="0 0 16 16"> <path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951z"/> </svg></a> <a href="#" class="text-dark footer-social-link"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="mx-2" fill="currentColor" class="bi bi-instagram" viewBox="0 0 16 16"> <path d="M8 0C5.829 0 5.556.01 4.703.048 3.85.088 3.269.222 2.76.42a3.917 3.917 0 0 0-1.417.923A3.927 3.927 0 0 0 .42 2.76C.222 3.268.087 3.85.048 4.7.01 5.555 0 5.827 0 8.001c0 2.172.01 2.444.048 3.297.04.852.174 1.433.372 1.942.205.526.478.972.923 1.417.444.445.89.719 1.416.923.51.198 1.09.333 1.942.372C5.555 15.99 5.827 16 8 16s2.444-.01 3.298-.048c.851-.04 1.434-.174 1.943-.372a3.916 3.916 0 0 0 1.416-.923c.445-.445.718-.891.923-1.417.197-.509.332-1.09.372-1.942C15.99 10.445 16 10.173 16 8s-.01-2.445-.048-3.299c-.04-.851-.175-1.433-.372-1.941a3.926 3.926 0 0 0-.923-1.417A3.911 3.911 0 0 0 13.24.42c-.51-.198-1.092-.333-1.943-.372C10.443.01 10.172 0 7.998 0h.003zm-.717 1.442h.718c2.136 0 2.389.007 3.232.046.78.035 1.204.166 1.486.275.373.145.64.319.92.599.28.28.453.546.598.92.11.281.24.705.275 1.485.039.843.047 1.096.047 3.231s-.008 2.389-.047 3.232c-.035.78-.166 1.203-.275 1.485a2.47 2.47 0 0 1-.599.919c-.28.28-.546.453-.92.598-.28.11-.704.24-1.485.276-.843.038-1.096.047-3.232.047s-2.39-.009-3.233-.047c-.78-.036-1.203-.166-1.485-.276a2.478 2.478 0 0 1-.92-.598 2.48 2.48 0 0 1-.6-.92c-.109-.281-.24-.705-.275-1.485-.038-.843-.046-1.096-.046-3.233 0-2.136.008-2.388.046-3.231.036-.78.166-1.204.276-1.486.145-.373.319-.64.599-.92.28-.28.546-.453.92-.598.282-.11.705-.24 1.485-.276.738-.034 1.024-.044 2.515-.045v.002zm4.988 1.328a.96.96 0 1 0 0 1.92.96.96 0 0 0 0-1.92zm-4.27 1.122a4.109 4.109 0 1 0 0 8.217 4.109 4.109 0 0 0 0-8.217zm0 1.441a2.667 2.667 0 1 1 0 5.334 2.667 2.667 0 0 1 0-5.334z"/> </svg></a> <a href="#" class="text-dark footer-social-link"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="mx-2" fill="currentColor" class="bi bi-twitter" viewBox="0 0 16 16"> <path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"/> </svg></a> </div> </div> </div>
یک header.html جنگو ایجاد کنید
env > mysite > main > templates > main > (New File) header.html
<!DOCTYPE html> <html lang="en"> <head> {% load static %} <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous"> <!--Custom CSS--> <link rel="stylesheet" href="{% static 'main/css/stylesheet.css'%}" type="text/css"> <!--Google Fonts--> <link rel="preconnect" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css2?family=Roboto+Slab:[email protected]&display=swap" rel="stylesheet"> <title>Django + Stripe SaaS</title> </head> <body> {% include "main/includes/navbar.html" %} {% include 'main/includes/messages.html' %} <div class="my-5"> {% block content %} {% endblock %} </div> {% include 'main/includes/footer.html' %} <!-- Optional JavaScript; choose one of the two! --> <!-- Option 1: Bootstrap Bundle with Popper --> <script class="lazy" data-class="lazy" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script> </body> </html>
این هدر است که به سایر قالب های HTML گسترش می یابد. از تگ های قالب جنگو و گسترش آن استفاده می کند . همچنین حاوی CDN های Bootstrap است.
یک صفحه اصلی ایجاد کنید
env > mysite > main > templates > main > (New File) home.html
{% extends 'main/header.html' %} {% block content %} {% load static %} <!--CTA--> <div class="container text-center p-lg-5 p-3"> <div class="row p-lg-5 p-3"> <div class="col-lg-6 col-md-12 mb-4"> <h1 class="cta">Track your monthly spending <span class="cta-span">easily.</span></h1> <h5 class="text-muted lead my-3">Don't run away from your finances. Jump right in.</h5> <form class="my-5" method="GET" action="/register"> <div class="row"> <div class="col-xl-6 col-lg-12 mb-4"> <div class="form-floating mb-3"> <input type="text" name="email" class="form-control" id="floatingTextInput1" placeholder="[email protected]"> <label for="floatingTextInput1">Enter email address...</label> </div> </div> <div class="col-xl-6 col-lg-12"> <div class="d-grid"> <button class="btn btn-primary btn-lg" type="submit">Join ➜</button> </div> </div> </div> </form> </div> <div class="col-lg-6 col-md-12 my-auto"> <img class="img-fluid" class="lazy" src="{% static 'main/img/jumping.svg' %}" alt="jumping"> </div> </div> </div> <!--End CTA--> <!--Value Prop--> <div class="container p-lg-5 p-3"> <div class="row p-lg-5 p-3 my-5"> <div class="col-lg-6 col-md-12 order-lg-2 order-md-1 my-auto"> <span class="badge rounded-pill">Track monthly spending</span> <h1 class="my-3">Reach your monthly budget</h1> <p class="text-muted">Set weekly and monthly spending habits for your entire team. Get alerts when your close to your limit.</p> </div> <div class="col-lg-6 col-md-12 order-lg-1 order-md-2 my-auto"> <img class="img-fluid" class="lazy" src="{% static 'main/img/sprinting.gif' %}" alt="sprinting"> </div> </div> </div> <div class="container p-lg-5 p-3"> <div class="row p-lg-5 p-3 my-5"> <div class="col-lg-6 col-md-12 my-auto"> <span class="badge rounded-pill">Upload images and PDFs</span> <h1 class="my-3">Spending made easy</h1> <p class="text-muted">No need to keep physical copies of receipts or bills. Upload your images and PDFs with a click of a button to your account.</p> </div> <div class="col-lg-6 col-md-12 my-auto"> <img class="img-fluid" class="lazy" src="{% static 'main/img/clumsy.svg' %}" alt="clumsy"> </div> </div> </div> <!--End Value Prop--> <!--Features--> <div class="container p-lg-5 p-3 text-center text-md-start"> <h1 class="cta mt-5 text-center">Healthy spending made easy</h1> <h5 class="text-muted lead mt-3 mb-5 text-center">We build a platform designed for small businesses. Track your spending, marketing budget and more all one a simple dashboard.</h5> <div class="row p-lg-5 px-3 py-5"> <div class="col-sm-12 col-md-6 col-lg-3 pb-4"> <div class="numberCircle my-3"> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-phone" viewBox="0 0 16 16"> <path d="M11 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h6zM5 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H5z"/> <path d="M8 14a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/> </svg> </div> <h5 class="card-title">Mobile Friendly</h5> <p class="card-text">Monitor your expenses on the go</p> </div> <div class="col-sm-12 col-md-6 col-lg-3 pb-4"> <div class="numberCircle my-3"> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-archive" viewBox="0 0 16 16"> <path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/> </svg> </div> <h5 class="card-title">Archived Files</h5> <p class="card-text">Refer to archived files forever</p> </div> <div class="col-sm-12 col-md-6 col-lg-3 pb-4"> <div class="numberCircle my-3"> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-bag-plus" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M8 7.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V12a.5.5 0 0 1-1 0v-1.5H6a.5.5 0 0 1 0-1h1.5V8a.5.5 0 0 1 .5-.5z"/> <path d="M8 1a2.5 2.5 0 0 1 2.5 2.5V4h-5v-.5A2.5 2.5 0 0 1 8 1zm3.5 3v-.5a3.5 3.5 0 1 0-7 0V4H1v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4h-3.5zM2 5h12v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5z"/> </svg> </div> <h5 class="card-title">Upload Receipts</h5> <p class="card-text">Upload an image of receipt of your purchases</p> </div> <div class="col-sm-12 col-md-6 col-lg-3 pb-4"> <div class="numberCircle my-3"> <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-alarm" viewBox="0 0 16 16"> <path d="M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z"/> <path d="M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z"/> </svg> </div> <h5 class="card-title">Set Alerts</h5> <p class="card-text">Monitor your spending habits with automatic alerts</p> </div> </div> </div> <!--End Features--> <!--Value Prop #2--> <div class="container p-lg-5 p-3"> <div class="row p-lg-5 p-3 my-5"> <div class="col-lg-6 col-md-12 order-lg-2 order-md-1 my-auto"> <span class="badge rounded-pill">Save your time</span> <h1 class="my-3">Efficiently allocate time</h1> <p class="text-muted">Don't send hours looking over your finances every month. Manage expenses as they happen so you can free up your time.</p> </div> <div class="col-lg-6 col-md-12 order-lg-1 order-md-2 my-auto"> <img class="img-fluid" class="lazy" src="{% static 'main/img/sitting-reading.svg' %}" alt="sitting-reading"> </div> </div> </div> <div class="container p-lg-5 p-3"> <div class="row p-lg-5 p-3 my-5"> <div class="col-lg-6 col-md-12 my-auto"> <span class="badge rounded-pill">Setup made easy</span> <h1>Out of the box configuration</h1> <p class="text-muted">Create an account and get going. Everything is already configured for easy file upload and tracking on the dashboard.</p> </div> <div class="col-lg-6 col-md-12 my-auto"> <img class="img-fluid" class="lazy" src="{% static 'main/img/unboxing.svg' %}" alt="unboxing"> </div> </div> </div> <!--End Value Prop #2--> <!--Testimonials--> <div class="container p-lg-5 p-3 text-center text-md-start"> <div class="row p-lg-5 p-3 my-5"> <div class="col-lg-6 col-md-12 my-5"> <svg xmlns="http://www.w3.org/2000/svg" width="3em" height="3em" fill="currentColor" class="my-3 bi bi-chat-square-quote" viewBox="0 0 16 16"> <path d="M14 1a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-2.5a2 2 0 0 0-1.6.8L8 14.333 6.1 11.8a2 2 0 0 0-1.6-.8H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2.5a1 1 0 0 1 .8.4l1.9 2.533a1 1 0 0 0 1.6 0l1.9-2.533a1 1 0 0 1 .8-.4H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/> <path d="M7.066 4.76A1.665 1.665 0 0 0 4 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112zm4 0A1.665 1.665 0 0 0 8 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112z"/> </svg> <figure> <blockquote class="blockquote"> <p>Great product. Love the easy-to-use interface. Saves my employees at least 2 hours by not having to constantly check up with one another and reference a spreadsheet.</p> </blockquote> <img class="lazy" src="{% static 'main/img/ben.png' %}" class="img-fluid client"><br> <b>Ben Smith</b> <br><small>Founder, Clean Paws</small><br> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16"> <path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16"> <path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16"> <path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16"> <path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star" viewBox="0 0 16 16"> <path d="M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.389 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.522-3.356c.33-.314.16-.888-.282-.95l-4.898-.696L8.465.792a.513.513 0 0 0-.927 0L5.354 5.12l-4.898.696c-.441.062-.612.636-.283.95l3.523 3.356-.83 4.73zm4.905-2.767-3.686 1.894.694-3.957a.565.565 0 0 0-.163-.505L1.71 6.745l4.052-.576a.525.525 0 0 0 .393-.288L8 2.223l1.847 3.658a.525.525 0 0 0 .393.288l4.052.575-2.906 2.77a.565.565 0 0 0-.163.506l.694 3.957-3.686-1.894a.503.503 0 0 0-.461 0z"/> </svg> </figure> </div> <div class="col-lg-6 col-md-12 my-5"> <svg xmlns="http://www.w3.org/2000/svg" width="3em" height="3em" fill="currentColor" class="my-3 bi bi-chat-square-quote" viewBox="0 0 16 16"> <path d="M14 1a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-2.5a2 2 0 0 0-1.6.8L8 14.333 6.1 11.8a2 2 0 0 0-1.6-.8H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2.5a1 1 0 0 1 .8.4l1.9 2.533a1 1 0 0 0 1.6 0l1.9-2.533a1 1 0 0 1 .8-.4H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/> <path d="M7.066 4.76A1.665 1.665 0 0 0 4 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112zm4 0A1.665 1.665 0 0 0 8 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112z"/> </svg> <figure> <blockquote class="blockquote"> <p>I never need to worry about keeping track of receipts or spending. Everything is gets uploaded directly to the site.</p> </blockquote> <img class="lazy" src="{% static 'main/img/karen.png' %}" class="img-fluid client"><br> <b>Karen Adams</b> <br><small>Owner, Restaurant Cafe</small><br> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16"> <path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16"> <path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16"> <path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16"> <path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star" viewBox="0 0 16 16"> <path d="M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.389 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.522-3.356c.33-.314.16-.888-.282-.95l-4.898-.696L8.465.792a.513.513 0 0 0-.927 0L5.354 5.12l-4.898.696c-.441.062-.612.636-.283.95l3.523 3.356-.83 4.73zm4.905-2.767-3.686 1.894.694-3.957a.565.565 0 0 0-.163-.505L1.71 6.745l4.052-.576a.525.525 0 0 0 .393-.288L8 2.223l1.847 3.658a.525.525 0 0 0 .393.288l4.052.575-2.906 2.77a.565.565 0 0 0-.163.506l.694 3.957-3.686-1.894a.503.503 0 0 0-.461 0z"/> </svg> </figure> </div> </div> </div> <!--End Testimonials--> <!--Second CTA--> <div class="container text-center p-lg-5 p-3"> <h1 class="cta my-5">Get started today</h1> <h5 class="text-muted lead my-4">Stay on top of your small business's finances. Jump right in.</h5> <form method="GET" action="/register"> <div class="form-floating mb-3 mx-auto" style="max-width: 40rem"> <input type="text" name="email" class="form-control" id="floatingTextInput1" placeholder="[email protected]"> <label for="floatingTextInput1">Enter email address...</label> <div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div> </div> <button class="btn btn-primary btn-lg my-4" type="submit">Join ➜</button> </form> </div> <!-- End Second CTA--> {% endblock %}
صفحه اصلی باید شامل یک فراخوان برای اقدام (CTA) در بالای صفحه باشد. به عبارت دیگر، CTA باید در بارگذاری صفحه بدون اسکرول کاربر ظاهر شود.
بعد از CTA گزاره ارزش قرار دارد. ممکن است چندین مورد وجود داشته باشد که مزایای نرم افزار را برجسته می کند. بعد ویژگی ها هستند. اینها نکات برجسته سریعی هستند که جنبه های مفید پلتفرم را به نمایش می گذارند.
بخش دیگری از صفحه اصلی باید توصیفات باشد. هدف برجسته کردن اعتماد موجود بین مشتری فعلی و نرم افزار است.
در نهایت یک CTA دیگر در پایین صفحه اضافه کنید. به این ترتیب کاربران مجبور نیستند فقط برای ثبت نام به بالای صفحه بروند.
یک صفحه ثبت نام ایجاد کنید
env > mysite > main > templates > main > (New File) login.html
{% extends 'main/header.html' %} {% block content %} {% load crispy_forms_tags %} {% load static %} <div class="container p-md-3 p-sm-2"> <div class="row g-0"> <div class="col d-none d-lg-block my-auto"> <img class="img-fluid" class="lazy" src="{% static 'main/img/sitting-reading.svg' %}" alt="sitting-reading"> </div> <div class="col my-auto"> <div class="mx-auto" style="max-width:30rem"> <h1 class="cta my-5 text-center">Register</h1> <form method="POST"> {% csrf_token %} {{ register_form|crispy }} <button class="btn btn-primary my-5" type="submit">Register</button> </form> </div> </div> </div> </div> {% endblock %}
یک الگوی ثبت نام با فرم ثبت نام اضافه شده به views.py در زیر است اضافه کنید.
برای تنظیم فرم های ثبت نام و ورود به سیستم مورد استفاده در این الگو و فرم زیر ، به راهنمای ثبت نام کاربر، ورود و خروج از سیستم جنگو بروید .
یک صفحه ورود ایجاد کنید
env > mysite > main > templates > main > (New File) login.html
{% extends 'main/header.html' %} {% block content %} {% load crispy_forms_tags %} {% load static %} <div class="container p-md-3 p-sm-2"> <div class="row g-0"> <div class="col d-none d-lg-block my-auto"> <img class="img-fluid" class="lazy" src="{% static 'main/img/reading-side.svg' %}" alt="reading-side"> </div> <div class="col my-auto"> <div class="mx-auto" style="max-width:30rem"> <h1 class="cta my-5 text-center">Login</h1> <form method="POST"> {% csrf_token %} {{ login_form|crispy }} <button class="btn btn-primary my-5" type="submit">Login</button> <p class="text-center">Don't have an account? <a href="/register">Create an account</a>.</p> </form> </div> </div> </div> </div> {% endblock %}
همچنین، یک الگوی ورود به سیستم با یک فرم ورود اضافه کنید. این دو قالب فرم جایی هستند که از Django Crispy Forms استفاده می شود.
Stripe را نصب کنید
خط فرمان ویندوز
(env)C:\Users\Owner\desktop\env>pip install --upgrade stripe
Stripe را نصب کنید.
یک حالت اشتراک ایجاد کنید
env > mysite > main > models.py
from django.db import models # Create your models here. class Subscription(models.Model): name = models.CharField(max_length=100) amount = models.DecimalField(decimal_places=2, max_digits=5) stripe_product_id = models.CharField(max_length=100) def __str__(self): return self.name
یک Subscription
مدل ایجاد کنید. این مدال را به admin.py اضافه کنید و یک superuser برای دسترسی به admin ایجاد کنید.
در ایجاد یک SuperUser جنگو و افزودن اشیاء مدل از طریق ادمین جنگو اقدام کنید.
به stripe_product_id
. توجه کنید ما این شناسه را در عرض یک ثانیه از Stripe دریافت خواهیم کرد.
به Stripe متصل شوید
به داشبورد Stipe خود وارد شوید و “View test data” را روشن کنید.
روی دکمه “+ Add product” در سمت راست بالای صفحه کلیک کنید و فرم اطلاعات محصول را پر کنید. ما سه محصول تکرارشونده اضافه خواهیم کرد: Basic، Pro، و Enterprise Plans.
اشیاء مدل اشتراک جنگو را ایجاد کنید
به ادمین وارد شوید و طرح اشتراک خود را علاوه بر شناسه منحصر به فرد Stipe API که در داشبورد Stripe پیدا میشود، با کلیک روی هر محصول اضافه کنید.
اگر به پیکربندی Django و Stripe برای همگام سازی خودکار علاقه دارید، دستورالعمل های dj-stripe را در استفاده از Django و Stripe برای اشتراک های ماهانه بررسی کنید.
یک صفحه قیمت ایجاد کنید
env > mysite > main > templates > main > (New File) pricing.html
{% extends 'main/header.html' %} {% block content %} {% load static %} <script class="lazy" src="https://polyfill.io/v3/polyfill.min.js?version=3.52.1&features=fetch"></script> <script class="lazy" src="https://js.stripe.com/v3/"></script> <!--Pricing--> <div class="container p-lg-5 p-3"> <h1 class="cta text-center">Pricing</span></h1> <h5 class="text-muted lead my-3 text-center">Don't run away from your finances. Jump right in.</h5> <div class="row p-lg-5 p-3"> {% for s in subscription %} <div class="col-lg-4 col-md-12 mb-4"> <div class="card h-100 shadow-lg text-center mx-auto sub-cards" style="max-width:20rem"> <div class="card-header sub-header"> <h4>{{s.name}}</h4> </div> <div class="card-body"> <h1>${{s.amount}}<span class="text-muted" style="font-weight: 300">/mo</span></h1> <p class="my-3"> {{s.description|safe|linebreaks}} </p> <div class="d-grid"> {% if user.is_authenticated %} <button class="btn btn-outline-primary btn-block btn-lg sub-button" onclick="selectPlan('{{s.stripe_api_id}}')">Select</button> {% else %} <a class="btn btn-outline-primary btn-block btn-lg sub-button" href="/register">Select</a> {% endif %} </div> </div> </div> </div> {% endfor %} </div> </div> <script type="text/javascript"> //grabs csrftoken let cookie = document.cookie let cookies = cookie.substring(cookie.indexOf('csrftoken=') + 10) let csrfToken = cookies.split(";")[0] // Create an instance of the Stripe object with your publishable API key var stripe = Stripe("pk_test"); //replace with your Stripe API key function selectPlan(pid) { fetch("/create-checkout-session", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRFToken": csrfToken, }, body: JSON.stringify({ product_id: pid }) }) .then(function (response) { return response.json(); }) .then(function (session) { return stripe.redirectToCheckout({ sessionId: session.id }); }) .then(function (result) { // If redirectToCheckout fails due to a browser or network // error, you should display the localized error message to your // customer using error.message. if (result.error) { alert(result.error.message); } }) .catch(function (error) { console.error("Error:", error); }); } </script> <!--End Pricing--> {% endblock %}
رمز CSRF را برای ارسال پست بگیرید، سپس کلید Stripe API خود را بارگیری کنید. و بعد selectPlan()
. را اضافه کنید این تابع شناسه منحصر به فرد API Stripe را برای هر محصول دریافت می کند.
/create-checkout-session
URL اضافه شده در مرحله بعد را واکشی کنید و به product_id
عنوان JSON پست کنید.
سپس پاسخ JSON و تغییر مسیر Stripe را به صفحه پرداخت Stripe برگردانید.
urls.py را به روز کنید
env > mysite > main > urls.py
from django.urls import path from . import views app_name = "main" urlpatterns = [ path("", views.homepage, name="homepage"), path("dashboard", views.dashboard, name="dashboard"), path("register", views.register_request, name="register"), path("login", views.login_request, name="login"), path("logout", views.logout_request, name= "logout"), path("create-checkout-session", views.create_checkout_session, name="create checkout session"), path('success', views.success_request, name="success"), path('cancel', views.cancel_request, name="cancel"), path('pricing', views.pricing, name="pricing"), ]
urls.py را با همه الگوهای URL لازم به روز کنید. ، create-checkout-session
و success_request
همه cancel_request
الگوهای URL برای اشتراک های ماهانه Stripe.
views.py را به روز کنید
env > mysite > main > views.py
from django.shortcuts import render, redirect from .forms import NewUserForm from django.contrib.auth import login, authenticate, logout from django.contrib import messages from django.contrib.auth.forms import AuthenticationForm from .models import Subscription from django.http import HttpResponse, JsonResponse import stripe import json # Create your views here. def homepage(request): return render(request = request, template_name="main/home.html") def pricing(request): subscription = Subscription.objects.all() return render(request = request, template_name="main/pricing.html", context={"subscription":subscription}) def dashboard(request): return render(request = request, template_name="main/dashboard.html") def register_request(request): if request.method == "POST": form = NewUserForm(request.POST) if form.is_valid(): user = form.save() login(request, user) messages.success(request, "Registration successful." ) return redirect("main:pricing") messages.error(request, "Unsuccessful registration. Invalid information.") email = request.GET.get("email") data = {"email":email} form = NewUserForm(initial=data) return render (request=request, template_name="main/register.html", context={"register_form":form}) def login_request(request): if request.method == "POST": form = AuthenticationForm(request, data=request.POST) if form.is_valid(): username = form.cleaned_data.get('username') password = form.cleaned_data.get('password') user = authenticate(username=username, password=password) if user is not None: login(request, user) return redirect("main:dashboard") else: messages.error(request,"Invalid username or password.") else: messages.error(request,"Invalid username or password.") form = AuthenticationForm() return render(request=request, template_name="main/login.html", context={"login_form":form}) def logout_request(request): logout(request) messages.info(request, "You have successfully logged out.") return redirect("main:homepage") def create_checkout_session(request): if request.method == "POST": YOUR_DOMAIN = 'http://127.0.0.1:8000' stripe.api_key = 'sk_test' #replace with your Stripe API key data = json.loads(request.body) customer = stripe.Customer.create( email=request.user.email) try: checkout_session = stripe.checkout.Session.create( payment_method_types=['card'], line_items=[ { 'price': data["product_id"], 'quantity': 1, } ], mode='subscription', success_url=YOUR_DOMAIN + '/success', cancel_url=YOUR_DOMAIN + '/cancel', customer_email = customer.email, ) return JsonResponse({'id': checkout_session.id}) except Exception as e: return JsonResponse({'error': (e.args[0])}, status =400) return JsonResponse({'error':'No GET request allowed.'}) def success_request(request): messages.info(request, "You have successfully subscribed.") return redirect("main:dashboard") def cancel_request(request): messages.info(request, "Payment Failed. Please try again.") return redirect("main:pricing")
بیایید روی سه عملکرد آخر تمرکز کنیم. create_checkout_session()
بارگذاری در دادههای JSON از صفحه قیمتگذاری، یک مشتری خطی ایجاد میکند، سپس سعی میکند جلسه پرداخت Stripe را ایجاد کند.
payment_method_types
, the را line_items
از دادههای JSON, the mode
, success_url
, cancel_url
و اعلان کنید customer_email
. در آخر، شناسه جلسه پرداخت را برگردانید.
در صورت موفقیت آمیز بودن اشتراک، درخواست success_request()
کنید و اگر جلسه پرداخت لغو شد، request cancel_request()
.
نتیجه گیری
این آموزش هم به پایان رسید مانند دیگر آموزشی های پایتونی ها و اکنون شما یک الگوی پایه SaaS دارید که می توانید آن را تکرار کرده و MVP جدید خود را ایجاد کنید.