"
108 | )
109 | # https://docs.djangoproject.com/en/dev/ref/settings/#server-email
110 | SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
111 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix
112 | EMAIL_SUBJECT_PREFIX = env(
113 | "DJANGO_EMAIL_SUBJECT_PREFIX",
114 | default="[Django React PayPal]",
115 | )
116 |
117 | # ADMIN
118 | # ------------------------------------------------------------------------------
119 | # Django Admin URL regex.
120 | ADMIN_URL = env("DJANGO_ADMIN_URL")
121 |
122 | # Anymail
123 | # ------------------------------------------------------------------------------
124 | # https://anymail.readthedocs.io/en/stable/installation/#installing-anymail
125 | INSTALLED_APPS += ["anymail"] # noqa F405
126 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
127 | # https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference
128 | # https://anymail.readthedocs.io/en/stable/esps/mailgun/
129 | EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
130 | ANYMAIL = {
131 | "MAILGUN_API_KEY": env("MAILGUN_API_KEY"),
132 | "MAILGUN_SENDER_DOMAIN": env("MAILGUN_DOMAIN"),
133 | "MAILGUN_API_URL": env("MAILGUN_API_URL", default="https://api.mailgun.net/v3"),
134 | }
135 |
136 | # Collectfast
137 | # ------------------------------------------------------------------------------
138 | # https://github.com/antonagestam/collectfast#installation
139 | INSTALLED_APPS = ["collectfast"] + INSTALLED_APPS # noqa F405
140 |
141 | # LOGGING
142 | # ------------------------------------------------------------------------------
143 | # https://docs.djangoproject.com/en/dev/ref/settings/#logging
144 | # See https://docs.djangoproject.com/en/dev/topics/logging for
145 | # more details on how to customize your logging configuration.
146 | # A sample logging configuration. The only tangible logging
147 | # performed by this configuration is to send an email to
148 | # the site admins on every HTTP 500 error when DEBUG=False.
149 | LOGGING = {
150 | "version": 1,
151 | "disable_existing_loggers": False,
152 | "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
153 | "formatters": {
154 | "verbose": {
155 | "format": "%(levelname)s %(asctime)s %(module)s "
156 | "%(process)d %(thread)d %(message)s"
157 | }
158 | },
159 | "handlers": {
160 | "mail_admins": {
161 | "level": "ERROR",
162 | "filters": ["require_debug_false"],
163 | "class": "django.utils.log.AdminEmailHandler",
164 | },
165 | "console": {
166 | "level": "DEBUG",
167 | "class": "logging.StreamHandler",
168 | "formatter": "verbose",
169 | },
170 | },
171 | "root": {"level": "INFO", "handlers": ["console"]},
172 | "loggers": {
173 | "django.request": {
174 | "handlers": ["mail_admins"],
175 | "level": "ERROR",
176 | "propagate": True,
177 | },
178 | "django.security.DisallowedHost": {
179 | "level": "ERROR",
180 | "handlers": ["console", "mail_admins"],
181 | "propagate": True,
182 | },
183 | },
184 | }
185 |
186 | # Your stuff...
187 | # ------------------------------------------------------------------------------
188 |
189 | PAYPAL_MODE = "live"
--------------------------------------------------------------------------------
/config/settings/test.py:
--------------------------------------------------------------------------------
1 | """
2 | With these settings, tests run faster.
3 | """
4 |
5 | from .base import * # noqa
6 | from .base import env
7 |
8 | # GENERAL
9 | # ------------------------------------------------------------------------------
10 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
11 | SECRET_KEY = env(
12 | "DJANGO_SECRET_KEY",
13 | default="DJji1cuTAlod6cBW98Ed7p2KTO2KjULrA37LaxQAkjlqt6ROarQ8FaJ8DalClmwX",
14 | )
15 | # https://docs.djangoproject.com/en/dev/ref/settings/#test-runner
16 | TEST_RUNNER = "django.test.runner.DiscoverRunner"
17 |
18 | # PASSWORDS
19 | # ------------------------------------------------------------------------------
20 | # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
21 | PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
22 |
23 | # TEMPLATES
24 | # ------------------------------------------------------------------------------
25 | TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405
26 | (
27 | "django.template.loaders.cached.Loader",
28 | [
29 | "django.template.loaders.filesystem.Loader",
30 | "django.template.loaders.app_directories.Loader",
31 | ],
32 | )
33 | ]
34 |
35 | # EMAIL
36 | # ------------------------------------------------------------------------------
37 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
38 | EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
39 |
40 | # Your stuff...
41 | # ------------------------------------------------------------------------------
42 |
--------------------------------------------------------------------------------
/config/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.conf.urls.static import static
3 | from django.contrib import admin
4 | from django.urls import include, path
5 | from django.views import defaults as default_views
6 | from django.views.generic import TemplateView
7 | from rest_framework.authtoken.views import obtain_auth_token
8 | from django_react_paypal.payments.views import ProcessWebhookView
9 |
10 | urlpatterns = [
11 | path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
12 | path(
13 | "about/", TemplateView.as_view(template_name="pages/about.html"), name="about"
14 | ),
15 | # Django Admin, use {% url 'admin:index' %}
16 | path(settings.ADMIN_URL, admin.site.urls),
17 | # User management
18 | path("users/", include("django_react_paypal.users.urls", namespace="users")),
19 | path("accounts/", include("allauth.urls")),
20 | path("webhooks/paypal/", ProcessWebhookView.as_view())
21 |
22 | # Your stuff: custom urls includes go here
23 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
24 |
25 | # API URLS
26 | urlpatterns += [
27 | # API base url
28 | path("api/", include("config.api_router")),
29 | # DRF auth token
30 | path("auth-token/", obtain_auth_token),
31 | ]
32 |
33 | if settings.DEBUG:
34 | # This allows the error pages to be debugged during development, just visit
35 | # these url in browser to see how these error pages look like.
36 | urlpatterns += [
37 | path(
38 | "400/",
39 | default_views.bad_request,
40 | kwargs={"exception": Exception("Bad Request!")},
41 | ),
42 | path(
43 | "403/",
44 | default_views.permission_denied,
45 | kwargs={"exception": Exception("Permission Denied")},
46 | ),
47 | path(
48 | "404/",
49 | default_views.page_not_found,
50 | kwargs={"exception": Exception("Page not Found")},
51 | ),
52 | path("500/", default_views.server_error),
53 | ]
54 | if "debug_toolbar" in settings.INSTALLED_APPS:
55 | import debug_toolbar
56 |
57 | urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns
58 |
--------------------------------------------------------------------------------
/config/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for Django React PayPal project.
3 |
4 | This module contains the WSGI application used by Django's development server
5 | and any production WSGI deployments. It should expose a module-level variable
6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
7 | this application via the ``WSGI_APPLICATION`` setting.
8 |
9 | Usually you will have the standard Django WSGI application here, but it also
10 | might make sense to replace the whole Django WSGI application with a custom one
11 | that later delegates to the Django one. For example, you could introduce WSGI
12 | middleware here, or combine a Django application with an application of another
13 | framework.
14 |
15 | """
16 | import os
17 | import sys
18 | from pathlib import Path
19 |
20 | from django.core.wsgi import get_wsgi_application
21 |
22 | # This allows easy placement of apps within the interior
23 | # django_react_paypal directory.
24 | ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent
25 | sys.path.append(str(ROOT_DIR / "django_react_paypal"))
26 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
27 | # if running multiple sites in the same mod_wsgi process. To fix this, use
28 | # mod_wsgi daemon mode with each site in its own daemon process, or use
29 | # os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production"
30 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
31 |
32 | # This application object is used by any WSGI server configured to use this
33 | # file. This includes Django's development server, if the WSGI_APPLICATION
34 | # setting points here.
35 | application = get_wsgi_application()
36 | # Apply WSGI middleware here.
37 | # from helloworld.wsgi import HelloWorldApplication
38 | # application = HelloWorldApplication(application)
39 |
--------------------------------------------------------------------------------
/django_react_paypal/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.1.0"
2 | __version_info__ = tuple(
3 | [
4 | int(num) if num.isdigit() else num
5 | for num in __version__.replace("-", ".", 1).split(".")
6 | ]
7 | )
8 |
--------------------------------------------------------------------------------
/django_react_paypal/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from django_react_paypal.users.models import User
4 | from django_react_paypal.users.tests.factories import UserFactory
5 |
6 |
7 | @pytest.fixture(autouse=True)
8 | def media_storage(settings, tmpdir):
9 | settings.MEDIA_ROOT = tmpdir.strpath
10 |
11 |
12 | @pytest.fixture
13 | def user() -> User:
14 | return UserFactory()
15 |
--------------------------------------------------------------------------------
/django_react_paypal/contrib/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 |
--------------------------------------------------------------------------------
/django_react_paypal/contrib/sites/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 |
--------------------------------------------------------------------------------
/django_react_paypal/contrib/sites/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | import django.contrib.sites.models
2 | from django.contrib.sites.models import _simple_domain_name_validator
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = []
9 |
10 | operations = [
11 | migrations.CreateModel(
12 | name="Site",
13 | fields=[
14 | (
15 | "id",
16 | models.AutoField(
17 | verbose_name="ID",
18 | serialize=False,
19 | auto_created=True,
20 | primary_key=True,
21 | ),
22 | ),
23 | (
24 | "domain",
25 | models.CharField(
26 | max_length=100,
27 | verbose_name="domain name",
28 | validators=[_simple_domain_name_validator],
29 | ),
30 | ),
31 | ("name", models.CharField(max_length=50, verbose_name="display name")),
32 | ],
33 | options={
34 | "ordering": ("domain",),
35 | "db_table": "django_site",
36 | "verbose_name": "site",
37 | "verbose_name_plural": "sites",
38 | },
39 | bases=(models.Model,),
40 | managers=[("objects", django.contrib.sites.models.SiteManager())],
41 | )
42 | ]
43 |
--------------------------------------------------------------------------------
/django_react_paypal/contrib/sites/migrations/0002_alter_domain_unique.py:
--------------------------------------------------------------------------------
1 | import django.contrib.sites.models
2 | from django.db import migrations, models
3 |
4 |
5 | class Migration(migrations.Migration):
6 |
7 | dependencies = [("sites", "0001_initial")]
8 |
9 | operations = [
10 | migrations.AlterField(
11 | model_name="site",
12 | name="domain",
13 | field=models.CharField(
14 | max_length=100,
15 | unique=True,
16 | validators=[django.contrib.sites.models._simple_domain_name_validator],
17 | verbose_name="domain name",
18 | ),
19 | )
20 | ]
21 |
--------------------------------------------------------------------------------
/django_react_paypal/contrib/sites/migrations/0003_set_site_domain_and_name.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 | from django.conf import settings
7 | from django.db import migrations
8 |
9 |
10 | def update_site_forward(apps, schema_editor):
11 | """Set site domain and name."""
12 | Site = apps.get_model("sites", "Site")
13 | Site.objects.update_or_create(
14 | id=settings.SITE_ID,
15 | defaults={
16 | "domain": "example.com",
17 | "name": "Django React PayPal",
18 | },
19 | )
20 |
21 |
22 | def update_site_backward(apps, schema_editor):
23 | """Revert site domain and name to default."""
24 | Site = apps.get_model("sites", "Site")
25 | Site.objects.update_or_create(
26 | id=settings.SITE_ID, defaults={"domain": "example.com", "name": "example.com"}
27 | )
28 |
29 |
30 | class Migration(migrations.Migration):
31 |
32 | dependencies = [("sites", "0002_alter_domain_unique")]
33 |
34 | operations = [migrations.RunPython(update_site_forward, update_site_backward)]
35 |
--------------------------------------------------------------------------------
/django_react_paypal/contrib/sites/migrations/0004_alter_options_ordering_domain.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.7 on 2021-02-04 14:49
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('sites', '0003_set_site_domain_and_name'),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterModelOptions(
14 | name='site',
15 | options={'ordering': ['domain'], 'verbose_name': 'site', 'verbose_name_plural': 'sites'},
16 | ),
17 | ]
18 |
--------------------------------------------------------------------------------
/django_react_paypal/contrib/sites/migrations/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/compose/local/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14-alpine AS development
2 | ENV NODE_ENV development
3 |
4 | WORKDIR /app
5 |
6 | # Cache and Install dependencies - very important step
7 | COPY package.json .
8 | COPY package-lock.json .
9 |
10 | RUN npm install
11 |
12 | COPY . .
13 |
14 | EXPOSE 3000
15 |
16 | CMD [ "npm", "run", "start" ]
--------------------------------------------------------------------------------
/django_react_paypal/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@paypal/react-paypal-js": "^7.2.1",
7 | "@testing-library/jest-dom": "^5.14.1",
8 | "@testing-library/react": "^11.2.7",
9 | "@testing-library/user-event": "^12.8.3",
10 | "@types/jest": "^26.0.24",
11 | "@types/node": "^12.20.18",
12 | "@types/react": "^17.0.15",
13 | "@types/react-dom": "^17.0.9",
14 | "react": "^17.0.2",
15 | "react-dom": "^17.0.2",
16 | "react-hot-toast": "^2.1.0",
17 | "react-scripts": "4.0.3",
18 | "typescript": "^4.3.5",
19 | "web-vitals": "^1.1.2"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": [
29 | "react-app",
30 | "react-app/jest"
31 | ]
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/django_react_paypal/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
25 |
27 | React App
28 |
29 |
30 |
31 | You need to enable JavaScript to run this app.
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/frontend/public/logo192.png
--------------------------------------------------------------------------------
/django_react_paypal/frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/frontend/public/logo512.png
--------------------------------------------------------------------------------
/django_react_paypal/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, screen } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | render( );
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { PayPalScriptProvider } from "@paypal/react-paypal-js";
2 | import { Toaster } from "react-hot-toast";
3 | import { Payment } from './components/Payment'
4 |
5 | export default function App() {
6 | return (
7 |
8 |
9 |
10 |
11 | );
12 | }
--------------------------------------------------------------------------------
/django_react_paypal/frontend/src/components/Payment.tsx:
--------------------------------------------------------------------------------
1 | import { PayPalButtons } from "@paypal/react-paypal-js";
2 | import toast from "react-hot-toast";
3 |
4 |
5 | export function Payment() {
6 |
7 | return (
8 |
9 |
10 |
11 |
Airtable Product
12 |
$10.00
13 |
Some information about the product
14 |
{
17 | return actions.order.create({
18 | purchase_units: [
19 | {
20 | amount: {
21 | value: "10.00"
22 | },
23 | custom_id: "e-book-1234" // the name or slug of the thing you're selling
24 | },
25 | ],
26 | });
27 | }}
28 | onApprove={(data, actions) => {
29 | return actions.order.capture().then(function (details) {
30 | toast.success('Payment completed. Thank you, ' + details.payer.name.given_name)
31 | });
32 | }}
33 | onCancel={() => toast(
34 | "You cancelled the payment. Try again by clicking the PayPal button", {
35 | duration: 6000,
36 | })}
37 | onError={(err) => {
38 | toast.error(
39 | "There was an error processing your payment. If this error please contact support.", {
40 | duration: 6000,
41 | });
42 | }}
43 | />
44 |
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #fdf1ec;
3 | }
4 |
5 | h1, img {
6 | margin: 0;
7 | padding: 0;
8 | }
9 |
10 | .card {
11 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
12 | max-width: 300px;
13 | margin: auto;
14 | text-align: center;
15 | font-family: arial;
16 | }
17 |
18 | .card h1, p {
19 | color: #474747;
20 | }
21 |
22 | .card-details {
23 | background-color: #ffffff;
24 | padding-bottom: 10px;
25 | padding-left: 5px;
26 | padding-right: 5px;
27 | }
28 |
29 | .price {
30 | color: grey;
31 | font-size: 22px;
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare namespace NodeJS {
4 | export interface ProcessEnv {
5 | REACT_APP_PAYPAL_CLIENT_ID: string;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/django_react_paypal/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/django_react_paypal/payments/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/payments/__init__.py
--------------------------------------------------------------------------------
/django_react_paypal/payments/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | # Register your models here.
4 |
--------------------------------------------------------------------------------
/django_react_paypal/payments/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class PaymentsConfig(AppConfig):
5 | name = "django_react_paypal.payments"
6 |
--------------------------------------------------------------------------------
/django_react_paypal/payments/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/payments/migrations/__init__.py
--------------------------------------------------------------------------------
/django_react_paypal/payments/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
--------------------------------------------------------------------------------
/django_react_paypal/payments/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/django_react_paypal/payments/views.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from django.conf import settings
4 | from django.core.mail import send_mail
5 | from django.http import HttpResponse, HttpResponseBadRequest
6 | from django.utils.decorators import method_decorator
7 | from django.views.decorators.csrf import csrf_exempt
8 | from django.views.generic import View
9 |
10 | from paypalrestsdk import notifications
11 |
12 |
13 | @method_decorator(csrf_exempt, name="dispatch")
14 | class ProcessWebhookView(View):
15 | def post(self, request):
16 | if "HTTP_PAYPAL_TRANSMISSION_ID" not in request.META:
17 | return HttpResponseBadRequest()
18 |
19 | auth_algo = request.META['HTTP_PAYPAL_AUTH_ALGO']
20 | cert_url = request.META['HTTP_PAYPAL_CERT_URL']
21 | transmission_id = request.META['HTTP_PAYPAL_TRANSMISSION_ID']
22 | transmission_sig = request.META['HTTP_PAYPAL_TRANSMISSION_SIG']
23 | transmission_time = request.META['HTTP_PAYPAL_TRANSMISSION_TIME']
24 | webhook_id = settings.PAYPAL_WEBHOOK_ID
25 | event_body = request.body.decode(request.encoding or "utf-8")
26 |
27 | valid = notifications.WebhookEvent.verify(
28 | transmission_id=transmission_id,
29 | timestamp=transmission_time,
30 | webhook_id=webhook_id,
31 | event_body=event_body,
32 | cert_url=cert_url,
33 | actual_sig=transmission_sig,
34 | auth_algo=auth_algo,
35 | )
36 |
37 | if not valid:
38 | return HttpResponseBadRequest()
39 |
40 | webhook_event = json.loads(event_body)
41 |
42 | event_type = webhook_event["event_type"]
43 |
44 | CHECKOUT_ORDER_APPROVED = "CHECKOUT.ORDER.APPROVED"
45 |
46 | if event_type == CHECKOUT_ORDER_APPROVED:
47 | customer_email = webhook_event["resource"]["payer"]["email_address"]
48 | product_link = "https://learn.justdjango.com"
49 | send_mail(
50 | subject="Your access",
51 | message=f"Thank you for purchasing my product. Here is the link: {product_link}",
52 | from_email="your@email.com",
53 | recipient_list=[customer_email]
54 | )
55 |
56 | # Accessing purchased items when selling multiple products
57 | # webhook_event["resource"]["purchase_units"][0]["custom_id"] # 'e-book-1234'
58 |
59 | return HttpResponse()
60 |
--------------------------------------------------------------------------------
/django_react_paypal/static/css/project.css:
--------------------------------------------------------------------------------
1 | /* These styles are generated from project.scss. */
2 |
3 | .alert-debug {
4 | color: black;
5 | background-color: white;
6 | border-color: #d6e9c6;
7 | }
8 |
9 | .alert-error {
10 | color: #b94a48;
11 | background-color: #f2dede;
12 | border-color: #eed3d7;
13 | }
14 |
--------------------------------------------------------------------------------
/django_react_paypal/static/fonts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/static/fonts/.gitkeep
--------------------------------------------------------------------------------
/django_react_paypal/static/images/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/static/images/favicons/favicon.ico
--------------------------------------------------------------------------------
/django_react_paypal/static/js/project.js:
--------------------------------------------------------------------------------
1 | /* Project specific Javascript goes here. */
2 |
--------------------------------------------------------------------------------
/django_react_paypal/static/sass/custom_bootstrap_vars.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/static/sass/custom_bootstrap_vars.scss
--------------------------------------------------------------------------------
/django_react_paypal/static/sass/project.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | // project specific CSS goes here
6 |
7 | ////////////////////////////////
8 | //Variables//
9 | ////////////////////////////////
10 |
11 | // Alert colors
12 |
13 | $white: #fff;
14 | $mint-green: #d6e9c6;
15 | $black: #000;
16 | $pink: #f2dede;
17 | $dark-pink: #eed3d7;
18 | $red: #b94a48;
19 |
20 | ////////////////////////////////
21 | //Alerts//
22 | ////////////////////////////////
23 |
24 | // bootstrap alert CSS, translated to the django-standard levels of
25 | // debug, info, success, warning, error
26 |
27 | .alert-debug {
28 | background-color: $white;
29 | border-color: $mint-green;
30 | color: $black;
31 | }
32 |
33 | .alert-error {
34 | background-color: $pink;
35 | border-color: $dark-pink;
36 | color: $red;
37 | }
38 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/403.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}Forbidden (403){% endblock %}
4 |
5 | {% block content %}
6 | Forbidden (403)
7 |
8 | {% if exception %}{{ exception }}{% else %}You're not allowed to access this page.{% endif %}
9 | {% endblock content %}
10 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/404.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}Page not found{% endblock %}
4 |
5 | {% block content %}
6 | Page not found
7 |
8 | {% if exception %}{{ exception }}{% else %}This is not the page you were looking for.{% endif %}
9 | {% endblock content %}
10 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/500.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block title %}Server Error{% endblock %}
4 |
5 | {% block content %}
6 | Ooops!!! 500
7 |
8 | Looks like something went wrong!
9 |
10 | We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.
11 | {% endblock content %}
12 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/account_inactive.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% translate "Account Inactive" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% translate "Account Inactive" %}
9 |
10 | {% translate "This account is inactive." %}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/base.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %}
3 |
4 | {% block content %}
5 |
6 |
7 | {% block inner %}{% endblock %}
8 |
9 |
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/email.html:
--------------------------------------------------------------------------------
1 |
2 | {% extends "account/base.html" %}
3 |
4 | {% load i18n %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}{% translate "Account" %}{% endblock %}
8 |
9 | {% block inner %}
10 | {% translate "E-mail Addresses" %}
11 |
12 | {% if user.emailaddress_set.all %}
13 | {% translate 'The following e-mail addresses are associated with your account:' %}
14 |
15 |
44 |
45 | {% else %}
46 | {% translate 'Warning:'%} {% translate "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}
47 |
48 | {% endif %}
49 |
50 |
51 | {% translate "Add E-mail Address" %}
52 |
53 |
58 |
59 | {% endblock %}
60 |
61 |
62 | {% block inline_javascript %}
63 | {{ block.super }}
64 |
81 | {% endblock %}
82 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/email_confirm.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account %}
5 |
6 | {% block head_title %}{% translate "Confirm E-mail Address" %}{% endblock %}
7 |
8 |
9 | {% block inner %}
10 | {% translate "Confirm E-mail Address" %}
11 |
12 | {% if confirmation %}
13 |
14 | {% user_display confirmation.email_address.user as user_display %}
15 |
16 | {% blocktranslate with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktranslate %}
17 |
18 |
22 |
23 | {% else %}
24 |
25 | {% url 'account_email' as email_url %}
26 |
27 | {% blocktranslate %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request .{% endblocktranslate %}
28 |
29 | {% endif %}
30 |
31 | {% endblock %}
32 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/login.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account socialaccount %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}{% translate "Sign In" %}{% endblock %}
8 |
9 | {% block inner %}
10 |
11 | {% translate "Sign In" %}
12 |
13 | {% get_providers as socialaccount_providers %}
14 |
15 | {% if socialaccount_providers %}
16 | {% blocktranslate with site.name as site_name %}Please sign in with one
17 | of your existing third party accounts. Or, sign up
18 | for a {{ site_name }} account and sign in below:{% endblocktranslate %}
19 |
20 |
21 |
22 |
23 | {% include "socialaccount/snippets/provider_list.html" with process="login" %}
24 |
25 |
26 |
{% translate 'or' %}
27 |
28 |
29 |
30 | {% include "socialaccount/snippets/login_extra.html" %}
31 |
32 | {% else %}
33 | {% blocktranslate %}If you have not created an account yet, then please
34 | sign up first.{% endblocktranslate %}
35 | {% endif %}
36 |
37 |
46 |
47 | {% endblock %}
48 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/logout.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% translate "Sign Out" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% translate "Sign Out" %}
9 |
10 | {% translate 'Are you sure you want to sign out?' %}
11 |
12 |
19 | {% endblock %}
20 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/password_change.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 |
6 | {% block head_title %}{% translate "Change Password" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% translate "Change Password" %}
10 |
11 |
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/password_reset.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}{% translate "Password Reset" %}{% endblock %}
8 |
9 | {% block inner %}
10 |
11 | {% translate "Password Reset" %}
12 | {% if user.is_authenticated %}
13 | {% include "account/snippets/already_logged_in.html" %}
14 | {% endif %}
15 |
16 | {% translate "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}
17 |
18 |
23 |
24 | {% blocktranslate %}Please contact us if you have any trouble resetting your password.{% endblocktranslate %}
25 | {% endblock %}
26 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/password_reset_done.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account %}
5 |
6 | {% block head_title %}{% translate "Password Reset" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% translate "Password Reset" %}
10 |
11 | {% if user.is_authenticated %}
12 | {% include "account/snippets/already_logged_in.html" %}
13 | {% endif %}
14 |
15 | {% blocktranslate %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %}
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/password_reset_from_key.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 | {% block head_title %}{% translate "Change Password" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% if token_fail %}{% translate "Bad Token" %}{% else %}{% translate "Change Password" %}{% endif %}
9 |
10 | {% if token_fail %}
11 | {% url 'account_reset_password' as passwd_reset_url %}
12 | {% blocktranslate %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset .{% endblocktranslate %}
13 | {% else %}
14 | {% if form %}
15 |
20 | {% else %}
21 | {% translate 'Your password is now changed.' %}
22 | {% endif %}
23 | {% endif %}
24 | {% endblock %}
25 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/password_reset_from_key_done.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% block head_title %}{% translate "Change Password" %}{% endblock %}
5 |
6 | {% block inner %}
7 | {% translate "Change Password" %}
8 | {% translate 'Your password is now changed.' %}
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/password_set.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 |
6 | {% block head_title %}{% translate "Set Password" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% translate "Set Password" %}
10 |
11 |
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/signup.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 |
6 | {% block head_title %}{% translate "Signup" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% translate "Sign Up" %}
10 |
11 | {% blocktranslate %}Already have an account? Then please sign in .{% endblocktranslate %}
12 |
13 |
21 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/signup_closed.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% translate "Sign Up Closed" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% translate "Sign Up Closed" %}
9 |
10 | {% translate "We are sorry, but the sign up is currently closed." %}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/verification_sent.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% translate "Verify Your E-mail Address" %}
9 |
10 | {% blocktranslate %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %}
11 |
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/account/verified_email_required.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% translate "Verify Your E-mail Address" %}
9 |
10 | {% url 'account_email' as email_url %}
11 |
12 | {% blocktranslate %}This part of the site requires us to verify that
13 | you are who you claim to be. For this purpose, we require that you
14 | verify ownership of your e-mail address. {% endblocktranslate %}
15 |
16 | {% blocktranslate %}We have sent an e-mail to you for
17 | verification. Please click on the link inside this e-mail. Please
18 | contact us if you do not receive it within a few minutes.{% endblocktranslate %}
19 |
20 | {% blocktranslate %}Note: you can still change your e-mail address .{% endblocktranslate %}
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load static i18n %}
2 |
3 |
4 |
5 |
6 | {% block title %}Django React PayPal{% endblock title %}
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 | {% block css %}
19 |
20 |
21 |
22 |
23 |
24 |
25 | {% endblock %}
26 |
28 | {# Placed at the top of the document so pages load faster with defer #}
29 | {% block javascript %}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {% endblock javascript %}
41 |
42 |
43 |
44 |
45 |
46 |
85 |
86 |
87 |
88 | {% if messages %}
89 | {% for message in messages %}
90 |
{{ message }}×
91 | {% endfor %}
92 | {% endif %}
93 |
94 | {% block content %}
95 |
Use this document as a way to quick start any new project.
96 | {% endblock content %}
97 |
98 |
99 |
100 | {% block modal %}{% endblock modal %}
101 |
102 | {% block inline_javascript %}
103 | {# Script tags with only code, no src (defer by default) #}
104 | {% endblock inline_javascript %}
105 |
106 |
107 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/pages/about.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/pages/home.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/users/user_detail.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load static %}
3 |
4 | {% block title %}User: {{ object.username }}{% endblock %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
{{ object.username }}
13 | {% if object.name %}
14 |
{{ object.name }}
15 | {% endif %}
16 |
17 |
18 |
19 | {% if object == request.user %}
20 |
21 |
30 |
31 | {% endif %}
32 |
33 |
34 | {% endblock content %}
35 |
--------------------------------------------------------------------------------
/django_react_paypal/templates/users/user_form.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load crispy_forms_tags %}
3 |
4 | {% block title %}{{ user.username }}{% endblock %}
5 |
6 | {% block content %}
7 | {{ user.username }}
8 |
17 | {% endblock %}
18 |
--------------------------------------------------------------------------------
/django_react_paypal/users/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/users/__init__.py
--------------------------------------------------------------------------------
/django_react_paypal/users/adapters.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 | from allauth.account.adapter import DefaultAccountAdapter
4 | from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
5 | from django.conf import settings
6 | from django.http import HttpRequest
7 |
8 |
9 | class AccountAdapter(DefaultAccountAdapter):
10 | def is_open_for_signup(self, request: HttpRequest):
11 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
12 |
13 |
14 | class SocialAccountAdapter(DefaultSocialAccountAdapter):
15 | def is_open_for_signup(self, request: HttpRequest, sociallogin: Any):
16 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
17 |
--------------------------------------------------------------------------------
/django_react_paypal/users/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.contrib.auth import admin as auth_admin
3 | from django.contrib.auth import get_user_model
4 | from django.utils.translation import gettext_lazy as _
5 |
6 | from django_react_paypal.users.forms import UserChangeForm, UserCreationForm
7 |
8 | User = get_user_model()
9 |
10 |
11 | @admin.register(User)
12 | class UserAdmin(auth_admin.UserAdmin):
13 |
14 | form = UserChangeForm
15 | add_form = UserCreationForm
16 | fieldsets = (
17 | (None, {"fields": ("username", "password")}),
18 | (_("Personal info"), {"fields": ("name", "email")}),
19 | (
20 | _("Permissions"),
21 | {
22 | "fields": (
23 | "is_active",
24 | "is_staff",
25 | "is_superuser",
26 | "groups",
27 | "user_permissions",
28 | ),
29 | },
30 | ),
31 | (_("Important dates"), {"fields": ("last_login", "date_joined")}),
32 | )
33 | list_display = ["username", "name", "is_superuser"]
34 | search_fields = ["name"]
35 |
--------------------------------------------------------------------------------
/django_react_paypal/users/api/serializers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from rest_framework import serializers
3 |
4 | User = get_user_model()
5 |
6 |
7 | class UserSerializer(serializers.ModelSerializer):
8 | class Meta:
9 | model = User
10 | fields = ["username", "name", "url"]
11 |
12 | extra_kwargs = {
13 | "url": {"view_name": "api:user-detail", "lookup_field": "username"}
14 | }
15 |
--------------------------------------------------------------------------------
/django_react_paypal/users/api/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from rest_framework import status
3 | from rest_framework.decorators import action
4 | from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
5 | from rest_framework.response import Response
6 | from rest_framework.viewsets import GenericViewSet
7 |
8 | from .serializers import UserSerializer
9 |
10 | User = get_user_model()
11 |
12 |
13 | class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet):
14 | serializer_class = UserSerializer
15 | queryset = User.objects.all()
16 | lookup_field = "username"
17 |
18 | def get_queryset(self, *args, **kwargs):
19 | return self.queryset.filter(id=self.request.user.id)
20 |
21 | @action(detail=False, methods=["GET"])
22 | def me(self, request):
23 | serializer = UserSerializer(request.user, context={"request": request})
24 | return Response(status=status.HTTP_200_OK, data=serializer.data)
25 |
--------------------------------------------------------------------------------
/django_react_paypal/users/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 | from django.utils.translation import gettext_lazy as _
3 |
4 |
5 | class UsersConfig(AppConfig):
6 | name = "django_react_paypal.users"
7 | verbose_name = _("Users")
8 |
9 | def ready(self):
10 | try:
11 | import django_react_paypal.users.signals # noqa F401
12 | except ImportError:
13 | pass
14 |
--------------------------------------------------------------------------------
/django_react_paypal/users/forms.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import forms as admin_forms
2 | from django.contrib.auth import get_user_model
3 | from django.utils.translation import gettext_lazy as _
4 |
5 | User = get_user_model()
6 |
7 |
8 | class UserChangeForm(admin_forms.UserChangeForm):
9 | class Meta(admin_forms.UserChangeForm.Meta):
10 | model = User
11 |
12 |
13 | class UserCreationForm(admin_forms.UserCreationForm):
14 | class Meta(admin_forms.UserCreationForm.Meta):
15 | model = User
16 |
17 | error_messages = {
18 | "username": {"unique": _("This username has already been taken.")}
19 | }
20 |
--------------------------------------------------------------------------------
/django_react_paypal/users/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | import django.contrib.auth.models
2 | import django.contrib.auth.validators
3 | from django.db import migrations, models
4 | import django.utils.timezone
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [("auth", "0008_alter_user_username_max_length")]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name="User",
16 | fields=[
17 | (
18 | "id",
19 | models.AutoField(
20 | auto_created=True,
21 | primary_key=True,
22 | serialize=False,
23 | verbose_name="ID",
24 | ),
25 | ),
26 | ("password", models.CharField(max_length=128, verbose_name="password")),
27 | (
28 | "last_login",
29 | models.DateTimeField(
30 | blank=True, null=True, verbose_name="last login"
31 | ),
32 | ),
33 | (
34 | "is_superuser",
35 | models.BooleanField(
36 | default=False,
37 | help_text="Designates that this user has all permissions without explicitly assigning them.",
38 | verbose_name="superuser status",
39 | ),
40 | ),
41 | (
42 | "username",
43 | models.CharField(
44 | error_messages={
45 | "unique": "A user with that username already exists."
46 | },
47 | help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
48 | max_length=150,
49 | unique=True,
50 | validators=[
51 | django.contrib.auth.validators.UnicodeUsernameValidator()
52 | ],
53 | verbose_name="username",
54 | ),
55 | ),
56 | (
57 | "email",
58 | models.EmailField(
59 | blank=True, max_length=254, verbose_name="email address"
60 | ),
61 | ),
62 | (
63 | "is_staff",
64 | models.BooleanField(
65 | default=False,
66 | help_text="Designates whether the user can log into this admin site.",
67 | verbose_name="staff status",
68 | ),
69 | ),
70 | (
71 | "is_active",
72 | models.BooleanField(
73 | default=True,
74 | help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
75 | verbose_name="active",
76 | ),
77 | ),
78 | (
79 | "date_joined",
80 | models.DateTimeField(
81 | default=django.utils.timezone.now, verbose_name="date joined"
82 | ),
83 | ),
84 | (
85 | "name",
86 | models.CharField(
87 | blank=True, max_length=255, verbose_name="Name of User"
88 | ),
89 | ),
90 | (
91 | "groups",
92 | models.ManyToManyField(
93 | blank=True,
94 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
95 | related_name="user_set",
96 | related_query_name="user",
97 | to="auth.Group",
98 | verbose_name="groups",
99 | ),
100 | ),
101 | (
102 | "user_permissions",
103 | models.ManyToManyField(
104 | blank=True,
105 | help_text="Specific permissions for this user.",
106 | related_name="user_set",
107 | related_query_name="user",
108 | to="auth.Permission",
109 | verbose_name="user permissions",
110 | ),
111 | ),
112 | ],
113 | options={
114 | "verbose_name_plural": "users",
115 | "verbose_name": "user",
116 | "abstract": False,
117 | },
118 | managers=[("objects", django.contrib.auth.models.UserManager())],
119 | )
120 | ]
121 |
--------------------------------------------------------------------------------
/django_react_paypal/users/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/users/migrations/__init__.py
--------------------------------------------------------------------------------
/django_react_paypal/users/models.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import AbstractUser
2 | from django.db.models import CharField
3 | from django.urls import reverse
4 | from django.utils.translation import gettext_lazy as _
5 |
6 |
7 | class User(AbstractUser):
8 | """Default user for Django React PayPal."""
9 |
10 | #: First and last name do not cover name patterns around the globe
11 | name = CharField(_("Name of User"), blank=True, max_length=255)
12 | first_name = None # type: ignore
13 | last_name = None # type: ignore
14 |
15 | def get_absolute_url(self):
16 | """Get url for user's detail view.
17 |
18 | Returns:
19 | str: URL for user detail.
20 |
21 | """
22 | return reverse("users:detail", kwargs={"username": self.username})
23 |
--------------------------------------------------------------------------------
/django_react_paypal/users/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/users/tests/__init__.py
--------------------------------------------------------------------------------
/django_react_paypal/users/tests/factories.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Sequence
2 |
3 | from django.contrib.auth import get_user_model
4 | from factory import Faker, post_generation
5 | from factory.django import DjangoModelFactory
6 |
7 |
8 | class UserFactory(DjangoModelFactory):
9 |
10 | username = Faker("user_name")
11 | email = Faker("email")
12 | name = Faker("name")
13 |
14 | @post_generation
15 | def password(self, create: bool, extracted: Sequence[Any], **kwargs):
16 | password = (
17 | extracted
18 | if extracted
19 | else Faker(
20 | "password",
21 | length=42,
22 | special_chars=True,
23 | digits=True,
24 | upper_case=True,
25 | lower_case=True,
26 | ).evaluate(None, None, extra={"locale": None})
27 | )
28 | self.set_password(password)
29 |
30 | class Meta:
31 | model = get_user_model()
32 | django_get_or_create = ["username"]
33 |
--------------------------------------------------------------------------------
/django_react_paypal/users/tests/test_admin.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from django.urls import reverse
3 |
4 | from django_react_paypal.users.models import User
5 |
6 | pytestmark = pytest.mark.django_db
7 |
8 |
9 | class TestUserAdmin:
10 | def test_changelist(self, admin_client):
11 | url = reverse("admin:users_user_changelist")
12 | response = admin_client.get(url)
13 | assert response.status_code == 200
14 |
15 | def test_search(self, admin_client):
16 | url = reverse("admin:users_user_changelist")
17 | response = admin_client.get(url, data={"q": "test"})
18 | assert response.status_code == 200
19 |
20 | def test_add(self, admin_client):
21 | url = reverse("admin:users_user_add")
22 | response = admin_client.get(url)
23 | assert response.status_code == 200
24 |
25 | response = admin_client.post(
26 | url,
27 | data={
28 | "username": "test",
29 | "password1": "My_R@ndom-P@ssw0rd",
30 | "password2": "My_R@ndom-P@ssw0rd",
31 | },
32 | )
33 | assert response.status_code == 302
34 | assert User.objects.filter(username="test").exists()
35 |
36 | def test_view_user(self, admin_client):
37 | user = User.objects.get(username="admin")
38 | url = reverse("admin:users_user_change", kwargs={"object_id": user.pk})
39 | response = admin_client.get(url)
40 | assert response.status_code == 200
41 |
--------------------------------------------------------------------------------
/django_react_paypal/users/tests/test_drf_urls.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from django.urls import resolve, reverse
3 |
4 | from django_react_paypal.users.models import User
5 |
6 | pytestmark = pytest.mark.django_db
7 |
8 |
9 | def test_user_detail(user: User):
10 | assert (
11 | reverse("api:user-detail", kwargs={"username": user.username})
12 | == f"/api/users/{user.username}/"
13 | )
14 | assert resolve(f"/api/users/{user.username}/").view_name == "api:user-detail"
15 |
16 |
17 | def test_user_list():
18 | assert reverse("api:user-list") == "/api/users/"
19 | assert resolve("/api/users/").view_name == "api:user-list"
20 |
21 |
22 | def test_user_me():
23 | assert reverse("api:user-me") == "/api/users/me/"
24 | assert resolve("/api/users/me/").view_name == "api:user-me"
25 |
--------------------------------------------------------------------------------
/django_react_paypal/users/tests/test_drf_views.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from django.test import RequestFactory
3 |
4 | from django_react_paypal.users.api.views import UserViewSet
5 | from django_react_paypal.users.models import User
6 |
7 | pytestmark = pytest.mark.django_db
8 |
9 |
10 | class TestUserViewSet:
11 | def test_get_queryset(self, user: User, rf: RequestFactory):
12 | view = UserViewSet()
13 | request = rf.get("/fake-url/")
14 | request.user = user
15 |
16 | view.request = request
17 |
18 | assert user in view.get_queryset()
19 |
20 | def test_me(self, user: User, rf: RequestFactory):
21 | view = UserViewSet()
22 | request = rf.get("/fake-url/")
23 | request.user = user
24 |
25 | view.request = request
26 |
27 | response = view.me(request)
28 |
29 | assert response.data == {
30 | "username": user.username,
31 | "name": user.name,
32 | "url": f"http://testserver/api/users/{user.username}/",
33 | }
34 |
--------------------------------------------------------------------------------
/django_react_paypal/users/tests/test_forms.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for all Form Tests.
3 | """
4 | import pytest
5 | from django.utils.translation import gettext_lazy as _
6 |
7 | from django_react_paypal.users.forms import UserCreationForm
8 | from django_react_paypal.users.models import User
9 |
10 | pytestmark = pytest.mark.django_db
11 |
12 |
13 | class TestUserCreationForm:
14 | """
15 | Test class for all tests related to the UserCreationForm
16 | """
17 |
18 | def test_username_validation_error_msg(self, user: User):
19 | """
20 | Tests UserCreation Form's unique validator functions correctly by testing:
21 | 1) A new user with an existing username cannot be added.
22 | 2) Only 1 error is raised by the UserCreation Form
23 | 3) The desired error message is raised
24 | """
25 |
26 | # The user already exists,
27 | # hence cannot be created.
28 | form = UserCreationForm(
29 | {
30 | "username": user.username,
31 | "password1": user.password,
32 | "password2": user.password,
33 | }
34 | )
35 |
36 | assert not form.is_valid()
37 | assert len(form.errors) == 1
38 | assert "username" in form.errors
39 | assert form.errors["username"][0] == _("This username has already been taken.")
40 |
--------------------------------------------------------------------------------
/django_react_paypal/users/tests/test_models.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from django_react_paypal.users.models import User
4 |
5 | pytestmark = pytest.mark.django_db
6 |
7 |
8 | def test_user_get_absolute_url(user: User):
9 | assert user.get_absolute_url() == f"/users/{user.username}/"
10 |
--------------------------------------------------------------------------------
/django_react_paypal/users/tests/test_urls.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from django.urls import resolve, reverse
3 |
4 | from django_react_paypal.users.models import User
5 |
6 | pytestmark = pytest.mark.django_db
7 |
8 |
9 | def test_detail(user: User):
10 | assert (
11 | reverse("users:detail", kwargs={"username": user.username})
12 | == f"/users/{user.username}/"
13 | )
14 | assert resolve(f"/users/{user.username}/").view_name == "users:detail"
15 |
16 |
17 | def test_update():
18 | assert reverse("users:update") == "/users/~update/"
19 | assert resolve("/users/~update/").view_name == "users:update"
20 |
21 |
22 | def test_redirect():
23 | assert reverse("users:redirect") == "/users/~redirect/"
24 | assert resolve("/users/~redirect/").view_name == "users:redirect"
25 |
--------------------------------------------------------------------------------
/django_react_paypal/users/tests/test_views.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from django.conf import settings
3 | from django.contrib import messages
4 | from django.contrib.auth.models import AnonymousUser
5 | from django.contrib.messages.middleware import MessageMiddleware
6 | from django.contrib.sessions.middleware import SessionMiddleware
7 | from django.http import HttpRequest
8 | from django.test import RequestFactory
9 | from django.urls import reverse
10 |
11 | from django_react_paypal.users.forms import UserChangeForm
12 | from django_react_paypal.users.models import User
13 | from django_react_paypal.users.tests.factories import UserFactory
14 | from django_react_paypal.users.views import (
15 | UserRedirectView,
16 | UserUpdateView,
17 | user_detail_view,
18 | )
19 |
20 | pytestmark = pytest.mark.django_db
21 |
22 |
23 | class TestUserUpdateView:
24 | """
25 | TODO:
26 | extracting view initialization code as class-scoped fixture
27 | would be great if only pytest-django supported non-function-scoped
28 | fixture db access -- this is a work-in-progress for now:
29 | https://github.com/pytest-dev/pytest-django/pull/258
30 | """
31 |
32 | def dummy_get_response(self, request: HttpRequest):
33 | return None
34 |
35 | def test_get_success_url(self, user: User, rf: RequestFactory):
36 | view = UserUpdateView()
37 | request = rf.get("/fake-url/")
38 | request.user = user
39 |
40 | view.request = request
41 |
42 | assert view.get_success_url() == f"/users/{user.username}/"
43 |
44 | def test_get_object(self, user: User, rf: RequestFactory):
45 | view = UserUpdateView()
46 | request = rf.get("/fake-url/")
47 | request.user = user
48 |
49 | view.request = request
50 |
51 | assert view.get_object() == user
52 |
53 | def test_form_valid(self, user: User, rf: RequestFactory):
54 | view = UserUpdateView()
55 | request = rf.get("/fake-url/")
56 |
57 | # Add the session/message middleware to the request
58 | SessionMiddleware(self.dummy_get_response).process_request(request)
59 | MessageMiddleware(self.dummy_get_response).process_request(request)
60 | request.user = user
61 |
62 | view.request = request
63 |
64 | # Initialize the form
65 | form = UserChangeForm()
66 | form.cleaned_data = []
67 | view.form_valid(form)
68 |
69 | messages_sent = [m.message for m in messages.get_messages(request)]
70 | assert messages_sent == ["Information successfully updated"]
71 |
72 |
73 | class TestUserRedirectView:
74 | def test_get_redirect_url(self, user: User, rf: RequestFactory):
75 | view = UserRedirectView()
76 | request = rf.get("/fake-url")
77 | request.user = user
78 |
79 | view.request = request
80 |
81 | assert view.get_redirect_url() == f"/users/{user.username}/"
82 |
83 |
84 | class TestUserDetailView:
85 | def test_authenticated(self, user: User, rf: RequestFactory):
86 | request = rf.get("/fake-url/")
87 | request.user = UserFactory()
88 |
89 | response = user_detail_view(request, username=user.username)
90 |
91 | assert response.status_code == 200
92 |
93 | def test_not_authenticated(self, user: User, rf: RequestFactory):
94 | request = rf.get("/fake-url/")
95 | request.user = AnonymousUser()
96 |
97 | response = user_detail_view(request, username=user.username)
98 | login_url = reverse(settings.LOGIN_URL)
99 |
100 | assert response.status_code == 302
101 | assert response.url == f"{login_url}?next=/fake-url/"
102 |
--------------------------------------------------------------------------------
/django_react_paypal/users/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from django_react_paypal.users.views import (
4 | user_detail_view,
5 | user_redirect_view,
6 | user_update_view,
7 | )
8 |
9 | app_name = "users"
10 | urlpatterns = [
11 | path("~redirect/", view=user_redirect_view, name="redirect"),
12 | path("~update/", view=user_update_view, name="update"),
13 | path("/", view=user_detail_view, name="detail"),
14 | ]
15 |
--------------------------------------------------------------------------------
/django_react_paypal/users/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from django.contrib.auth.mixins import LoginRequiredMixin
3 | from django.contrib.messages.views import SuccessMessageMixin
4 | from django.urls import reverse
5 | from django.utils.translation import gettext_lazy as _
6 | from django.views.generic import DetailView, RedirectView, UpdateView
7 |
8 | User = get_user_model()
9 |
10 |
11 | class UserDetailView(LoginRequiredMixin, DetailView):
12 |
13 | model = User
14 | slug_field = "username"
15 | slug_url_kwarg = "username"
16 |
17 |
18 | user_detail_view = UserDetailView.as_view()
19 |
20 |
21 | class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
22 |
23 | model = User
24 | fields = ["name"]
25 | success_message = _("Information successfully updated")
26 |
27 | def get_success_url(self):
28 | return self.request.user.get_absolute_url() # type: ignore [union-attr]
29 |
30 | def get_object(self):
31 | return self.request.user
32 |
33 |
34 | user_update_view = UserUpdateView.as_view()
35 |
36 |
37 | class UserRedirectView(LoginRequiredMixin, RedirectView):
38 |
39 | permanent = False
40 |
41 | def get_redirect_url(self):
42 | return reverse("users:detail", kwargs={"username": self.request.user.username})
43 |
44 |
45 | user_redirect_view = UserRedirectView.as_view()
46 |
--------------------------------------------------------------------------------
/django_react_paypal/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdjango/django_react_paypal/d3aa6a16ff0bf08d30ce79204a37d8bb7b806bd5/django_react_paypal/utils/__init__.py
--------------------------------------------------------------------------------
/django_react_paypal/utils/context_processors.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 |
4 | def settings_context(_request):
5 | """Settings available by default to the templates context."""
6 | # Note: we intentionally do NOT expose the entire settings
7 | # to prevent accidental leaking of sensitive information
8 | return {"DEBUG": settings.DEBUG}
9 |
--------------------------------------------------------------------------------
/django_react_paypal/utils/storages.py:
--------------------------------------------------------------------------------
1 | from storages.backends.s3boto3 import S3Boto3Storage
2 |
3 |
4 | class StaticRootS3Boto3Storage(S3Boto3Storage):
5 | location = "static"
6 | default_acl = "public-read"
7 |
8 |
9 | class MediaRootS3Boto3Storage(S3Boto3Storage):
10 | location = "media"
11 | file_overwrite = False
12 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build -c .
8 | SOURCEDIR = .
9 | BUILDDIR = ./_build
10 | APP = /app
11 |
12 | .PHONY: help livehtml apidocs Makefile
13 |
14 | # Put it first so that "make" without argument is like "make help".
15 | help:
16 | @$(SPHINXBUILD) help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
17 |
18 | # Build, watch and serve docs with live reload
19 | livehtml:
20 | sphinx-autobuild -b html --host 0.0.0.0 --port 7000 --watch $(APP) -c . $(SOURCEDIR) $(BUILDDIR)/html
21 |
22 | # Outputs rst files from django application code
23 | apidocs:
24 | sphinx-apidoc -o $(SOURCEDIR)/api /app
25 |
26 | # Catch-all target: route all unknown targets to Sphinx using the new
27 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
28 | %: Makefile
29 | @$(SPHINXBUILD) -b $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
30 |
--------------------------------------------------------------------------------
/docs/__init__.py:
--------------------------------------------------------------------------------
1 | # Included so that Django's startproject comment runs against the docs directory
2 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 |
13 | import os
14 | import sys
15 | import django
16 |
17 | if os.getenv("READTHEDOCS", default=False) == "True":
18 | sys.path.insert(0, os.path.abspath(".."))
19 | os.environ["DJANGO_READ_DOT_ENV_FILE"] = "True"
20 | os.environ["USE_DOCKER"] = "no"
21 | else:
22 | sys.path.insert(0, os.path.abspath("/app"))
23 | os.environ["DATABASE_URL"] = "sqlite:///readthedocs.db"
24 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
25 | django.setup()
26 |
27 | # -- Project information -----------------------------------------------------
28 |
29 | project = "Django React PayPal"
30 | copyright = """2021, Matthew Freire"""
31 | author = "Matthew Freire"
32 |
33 |
34 | # -- General configuration ---------------------------------------------------
35 |
36 | # Add any Sphinx extension module names here, as strings. They can be
37 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
38 | # ones.
39 | extensions = [
40 | "sphinx.ext.autodoc",
41 | "sphinx.ext.napoleon",
42 | ]
43 |
44 | # Add any paths that contain templates here, relative to this directory.
45 | # templates_path = ["_templates"]
46 |
47 | # List of patterns, relative to source directory, that match files and
48 | # directories to ignore when looking for source files.
49 | # This pattern also affects html_static_path and html_extra_path.
50 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
51 |
52 | # -- Options for HTML output -------------------------------------------------
53 |
54 | # The theme to use for HTML and HTML Help pages. See the documentation for
55 | # a list of builtin themes.
56 | #
57 | html_theme = "alabaster"
58 |
59 | # Add any paths that contain custom static files (such as style sheets) here,
60 | # relative to this directory. They are copied after the builtin static files,
61 | # so a file named "default.css" will overwrite the builtin "default.css".
62 | # html_static_path = ["_static"]
63 |
--------------------------------------------------------------------------------
/docs/howto.rst:
--------------------------------------------------------------------------------
1 | How To - Project Documentation
2 | ======================================================================
3 |
4 | Get Started
5 | ----------------------------------------------------------------------
6 |
7 | Documentation can be written as rst files in `django_react_paypal/docs`.
8 |
9 |
10 | To build and serve docs, use the commands::
11 |
12 | docker-compose -f local.yml up docs
13 |
14 |
15 |
16 | Changes to files in `docs/_source` will be picked up and reloaded automatically.
17 |
18 | `Sphinx `_ is the tool used to build documentation.
19 |
20 | Docstrings to Documentation
21 | ----------------------------------------------------------------------
22 |
23 | The sphinx extension `apidoc `_ is used to automatically document code using signatures and docstrings.
24 |
25 | Numpy or Google style docstrings will be picked up from project files and availble for documentation. See the `Napoleon `_ extension for details.
26 |
27 | For an in-use example, see the `page source <_sources/users.rst.txt>`_ for :ref:`users`.
28 |
29 | To compile all docstrings automatically into documentation source files, use the command:
30 | ::
31 |
32 | make apidocs
33 |
34 |
35 | This can be done in the docker container:
36 | ::
37 |
38 | docker run --rm docs make apidocs
39 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. Django React PayPal documentation master file, created by
2 | sphinx-quickstart.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to Django React PayPal's documentation!
7 | ======================================================================
8 |
9 | .. toctree::
10 | :maxdepth: 2
11 | :caption: Contents:
12 |
13 | howto
14 | pycharm/configuration
15 | users
16 |
17 |
18 |
19 | Indices and tables
20 | ==================
21 |
22 | * :ref:`genindex`
23 | * :ref:`modindex`
24 | * :ref:`search`
25 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 |
8 | if "%SPHINXBUILD%" == "" (
9 | set SPHINXBUILD=sphinx-build -c .
10 | )
11 | set SOURCEDIR=_source
12 | set BUILDDIR=_build
13 | set APP=..\django_react_paypal
14 |
15 | if "%1" == "" goto help
16 |
17 | %SPHINXBUILD% >NUL 2>NUL
18 | if errorlevel 9009 (
19 | echo.
20 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
21 | echo.installed, then set the SPHINXBUILD environment variable to point
22 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
23 | echo.may add the Sphinx directory to PATH.
24 | echo.
25 | echo.Install sphinx-autobuild for live serving.
26 | echo.If you don't have Sphinx installed, grab it from
27 | echo.http://sphinx-doc.org/
28 | exit /b 1
29 | )
30 |
31 | %SPHINXBUILD% -b %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
32 | goto end
33 |
34 | :livehtml
35 | sphinx-autobuild -b html --open-browser -p 7000 --watch %APP% -c . %SOURCEDIR% %BUILDDIR%/html
36 | GOTO :EOF
37 |
38 | :apidocs
39 | sphinx-apidoc -o %SOURCEDIR%/api %APP%
40 | GOTO :EOF
41 |
42 | :help
43 | %SPHINXBUILD% -b help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
44 |
45 | :end
46 | popd
47 |
--------------------------------------------------------------------------------
/docs/users.rst:
--------------------------------------------------------------------------------
1 | .. _users:
2 |
3 | Users
4 | ======================================================================
5 |
6 | Starting a new project, it’s highly recommended to set up a custom user model,
7 | even if the default User model is sufficient for you.
8 |
9 | This model behaves identically to the default user model,
10 | but you’ll be able to customize it in the future if the need arises.
11 |
12 | .. automodule:: django_react_paypal.users.models
13 | :members:
14 | :noindex:
15 |
16 |
--------------------------------------------------------------------------------
/local.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | volumes:
4 | local_postgres_data: {}
5 | local_postgres_data_backups: {}
6 |
7 | services:
8 | django:
9 | build:
10 | context: .
11 | dockerfile: ./compose/local/django/Dockerfile
12 | image: django_react_paypal_local_django
13 | container_name: django_react_paypal_django
14 | depends_on:
15 | - postgres
16 | volumes:
17 | - .:/app:z
18 | env_file:
19 | - ./.envs/.local/.django
20 | - ./.envs/.local/.postgres
21 | ports:
22 | - "8000:8000"
23 | command: /start
24 |
25 | react:
26 | build:
27 | context: ./django_react_paypal/frontend
28 | dockerfile: ./compose/local/Dockerfile
29 | target: development
30 | image: django_react_paypal_local_react
31 | container_name: django_react_paypal_react
32 | volumes:
33 | - ./django_react_paypal/frontend/src:/app/src
34 | ports:
35 | - 3000:3000
36 |
37 | postgres:
38 | build:
39 | context: .
40 | dockerfile: ./compose/production/postgres/Dockerfile
41 | image: django_react_paypal_production_postgres
42 | container_name: django_react_paypal_postgres
43 | volumes:
44 | - local_postgres_data:/var/lib/postgresql/data:Z
45 | - local_postgres_data_backups:/backups:z
46 | env_file:
47 | - ./.envs/.local/.postgres
48 |
49 | docs:
50 | image: django_react_paypal_local_docs
51 | container_name: django_react_paypal_docs
52 | build:
53 | context: .
54 | dockerfile: ./compose/local/docs/Dockerfile
55 | env_file:
56 | - ./.envs/.local/.django
57 | volumes:
58 | - ./docs:/docs:z
59 | - ./config:/app/config:z
60 | - ./django_react_paypal:/app/django_react_paypal:z
61 | ports:
62 | - "7000:7000"
63 | command: /start-docs
64 |
--------------------------------------------------------------------------------
/locale/README.rst:
--------------------------------------------------------------------------------
1 | Translations
2 | ============
3 |
4 | Translations will be placed in this folder when running::
5 |
6 | python manage.py makemessages
7 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 | from pathlib import Path
5 |
6 | if __name__ == "__main__":
7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
8 |
9 | try:
10 | from django.core.management import execute_from_command_line
11 | except ImportError:
12 | # The above import may fail for some other reason. Ensure that the
13 | # issue is really that Django is missing to avoid masking other
14 | # exceptions on Python 2.
15 | try:
16 | import django # noqa
17 | except ImportError:
18 | raise ImportError(
19 | "Couldn't import Django. Are you sure it's installed and "
20 | "available on your PYTHONPATH environment variable? Did you "
21 | "forget to activate a virtual environment?"
22 | )
23 |
24 | raise
25 |
26 | # This allows easy placement of apps within the interior
27 | # django_react_paypal directory.
28 | current_path = Path(__file__).parent.resolve()
29 | sys.path.append(str(current_path / "django_react_paypal"))
30 |
31 | execute_from_command_line(sys.argv)
32 |
--------------------------------------------------------------------------------
/merge_production_dotenvs_in_dotenv.py:
--------------------------------------------------------------------------------
1 | import os
2 | from pathlib import Path
3 | from typing import Sequence
4 |
5 | import pytest
6 |
7 | ROOT_DIR_PATH = Path(__file__).parent.resolve()
8 | PRODUCTION_DOTENVS_DIR_PATH = ROOT_DIR_PATH / ".envs" / ".production"
9 | PRODUCTION_DOTENV_FILE_PATHS = [
10 | PRODUCTION_DOTENVS_DIR_PATH / ".django",
11 | PRODUCTION_DOTENVS_DIR_PATH / ".postgres",
12 | ]
13 | DOTENV_FILE_PATH = ROOT_DIR_PATH / ".env"
14 |
15 |
16 | def merge(
17 | output_file_path: str, merged_file_paths: Sequence[str], append_linesep: bool = True
18 | ) -> None:
19 | with open(output_file_path, "w") as output_file:
20 | for merged_file_path in merged_file_paths:
21 | with open(merged_file_path, "r") as merged_file:
22 | merged_file_content = merged_file.read()
23 | output_file.write(merged_file_content)
24 | if append_linesep:
25 | output_file.write(os.linesep)
26 |
27 |
28 | def main():
29 | merge(DOTENV_FILE_PATH, PRODUCTION_DOTENV_FILE_PATHS)
30 |
31 |
32 | @pytest.mark.parametrize("merged_file_count", range(3))
33 | @pytest.mark.parametrize("append_linesep", [True, False])
34 | def test_merge(tmpdir_factory, merged_file_count: int, append_linesep: bool):
35 | tmp_dir_path = Path(str(tmpdir_factory.getbasetemp()))
36 |
37 | output_file_path = tmp_dir_path / ".env"
38 |
39 | expected_output_file_content = ""
40 | merged_file_paths = []
41 | for i in range(merged_file_count):
42 | merged_file_ord = i + 1
43 |
44 | merged_filename = ".service{}".format(merged_file_ord)
45 | merged_file_path = tmp_dir_path / merged_filename
46 |
47 | merged_file_content = merged_filename * merged_file_ord
48 |
49 | with open(merged_file_path, "w+") as file:
50 | file.write(merged_file_content)
51 |
52 | expected_output_file_content += merged_file_content
53 | if append_linesep:
54 | expected_output_file_content += os.linesep
55 |
56 | merged_file_paths.append(merged_file_path)
57 |
58 | merge(output_file_path, merged_file_paths, append_linesep)
59 |
60 | with open(output_file_path, "r") as output_file:
61 | actual_output_file_content = output_file.read()
62 |
63 | assert actual_output_file_content == expected_output_file_content
64 |
65 |
66 | if __name__ == "__main__":
67 | main()
68 |
--------------------------------------------------------------------------------
/production.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | volumes:
4 | production_postgres_data: {}
5 | production_postgres_data_backups: {}
6 | production_traefik: {}
7 |
8 | services:
9 | django:
10 | build:
11 | context: .
12 | dockerfile: ./compose/production/django/Dockerfile
13 | image: django_react_paypal_production_django
14 | depends_on:
15 | - postgres
16 | - redis
17 | env_file:
18 | - ./.envs/.production/.django
19 | - ./.envs/.production/.postgres
20 | command: /start
21 |
22 | postgres:
23 | build:
24 | context: .
25 | dockerfile: ./compose/production/postgres/Dockerfile
26 | image: django_react_paypal_production_postgres
27 | volumes:
28 | - production_postgres_data:/var/lib/postgresql/data:Z
29 | - production_postgres_data_backups:/backups:z
30 | env_file:
31 | - ./.envs/.production/.postgres
32 |
33 | traefik:
34 | build:
35 | context: .
36 | dockerfile: ./compose/production/traefik/Dockerfile
37 | image: django_react_paypal_production_traefik
38 | depends_on:
39 | - django
40 | volumes:
41 | - production_traefik:/etc/traefik/acme:z
42 | ports:
43 | - "0.0.0.0:80:80"
44 | - "0.0.0.0:443:443"
45 |
46 | redis:
47 | image: redis:5.0
48 |
49 | awscli:
50 | build:
51 | context: .
52 | dockerfile: ./compose/production/aws/Dockerfile
53 | env_file:
54 | - ./.envs/.production/.django
55 | volumes:
56 | - production_postgres_data_backups:/backups:z
57 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = --ds=config.settings.test --reuse-db
3 | python_files = tests.py test_*.py
4 |
--------------------------------------------------------------------------------
/requirements/base.txt:
--------------------------------------------------------------------------------
1 | pytz==2021.1 # https://github.com/stub42/pytz
2 | python-slugify==5.0.2 # https://github.com/un33k/python-slugify
3 | Pillow==8.3.1 # https://github.com/python-pillow/Pillow
4 | argon2-cffi==20.1.0 # https://github.com/hynek/argon2_cffi
5 | redis==3.5.3 # https://github.com/andymccurdy/redis-py
6 | hiredis==2.0.0 # https://github.com/redis/hiredis-py
7 | paypalrestsdk==1.13.1 # https://github.com/paypal/PayPal-Python-SDK
8 |
9 | # Django
10 | # ------------------------------------------------------------------------------
11 | django==3.1.13 # pyup: < 3.2 # https://www.djangoproject.com/
12 | django-environ==0.4.5 # https://github.com/joke2k/django-environ
13 | django-model-utils==4.1.1 # https://github.com/jazzband/django-model-utils
14 | django-allauth==0.45.0 # https://github.com/pennersr/django-allauth
15 | django-crispy-forms==1.12.0 # https://github.com/django-crispy-forms/django-crispy-forms
16 | django-redis==5.0.0 # https://github.com/jazzband/django-redis
17 | # Django REST Framework
18 | djangorestframework==3.12.4 # https://github.com/encode/django-rest-framework
19 | django-cors-headers==3.7.0 # https://github.com/adamchainz/django-cors-headers
20 |
--------------------------------------------------------------------------------
/requirements/local.txt:
--------------------------------------------------------------------------------
1 | -r base.txt
2 |
3 | Werkzeug==1.0.1 # https://github.com/pallets/werkzeug
4 | ipdb==0.13.9 # https://github.com/gotcha/ipdb
5 | psycopg2==2.9.1 # https://github.com/psycopg/psycopg2
6 |
7 | # Testing
8 | # ------------------------------------------------------------------------------
9 | mypy==0.910 # https://github.com/python/mypy
10 | django-stubs==1.8.0 # https://github.com/typeddjango/django-stubs
11 | pytest==6.2.4 # https://github.com/pytest-dev/pytest
12 | pytest-sugar==0.9.4 # https://github.com/Frozenball/pytest-sugar
13 |
14 | # Documentation
15 | # ------------------------------------------------------------------------------
16 | sphinx==4.1.2 # https://github.com/sphinx-doc/sphinx
17 | sphinx-autobuild==2021.3.14 # https://github.com/GaretJax/sphinx-autobuild
18 |
19 | # Code quality
20 | # ------------------------------------------------------------------------------
21 | flake8==3.9.2 # https://github.com/PyCQA/flake8
22 | flake8-isort==4.0.0 # https://github.com/gforcada/flake8-isort
23 | coverage==5.5 # https://github.com/nedbat/coveragepy
24 | black==21.7b0 # https://github.com/psf/black
25 | pylint-django==2.4.4 # https://github.com/PyCQA/pylint-django
26 | pre-commit==2.13.0 # https://github.com/pre-commit/pre-commit
27 |
28 | # Django
29 | # ------------------------------------------------------------------------------
30 | factory-boy==3.2.0 # https://github.com/FactoryBoy/factory_boy
31 |
32 | django-debug-toolbar==3.2.1 # https://github.com/jazzband/django-debug-toolbar
33 | django-extensions==3.1.3 # https://github.com/django-extensions/django-extensions
34 | django-coverage-plugin==2.0.0 # https://github.com/nedbat/django_coverage_plugin
35 | pytest-django==4.4.0 # https://github.com/pytest-dev/pytest-django
36 |
--------------------------------------------------------------------------------
/requirements/production.txt:
--------------------------------------------------------------------------------
1 | # PRECAUTION: avoid production dependencies that aren't in development
2 |
3 | -r base.txt
4 |
5 | gunicorn==20.1.0 # https://github.com/benoitc/gunicorn
6 | psycopg2==2.9.1 # https://github.com/psycopg/psycopg2
7 | Collectfast==2.2.0 # https://github.com/antonagestam/collectfast
8 |
9 | # Django
10 | # ------------------------------------------------------------------------------
11 | django-storages[boto3]==1.11.1 # https://github.com/jschneier/django-storages
12 | django-anymail[mailgun]==8.4 # https://github.com/anymail/django-anymail
13 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 120
3 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
4 |
5 | [pycodestyle]
6 | max-line-length = 120
7 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
8 |
9 | [mypy]
10 | python_version = 3.9
11 | check_untyped_defs = True
12 | ignore_missing_imports = True
13 | warn_unused_ignores = True
14 | warn_redundant_casts = True
15 | warn_unused_configs = True
16 | plugins = mypy_django_plugin.main
17 |
18 | [mypy.plugins.django-stubs]
19 | django_settings_module = config.settings.test
20 |
21 | [mypy-*.migrations.*]
22 | # Django migrations should not produce any errors:
23 | ignore_errors = True
24 |
25 | [coverage:run]
26 | include = django_react_paypal/*
27 | omit = *migrations*, *tests*
28 | plugins =
29 | django_coverage_plugin
30 |
--------------------------------------------------------------------------------