2024-08-21 22:32:06 +03:00
# Project Makefile
#
# A makefile to automate setup of a Wagtail CMS project and related tasks.
#
# https://github.com/aclark4life/project-makefile
#
# --------------------------------------------------------------------------------
# Set the default goal to be `git commit -a -m $(GIT_MESSAGE)` and `git push`
# --------------------------------------------------------------------------------
.DEFAULT_GOAL := git-commit
# --------------------------------------------------------------------------------
# Single line variables to be used by phony target rules
# --------------------------------------------------------------------------------
ADD_DIR := mkdir -pv
ADD_FILE := touch
AWS_OPTS := --no-cli-pager --output table
COPY_DIR := cp -rv
COPY_FILE := cp -v
DEL_DIR := rm -rv
DEL_FILE := rm -v
DJANGO_DB_COL = awk -F\= '{print $$2}'
DJANGO_DB_URL = eb ssh -c "source /opt/elasticbeanstalk/deployment/custom_env_var; env | grep DATABASE_URL"
DJANGO_DB_HOST = $( shell $( DJANGO_DB_URL) | $( DJANGO_DB_COL) | \
python -c 'import dj_database_url; url = input(); url = dj_database_url.parse(url); print(url["HOST"])' )
DJANGO_DB_NAME = $( shell $( DJANGO_DB_URL) | $( DJANGO_DB_COL) | \
python -c 'import dj_database_url; url = input(); url = dj_database_url.parse(url); print(url["NAME"])' )
DJANGO_DB_PASS = $( shell $( DJANGO_DB_URL) | $( DJANGO_DB_COL) | \
python -c 'import dj_database_url; url = input(); url = dj_database_url.parse(url); print(url["PASSWORD"])' )
DJANGO_DB_USER = $( shell $( DJANGO_DB_URL) | $( DJANGO_DB_COL) | \
python -c 'import dj_database_url; url = input(); url = dj_database_url.parse(url); print(url["USER"])' )
DJANGO_BACKEND_APPS_FILE := backend/apps.py
DJANGO_CUSTOM_ADMIN_FILE := backend/admin.py
DJANGO_FRONTEND_FILES = .babelrc .browserslistrc .eslintrc .nvmrc .stylelintrc.json frontend package-lock.json \
package.json postcss.config.js
DJANGO_SETTINGS_DIR = backend/settings
DJANGO_SETTINGS_BASE_FILE = $( DJANGO_SETTINGS_DIR) /base.py
DJANGO_SETTINGS_DEV_FILE = $( DJANGO_SETTINGS_DIR) /dev.py
DJANGO_SETTINGS_PROD_FILE = $( DJANGO_SETTINGS_DIR) /production.py
DJANGO_SETTINGS_SECRET_KEY = $( shell openssl rand -base64 48)
DJANGO_URLS_FILE = backend/urls.py
EB_DIR_NAME := .elasticbeanstalk
EB_ENV_NAME ?= $( PROJECT_NAME) -$( GIT_BRANCH) -$( GIT_REV)
EB_PLATFORM ?= "Python 3.11 running on 64bit Amazon Linux 2023"
EC2_INSTANCE_MAX ?= 1
EC2_INSTANCE_MIN ?= 1
EC2_INSTANCE_PROFILE ?= aws-elasticbeanstalk-ec2-role
EC2_INSTANCE_TYPE ?= t4g.small
EC2_LB_TYPE ?= application
EDITOR_REVIEW = subl
GIT_ADD := git add
GIT_BRANCH = $( shell git branch --show-current)
GIT_BRANCHES = $( shell git branch -a)
GIT_CHECKOUT = git checkout
GIT_COMMIT_MSG = " Update $( PROJECT_NAME) "
GIT_COMMIT = git commit
GIT_PUSH = git push
GIT_PUSH_FORCE = git push --force-with-lease
GIT_REV = $( shell git rev-parse --short HEAD)
MAKEFILE_CUSTOM_FILE := project.mk
PACKAGE_NAME = $( shell echo $( PROJECT_NAME) | sed 's/-/_/g' )
PAGER ?= less
PIP_ENSURE = python -m ensurepip
PIP_INSTALL_PLONE_CONSTRAINTS = https://dist.plone.org/release/6.0.11.1/constraints.txt
PROJECT_DIRS = backend contactpage home privacy siteuser
PROJECT_EMAIL := aclark@aclark.net
PROJECT_NAME = project-makefile
RANDIR := $( shell openssl rand -base64 12 | sed 's/\///g' )
TMPDIR := $( shell mktemp -d)
UNAME := $( shell uname)
WAGTAIL_CLEAN_DIRS = backend contactpage dist frontend home logging_demo model_form_demo node_modules payments privacy search sitepage siteuser
WAGTAIL_CLEAN_FILES = .babelrc .browserslistrc .dockerignore .eslintrc .gitignore .nvmrc .stylelintrc.json Dockerfile db.sqlite3 docker-compose.yml manage.py package-lock.json package.json postcss.config.js requirements-test.txt requirements.txt
# --------------------------------------------------------------------------------
# Include $(MAKEFILE_CUSTOM_FILE) if it exists
# --------------------------------------------------------------------------------
i f n e q ( $( wildcard $ ( MAKEFILE_CUSTOM_FILE ) ) , )
include $( MAKEFILE_CUSTOM_FILE)
e n d i f
# --------------------------------------------------------------------------------
# Multi-line variables to be used in phony target rules
# --------------------------------------------------------------------------------
d e f i n e D J A N G O _ A L L A U T H _ B A S E _ T E M P L A T E
{ % e x t e n d s 'base.html' % }
e n d e f
d e f i n e D J A N G O _ A P I _ S E R I A L I Z E R S
f r o m r e s t _ f r a m e w o r k i m p o r t s e r i a l i z e r s
f r o m s i t e u s e r . m o d e l s i m p o r t U s e r
class UserSerializer(serializers.HyperlinkedModelSerializer) :
class Meta:
model = User
fields = [ "url" , "username" , "email" , "is_staff" ]
e n d e f
d e f i n e D J A N G O _ A P I _ V I E W S
f r o m n i n j a i m p o r t N i n j a A P I
f r o m r e s t _ f r a m e w o r k i m p o r t v i e w s e t s
f r o m s i t e u s e r . m o d e l s i m p o r t U s e r
f r o m . s e r i a l i z e r s i m p o r t U s e r S e r i a l i z e r
api = NinjaAPI( )
@ a p i . g e t ( "/hello" )
def hello(request) :
return "Hello world"
class UserViewSet(viewsets.ModelViewSet) :
queryset = User.objects.all( )
serializer_class = UserSerializer
e n d e f
d e f i n e D J A N G O _ A P P _ T E S T S
f r o m d j a n g o . t e s t i m p o r t T e s t C a s e
f r o m d j a n g o . u r l s i m p o r t r e v e r s e
f r o m . m o d e l s i m p o r t Y o u r M o d e l
f r o m . f o r m s i m p o r t Y o u r F o r m
class YourModelTest(TestCase) :
def setUp( self) :
self.instance = YourModel.objects.create( field1 = "value1" , field2 = "value2" )
def test_instance_creation( self) :
self.assertIsInstance( self.instance, YourModel)
self.assertEqual( self.instance.field1, "value1" )
self.assertEqual( self.instance.field2, "value2" )
def test_str_method( self) :
self.assertEqual( str( self.instance) , "Expected String Representation" )
class YourViewTest(TestCase) :
def setUp( self) :
self.instance = YourModel.objects.create( field1 = "value1" , field2 = "value2" )
def test_view_url_exists_at_desired_location( self) :
response = self.client.get( "/your-url/" )
self.assertEqual( response.status_code, 200)
def test_view_url_accessible_by_name( self) :
response = self.client.get( reverse( "your-view-name" ) )
self.assertEqual( response.status_code, 200)
def test_view_uses_correct_template( self) :
response = self.client.get( reverse( "your-view-name" ) )
self.assertEqual( response.status_code, 200)
self.assertTemplateUsed( response, "your_template.html" )
def test_view_context( self) :
response = self.client.get( reverse( "your-view-name" ) )
self.assertEqual( response.status_code, 200)
self.assertIn( "context_variable" , response.context)
class YourFormTest(TestCase) :
def test_form_valid_data( self) :
form = YourForm( data = { "field1" : "value1" , "field2" : "value2" } )
self.assertTrue( form.is_valid( ) )
def test_form_invalid_data( self) :
form = YourForm( data = { "field1" : "" , "field2" : "value2" } )
self.assertFalse( form.is_valid( ) )
self.assertIn( "field1" , form.errors)
def test_form_save( self) :
form = YourForm( data = { "field1" : "value1" , "field2" : "value2" } )
if form.is_valid( ) :
instance = form.save( )
self.assertEqual( instance.field1, "value1" )
self.assertEqual( instance.field2, "value2" )
e n d e f
d e f i n e D J A N G O _ B A C K E N D _ A P P S
f r o m d j a n g o . c o n t r i b . a d m i n . a p p s i m p o r t A d m i n C o n f i g
class CustomAdminConfig(AdminConfig) :
default_site = "backend.admin.CustomAdminSite"
e n d e f
d e f i n e D J A N G O _ B A S E _ T E M P L A T E
{ % l o a d s t a t i c w e b p a c k _ l o a d e r % }
< ! D O C T Y P E h t m l >
< h t m l lang = "en"
class = "h-100"
data-bs-theme= "{{ request.user.user_theme_preference|default:'light' }}" >
<head>
<meta charset = "utf-8" />
<title>
{ % block title %} { % endblock %}
{ % block title_suffix %} { % endblock %}
</title>
<meta name = "viewport" content = "width=device-width, initial-scale=1" />
{ % stylesheet_pack 'app' %}
{ % block extra_css %} { # Override this in templates to add extra stylesheets #}{% endblock %}
<style>
.success {
background-color: #d4edda;
border-color: #c3e6cb;
color: #155724;
}
.info {
background-color: #d1ecf1;
border-color: #bee5eb;
color: #0c5460;
}
.warning {
background-color: #fff3cd;
border-color: #ffeeba;
color: #856404;
}
.danger {
background-color: #f8d7da;
border-color: #f5c6cb;
color: #721c24;
}
</style>
{ % include 'favicon.html' %}
{ % csrf_token %}
</head>
<body class = "{% block body_class %}{% endblock %} d-flex flex-column h-100" >
<main class = "flex-shrink-0" >
<div id = "app" ></div>
{ % include 'header.html' %}
{ % if messages %}
<div class = "messages container" >
{ % for message in messages %}
<div class = "alert {{ message.tags }} alert-dismissible fade show"
role = "alert" >
{ { message } }
<button type = "button"
class = "btn-close"
data-bs-dismiss= "alert"
aria-label= "Close" ></button>
</div>
{ % endfor %}
</div>
{ % endif %}
<div class = "container" >
{ % block content %} { % endblock %}
</div>
</main>
{ % include 'footer.html' %}
{ % include 'offcanvas.html' %}
{ % javascript_pack 'app' %}
{ % block extra_js %} { # Override this in templates to add extra javascript #}{% endblock %}
</body>
< / h t m l >
e n d e f
d e f i n e D J A N G O _ C U S T O M _ A D M I N
f r o m d j a n g o . c o n t r i b . a d m i n i m p o r t A d m i n S i t e
class CustomAdminSite(AdminSite) :
site_header = "Project Makefile"
site_title = "Project Makefile"
index_title = "Project Makefile"
custom_admin_site = CustomAdminSite( name = "custom_admin" )
e n d e f
d e f i n e D J A N G O _ D O C K E R C O M P O S E
version : '3'
services :
db:
image: postgres:latest
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: project
POSTGRES_USER: admin
POSTGRES_PASSWORD: admin
web:
build: .
command: sh -c "python manage.py migrate && gunicorn project.wsgi:application -b 0.0.0.0:8000"
volumes:
- .:/app
ports:
- "8000:8000"
depends_on:
- db
environment:
DATABASE_URL: postgres://admin:admin@db:5432/project
volumes :
postgres_data:
e n d e f
d e f i n e D J A N G O _ D O C K E R F I L E
FROM amazonlinux : 2023
R U N d n f i n s t a l l - y s h a d o w - u t i l s p y t h o n 3 . 1 1 p y t h o n 3 . 1 1 - p i p m a k e n o d e j s 2 0 - n p m n o d e j s p o s t g r e s q l 1 5 p o s t g r e s q l 1 5 - s e r v e r
U S E R p o s t g r e s
R U N i n i t d b - D / v a r / l i b / p g s q l / d a t a
U S E R r o o t
R U N u s e r a d d w a g t a i l
E X P O S E 8 0 0 0
E N V PYTHONUNBUFFERED = 1 PORT = 8000
C O P Y r e q u i r e m e n t s . t x t /
R U N p y t h o n 3 . 1 1 - m p i p i n s t a l l - r / r e q u i r e m e n t s . t x t
W O R K D I R / a p p
RUN chown wagtail : wagtail /app
COPY --chown=wagtail : wagtail . .
U S E R w a g t a i l
R U N n p m - 2 0 i n s t a l l ; n p m - 2 0 r u n b u i l d
R U N p y t h o n 3 . 1 1 m a n a g e . p y c o l l e c t s t a t i c - - n o i n p u t - - c l e a r
CMD set -xe; pg_ctl -D /var/lib/pgsql/data -l /tmp/logfile start; python3.11 manage.py migrate --noinput; gunicorn backend.wsgi : application
e n d e f
d e f i n e D J A N G O _ F A V I C O N _ T E M P L A T E
{ % l o a d s t a t i c % }
< l i n k href = "{% static 'wagtailadmin/images/favicon.ico' %}" rel = "icon" >
e n d e f
d e f i n e D J A N G O _ F O O T E R _ T E M P L A T E
< f o o t e r class = "footer mt-auto py-3 bg-body-tertiary pt-5 text-center text-small" >
<p class = "mb-1" >& copy; { % now "Y" %} { { current_site.site_name| default:"Project Makefile" } } </p>
<ul class = "list-inline" >
<li class = "list-inline-item" >
<a class = "text-secondary text-decoration-none {% if request.path == '/' %}active{% endif %}"
href = "/" >Home</a>
</li>
{ % for child in current_site.root_page.get_children %}
<li class = "list-inline-item" >
<a class = "text-secondary text-decoration-none {% if request.path == child.url %}active{% endif %}"
href = "{{ child.url }}" >{ { child } } </a>
</li>
{ % endfor %}
</ul>
< / f o o t e r >
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ A P P
i m p o r t R e a c t f r o m 'react' ;
i m p o r t { c r e a t e R o o t } f r o m 'react-dom/client' ;
i m p o r t 'bootstrap' ;
i m p o r t '@fortawesome/fontawesome-free/js/fontawesome' ;
i m p o r t '@fortawesome/fontawesome-free/js/solid' ;
i m p o r t '@fortawesome/fontawesome-free/js/regular' ;
i m p o r t '@fortawesome/fontawesome-free/js/brands' ;
i m p o r t g e t D a t a C o m p o n e n t s f r o m '../dataComponents' ;
i m p o r t U s e r C o n t e x t P r o v i d e r f r o m '../context' ;
i m p o r t * a s c o m p o n e n t s f r o m '../components' ;
i m p o r t "../styles/index.scss" ;
i m p o r t "../styles/theme-blue.scss" ;
i m p o r t "./config" ;
c o n s t { E r r o r B o u n d a r y } = components;
c o n s t dataComponents = getDataComponents( components) ;
c o n s t container = document.getElementById( 'app' ) ;
c o n s t root = createRoot( container) ;
c o n s t App = ( ) = > (
<ErrorBoundary>
<UserContextProvider>
{ dataComponents}
</UserContextProvider>
</ErrorBoundary>
) ;
r o o t . r e n d e r ( < A p p / > ) ;
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ A P P _ C O N F I G
i m p o r t '../utils/themeToggler.js' ;
/ / i m p o r t '../utils/tinymce.js' ;
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ B A B E L R C
{
"presets" : [
[
"@babel/preset-react" ,
] ,
[
"@babel/preset-env" ,
{
"useBuiltIns" : "usage" ,
"corejs" : "3.0.0"
}
]
] ,
"plugins" : [
"@babel/plugin-syntax-dynamic-import" ,
"@babel/plugin-transform-class-properties"
]
}
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ C O M P O N E N T S
export { d e f a u l t a s E r r o r B o u n d a r y } f r o m './ErrorBoundary' ;
export { d e f a u l t a s U s e r M e n u } f r o m './UserMenu' ;
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ C O M P O N E N T _ C L O C K
/ / V i a C h a t G P T
i m p o r t R e a c t , { u s e S t a t e , u s e E f f e c t , u s e C a l l b a c k , u s e R e f } f r o m 'react' ;
i m p o r t P r o p T y p e s f r o m 'prop-types' ;
c o n s t Clock = ( { color = '#fff' } ) = > {
const [ date, setDate] = useState( new Date( ) ) ;
const [ blink, setBlink] = useState( true ) ;
const timerID = useRef( ) ;
const tick = useCallback( ( ) = > {
setDate( new Date( ) ) ;
setBlink( prevBlink = > !prevBlink) ;
} , [ ] ) ;
useEffect( ( ) = > {
timerID.current = setInterval( ( ) = > tick( ) , 1000) ;
// Return a cleanup function to be run on component unmount
return ( ) = > clearInterval( timerID.current) ;
} , [ tick] ) ;
const formattedDate = date.toLocaleDateString( undefined, {
weekday: 'short' ,
year: 'numeric' ,
month: 'short' ,
day: 'numeric' ,
} ) ;
const formattedTime = date.toLocaleTimeString( undefined, {
hour: 'numeric' ,
minute: 'numeric' ,
} ) ;
return (
<>
<div style = { { animation: blink ? 'blink 1s infinite' : 'none' } } ><span className = 'me-2' >{ formattedDate} </span> { formattedTime} </div>
</>
) ;
} ;
Clock.propTypes = {
color: PropTypes.string,
} ;
export d e f a u l t C l o c k ;
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ C O M P O N E N T _ E R R O R
i m p o r t { C o m p o n e n t } f r o m 'react' ;
i m p o r t P r o p T y p e s f r o m 'prop-types' ;
c l a s s E r r o r B o u n d a r y e x t e n d s C o m p o n e n t {
constructor ( props) {
super( props) ;
this.state = { hasError: false } ;
}
static getDerivedStateFromError ( ) {
return { hasError: true } ;
}
componentDidCatch ( error, info) {
const { onError } = this.props;
console.error( error) ;
onError && onError( error, info) ;
}
render ( ) {
const { children = null } = this.props;
const { hasError } = this.state;
return hasError ? null : children;
}
}
ErrorBoundary.propTypes = {
onError: PropTypes.func,
children: PropTypes.node,
} ;
export d e f a u l t E r r o r B o u n d a r y ;
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ C O M P O N E N T _ U S E R _ M E N U
/ / U s e r M e n u . j s
i m p o r t R e a c t f r o m 'react' ;
i m p o r t P r o p T y p e s f r o m 'prop-types' ;
f u n c t i o n h a n d l e L o g o u t ( ) {
window.location.href = '/accounts/logout' ;
}
c o n s t UserMenu = ( { isAuthenticated, isSuperuser, textColor } ) = > {
return (
<div>
{ isAuthenticated ? (
<li className = "nav-item dropdown" >
<a className = "nav-link dropdown-toggle" type = "button" data-bs-toggle= "dropdown" aria-expanded= "false" >
<i className = "fa-solid fa-circle-user" ></i>
</a>
<ul className = "dropdown-menu" >
<li><a className = "dropdown-item" href = "/user/profile/" >Profile</a></li>
<li><a className = "dropdown-item" href = "/model-form-demo/" >Model Form Demo</a></li>
<li><a className = "dropdown-item" href = "/logging-demo/" >Logging Demo</a></li>
<li><a className = "dropdown-item" href = "/payments/" >Payments Demo</a></li>
{ isSuperuser ? (
<>
<li><hr className = "dropdown-divider" ></hr></li>
<li><a className = "dropdown-item" href = "/django" target = "_blank" >Django admin</a></li>
<li><a className = "dropdown-item" href = "/api" target = "_blank" >Django API</a></li>
<li><a className = "dropdown-item" href = "/wagtail" target = "_blank" >Wagtail admin</a></li>
<li><a className = "dropdown-item" href = "/explorer" target = "_blank" >SQL Explorer</a></li>
</>
) : null}
<li><hr className = "dropdown-divider" ></hr></li>
<li><a className = "dropdown-item" href = "/accounts/logout" >Logout</a></li>
</ul>
</li>
) : (
<li className = "nav-item" >
<a className = { ` nav-link text-$$ { textColor} ` } href = "/accounts/login" ><i className = "fa-solid fa-circle-user" ></i></a>
</li>
) }
</div>
) ;
} ;
UserMenu.propTypes = {
isAuthenticated: PropTypes.bool.isRequired,
isSuperuser: PropTypes.bool.isRequired,
textColor: PropTypes.string,
} ;
export d e f a u l t U s e r M e n u ;
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ C O N T E X T _ I N D E X
export { U s e r C o n t e x t P r o v i d e r a s d e f a u l t } f r o m './UserContextProvider' ;
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ C O N T E X T _ U S E R _ P R O V I D E R
/ / U s e r C o n t e x t P r o v i d e r . j s
i m p o r t R e a c t , { c r e a t e C o n t e x t , u s e C o n t e x t , u s e S t a t e } f r o m 'react' ;
i m p o r t P r o p T y p e s f r o m 'prop-types' ;
c o n s t UserContext = createContext( ) ;
export c o n s t UserContextProvider = ( { children } ) = > {
const [ isAuthenticated, setIsAuthenticated] = useState( false ) ;
const login = ( ) = > {
try {
// Add logic to handle login, set isAuthenticated to true
setIsAuthenticated( true ) ;
} catch ( error) {
console.error( 'Login error:' , error) ;
// Handle error, e.g., show an error message to the user
}
} ;
const logout = ( ) = > {
try {
// Add logic to handle logout, set isAuthenticated to false
setIsAuthenticated( false ) ;
} catch ( error) {
console.error( 'Logout error:' , error) ;
// Handle error, e.g., show an error message to the user
}
} ;
return (
<UserContext.Provider value = { { isAuthenticated, login, logout } } >
{ children}
</UserContext.Provider>
) ;
} ;
UserContextProvider.propTypes = {
children: PropTypes.node.isRequired,
} ;
export c o n s t useUserContext = ( ) = > {
const context = useContext( UserContext) ;
if ( !context) {
throw new Error( 'useUserContext must be used within a UserContextProvider' ) ;
}
return context;
} ;
/ / A d d P r o p T y p e s f o r t h e r e t u r n v a l u e o f u s e U s e r C o n t e x t
useUserContext.propTypes = {
isAuthenticated: PropTypes.bool.isRequired,
login: PropTypes.func.isRequired,
logout: PropTypes.func.isRequired,
} ;
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ E S L I N T R C
{
"env" : {
"browser" : true,
"es2021" : true,
"node" : true
} ,
"extends" : [
"eslint:recommended" ,
"plugin:react/recommended"
] ,
"overrides" : [
{
"env" : {
"node" : true
} ,
"files" : [
".eslintrc.{js,cjs}"
] ,
"parserOptions" : {
"sourceType" : "script"
}
}
] ,
"parserOptions" : {
"ecmaVersion" : "latest" ,
"sourceType" : "module"
} ,
"plugins" : [
"react"
] ,
"rules" : {
"no-unused-vars" : "off"
} ,
settings: {
react: {
version: 'detect' ,
} ,
} ,
}
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ O F F C A N V A S _ T E M P L A T E
< d i v class = "offcanvas offcanvas-start bg-dark"
tabindex = "-1"
id = "offcanvasExample"
aria-labelledby= "offcanvasExampleLabel" >
<div class = "offcanvas-header" >
<a class = "offcanvas-title text-light h5 text-decoration-none"
id = "offcanvasExampleLabel"
href = "/" >{ { current_site.site_name| default:"Project Makefile" } } </a>
<button type = "button"
class = "btn-close bg-light"
data-bs-dismiss= "offcanvas"
aria-label= "Close" ></button>
</div>
<div class = "offcanvas-body bg-dark" >
<ul class = "navbar-nav justify-content-end flex-grow-1 pe-3" >
<li class = "nav-item" >
<a class = "nav-link text-light active" aria-current= "page" href = "/" >Home</a>
</li>
{ % for child in current_site.root_page.get_children %}
<li class = "nav-item" >
<a class = "nav-link text-light" href = "{{ child.url }}" >{ { child } } </a>
</li>
{ % endfor %}
<li class = "nav-item"
id = "{% if request.user.is_authenticated %}theme-toggler-authenticated{% else %}theme-toggler-anonymous{% endif %}" >
<span class = "nav-link text-light"
data-bs-toggle= "tooltip"
title = "Toggle dark mode" >
<i class = "fas fa-circle-half-stroke" ></i>
</span>
</li>
<div data-component= "UserMenu"
data-text-color= "light"
data-is-authenticated= "{{ request.user.is_authenticated }}"
data-is-superuser= "{{ request.user.is_superuser }}" ></div>
</ul>
</div>
< / d i v >
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ P O R T A L
/ / V i a p w e l l e v e r
i m p o r t R e a c t f r o m 'react' ;
i m p o r t { c r e a t e P o r t a l } f r o m 'react-dom' ;
c o n s t parseProps = data = > Object.entries( data) .reduce( ( result, [ key, value] ) = > {
if ( value.toLowerCase( ) = = = 'true' ) {
value = true;
} else if ( value.toLowerCase( ) = = = 'false' ) {
value = false;
} else if ( value.toLowerCase( ) = = = 'null' ) {
value = null;
} else if ( !isNaN( parseFloat( value) ) && isFinite( value) ) {
// Parse numeric value
value = parseFloat( value) ;
} else if (
( value[ 0] = = = '[' && value.slice( -1) = = = ']' ) || ( value[ 0] = = = '{' && value.slice( -1) = = = '}' )
) {
// Parse JSON strings
value = JSON.parse( value) ;
}
result[ key] = value;
return result;
} , { } ) ;
/ / T h i s m e t h o d o f u s i n g p o r t a l s i n s t e a d o f c a l l i n g R e a c t D O M . r e n d e r o n i n d i v i d u a l c o m p o n e n t s
/ / e n s u r e s t h a t a l l c o m p o n e n t s a r e m o u n t e d u n d e r a s i n g l e R e a c t t r e e , a n d a r e t h e r e f o r e a b l e
/ / t o s h a r e c o n t e x t .
export d e f a u l t f u n c t i o n g e t P a g e C o m p o n e n t s ( c o m p o n e n t s ) {
const getPortalComponent = domEl = > {
// The element' s "data-component" attribute is used to determine which component to render.
// All other "data-*" attributes are passed as props.
const { component: componentName, ...rest } = domEl.dataset;
const Component = components[ componentName] ;
if ( !Component) {
console.error( ` Component " $$ {componentName} " not found.` ) ;
return null;
}
const props = parseProps( rest) ;
domEl.innerHTML = '' ;
// eslint-disable-next-line no-unused-vars
const { ErrorBoundary } = components;
return createPortal(
<ErrorBoundary>
<Component { ...props} />
</ErrorBoundary>,
domEl,
) ;
} ;
return Array.from( document.querySelectorAll( '[data-component]' ) ) .map( getPortalComponent) ;
}
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ S T Y L E S
/ / I f y o u c o m m e n t o u t c o d e b e l o w , b o o t s t r a p w i l l u s e r e d a s p r i m a r y c o l o r
/ / a n d b t n - p r i m a r y w i l l b e c o m e r e d
// $primary : red ;
@ i m p o r t "~bootstrap/scss/bootstrap.scss" ;
. j u m b o t r o n {
// should be relative path of the entry scss file
background-image: url( "../../vendors/images/sample.jpg" ) ;
background-size: cover;
}
#theme-toggler-authenticated:hover {
cursor: pointer; /* Change cursor to pointer on hover */
color: #007bff; /* Change color on hover */
}
#theme-toggler-anonymous:hover {
cursor: pointer; /* Change cursor to pointer on hover */
color: #007bff; /* Change color on hover */
}
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ T H E M E _ B L U E
@ i m p o r t "~bootstrap/scss/bootstrap.scss" ;
[ data-bs-theme = "blue" ] {
--bs-body-color: var( --bs-white) ;
--bs-body-color-rgb: #{to-rgb($$white)};
--bs-body-bg: var( --bs-blue) ;
--bs-body-bg-rgb: #{to-rgb($$blue)};
--bs-tertiary-bg: #{$$blue-600};
.dropdown-menu {
--bs-dropdown-bg: #{color-mix($$blue-500, $$blue-600)};
--bs-dropdown-link-active-bg: #{$$blue-700};
}
.btn-secondary {
--bs-btn-bg: #{color-mix($gray-600, $blue-400, .5)};
--bs-btn-border-color: #{rgba($$white, .25)};
--bs-btn-hover-bg: #{color-adjust(color-mix($gray-600, $blue-400, .5), 5%)};
--bs-btn-hover-border-color: #{rgba($$white, .25)};
--bs-btn-active-bg: #{color-adjust(color-mix($gray-600, $blue-400, .5), 10%)};
--bs-btn-active-border-color: #{rgba($$white, .5)};
--bs-btn-focus-border-color: #{rgba($$white, .5)};
// --bs-btn-focus-box-shadow: 0 0 0 .25rem rgba( 255, 255, 255, 20%) ;
}
}
e n d e f
d e f i n e D J A N G O _ F R O N T E N D _ T H E M E _ T O G G L E R
d o c u m e n t . a d d E v e n t L i s t e n e r ( 'DOMContentLoaded' , f u n c t i o n ( ) {
const rootElement = document.documentElement;
const anonThemeToggle = document.getElementById( 'theme-toggler-anonymous' ) ;
const authThemeToggle = document.getElementById( 'theme-toggler-authenticated' ) ;
if ( authThemeToggle) {
localStorage.removeItem( 'data-bs-theme' ) ;
}
const anonSavedTheme = localStorage.getItem( 'data-bs-theme' ) ;
if ( anonSavedTheme) {
rootElement.setAttribute( 'data-bs-theme' , anonSavedTheme) ;
}
if ( anonThemeToggle) {
anonThemeToggle.addEventListener( 'click' , function ( ) {
const currentTheme = rootElement.getAttribute( 'data-bs-theme' ) || 'light' ;
const newTheme = currentTheme = = = 'light' ? 'dark' : 'light' ;
rootElement.setAttribute( 'data-bs-theme' , newTheme) ;
localStorage.setItem( 'data-bs-theme' , newTheme) ;
} ) ;
}
if ( authThemeToggle) {
const csrfToken = document.querySelector( '[name=csrfmiddlewaretoken]' ) .value;
authThemeToggle.addEventListener( 'click' , function ( ) {
const currentTheme = rootElement.getAttribute( 'data-bs-theme' ) || 'light' ;
const newTheme = currentTheme = = = 'light' ? 'dark' : 'light' ;
fetch( '/user/update_theme_preference/' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-CSRFToken' : csrfToken, // Include the CSRF token in the headers
} ,
body: JSON.stringify( { theme: newTheme } ) ,
} )
.then( response = > response.json( ) )
.then( data = > {
rootElement.setAttribute( 'data-bs-theme' , newTheme) ;
} )
.catch( error = > {
console.error( 'Error updating theme preference:' , error) ;
} ) ;
} ) ;
}
} ) ;
e n d e f
d e f i n e D J A N G O _ H E A D E R _ T E M P L A T E
< d i v class = "app-header" >
<div class = "container py-4 app-navbar" >
<nav class = "navbar navbar-transparent navbar-padded navbar-expand-md" >
<a class = "navbar-brand me-auto" href = "/" >{ { current_site.site_name| default:"Project Makefile" } } </a>
<button class = "navbar-toggler"
type = "button"
data-bs-toggle= "offcanvas"
data-bs-target= "#offcanvasExample"
aria-controls= "offcanvasExample"
aria-expanded= "false"
aria-label= "Toggle navigation" >
<span class = "navbar-toggler-icon" ></span>
</button>
<div class = "d-none d-md-block" >
<ul class = "navbar-nav" >
<li class = "nav-item" >
<a id = "home-nav"
class = "nav-link {% if request.path == '/' %}active{% endif %}"
aria-current= "page"
href = "/" >Home</a>
</li>
{ % for child in current_site.root_page.get_children %}
{ % if child.show_in_menus %}
<li class = "nav-item" >
<a class = "nav-link {% if request.path == child.url %}active{% endif %}"
aria-current= "page"
href = "{{ child.url }}" >{ { child } } </a>
</li>
{ % endif %}
{ % endfor %}
<div data-component= "UserMenu"
data-is-authenticated= "{{ request.user.is_authenticated }}"
data-is-superuser= "{{ request.user.is_superuser }}" ></div>
<li class = "nav-item"
id = "{% if request.user.is_authenticated %}theme-toggler-authenticated{% else %}theme-toggler-anonymous{% endif %}" >
<span class = "nav-link" data-bs-toggle= "tooltip" title = "Toggle dark mode" >
<i class = "fas fa-circle-half-stroke" ></i>
</span>
</li>
<li class = "nav-item" >
<form class = "form" action = "/search" >
<div class = "row" >
<div class = "col-8" >
<input class = "form-control"
type = "search"
name = "query"
{ % if search_query %} value = "{{ search_query }}" { % endif %} >
</div>
<div class = "col-4" >
<input type = "submit" value = "Search" class = "form-control" >
</div>
</div>
</form>
</li>
</ul>
</div>
</nav>
</div>
< / d i v >
e n d e f
d e f i n e D J A N G O _ H O M E _ P A G E _ A D M I N
f r o m d j a n g o . c o n t r i b i m p o r t a d m i n # noqa
# Register your models here.
e n d e f
d e f i n e D J A N G O _ H O M E _ P A G E _ M O D E L S
f r o m d j a n g o . d b i m p o r t m o d e l s # noqa
# Create your models here.
e n d e f
d e f i n e D J A N G O _ H O M E _ P A G E _ T E M P L A T E
{ % e x t e n d s "base.html" % }
{ % b l o c k c o n t e n t % }
<main class = "{% block main_class %}{% endblock %}" >
</main>
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ H O M E _ P A G E _ U R L S
f r o m d j a n g o . u r l s i m p o r t p a t h
f r o m . v i e w s i m p o r t H o m e V i e w
urlpatterns = [ path( "" , HomeView.as_view( ) , name = "home" ) ]
e n d e f
d e f i n e D J A N G O _ H O M E _ P A G E _ V I E W S
f r o m d j a n g o . v i e w s . g e n e r i c i m p o r t T e m p l a t e V i e w
class HomeView(TemplateView) :
template_name = "home.html"
e n d e f
d e f i n e D J A N G O _ L O G G I N G _ D E M O _ A D M I N
# Register your models here.
e n d e f
d e f i n e D J A N G O _ L O G G I N G _ D E M O _ M O D E L S
f r o m d j a n g o . d b i m p o r t m o d e l s # noqa
# Create your models here.
e n d e f
d e f i n e D J A N G O _ L O G G I N G _ D E M O _ S E T T I N G S
I N S T A L L E D _ A P P S . a p p e n d ( "logging_demo" ) # noqa
e n d e f
d e f i n e D J A N G O _ L O G G I N G _ D E M O _ U R L S
f r o m d j a n g o . u r l s i m p o r t p a t h
f r o m . v i e w s i m p o r t l o g g i n g _ d e m o
urlpatterns = [
path( "" , logging_demo, name = "logging_demo" ) ,
]
e n d e f
d e f i n e D J A N G O _ L O G G I N G _ D E M O _ V I E W S
f r o m d j a n g o . h t t p i m p o r t H t t p R e s p o n s e
i m p o r t l o g g i n g
logger = logging.getLogger( __name__)
def logging_demo(request) :
logger.debug( "Hello, world!" )
return HttpResponse( "Hello, world!" )
e n d e f
d e f i n e D J A N G O _ M A N A G E _ P Y
#!/usr/bin/env python
"" "Django's command-line utility for administrative tasks." ""
i m p o r t o s
i m p o r t s y s
def main() :
"" "Run administrative tasks." ""
os.environ.setdefault( "DJANGO_SETTINGS_MODULE" , "backend.settings.dev" )
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line( sys.argv)
if __name__ == "__main__" :
main( )
e n d e f
d e f i n e D J A N G O _ M O D E L _ F O R M _ D E M O _ A D M I N
f r o m d j a n g o . c o n t r i b i m p o r t a d m i n
f r o m . m o d e l s i m p o r t M o d e l F o r m D e m o
@ a d m i n . r e g i s t e r ( M o d e l F o r m D e m o )
class ModelFormDemoAdmin(admin.ModelAdmin) :
pass
e n d e f
d e f i n e D J A N G O _ M O D E L _ F O R M _ D E M O _ F O R M S
f r o m d j a n g o i m p o r t f o r m s
f r o m . m o d e l s i m p o r t M o d e l F o r m D e m o
class ModelFormDemoForm(forms.ModelForm) :
class Meta:
model = ModelFormDemo
fields = [ "name" , "email" , "age" , "is_active" ]
e n d e f
d e f i n e D J A N G O _ M O D E L _ F O R M _ D E M O _ M O D E L
f r o m d j a n g o . d b i m p o r t m o d e l s
f r o m d j a n g o . s h o r t c u t s i m p o r t r e v e r s e
class ModelFormDemo(models.Model) :
name = models.CharField( max_length = 100, blank = True, null = True)
email = models.EmailField( blank = True, null = True)
age = models.IntegerField( blank = True, null = True)
is_active = models.BooleanField( default = True)
created_at = models.DateTimeField( auto_now_add = True)
def __str__( self) :
return self.name or f"test-model-{self.pk}"
def get_absolute_url( self) :
return reverse( "model_form_demo_detail" , kwargs = { "pk" : self.pk} )
e n d e f
d e f i n e D J A N G O _ M O D E L _ F O R M _ D E M O _ T E M P L A T E _ D E T A I L
{ % e x t e n d s 'base.html' % }
{ % b l o c k c o n t e n t % }
<h1>Test Model Detail: { { model_form_demo.name } } </h1>
<p>Name: { { model_form_demo.name } } </p>
<p>Email: { { model_form_demo.email } } </p>
<p>Age: { { model_form_demo.age } } </p>
<p>Active: { { model_form_demo.is_active } } </p>
<p>Created At: { { model_form_demo.created_at } } </p>
<a href = "{% url 'model_form_demo_update' model_form_demo.pk %}" >Edit Test Model</a>
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ M O D E L _ F O R M _ D E M O _ T E M P L A T E _ F O R M
{ % e x t e n d s 'base.html' % }
{ % b l o c k c o n t e n t % }
<h1>
{ % if form.instance.pk %}
Update Test Model
{ % else %}
Create Test Model
{ % endif %}
</h1>
<form method = "post" >
{ % csrf_token %}
{ { form.as_p } }
<button type = "submit" >Save</button>
</form>
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ M O D E L _ F O R M _ D E M O _ T E M P L A T E _ L I S T
{ % e x t e n d s 'base.html' % }
{ % b l o c k c o n t e n t % }
<h1>Test Models List</h1>
<ul>
{ % for model_form_demo in model_form_demos %}
<li>
<a href = "{% url 'model_form_demo_detail' model_form_demo.pk %}" >{ { model_form_demo.name } } </a>
</li>
{ % endfor %}
</ul>
<a href = "{% url 'model_form_demo_create' %}" >Create New Test Model</a>
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ M O D E L _ F O R M _ D E M O _ U R L S
f r o m d j a n g o . u r l s i m p o r t p a t h
f r o m . v i e w s i m p o r t (
ModelFormDemoListView,
ModelFormDemoCreateView,
ModelFormDemoUpdateView,
ModelFormDemoDetailView,
)
urlpatterns = [
path( "" , ModelFormDemoListView.as_view( ) , name = "model_form_demo_list" ) ,
path( "create/" , ModelFormDemoCreateView.as_view( ) , name = "model_form_demo_create" ) ,
path(
"<int:pk>/update/" ,
ModelFormDemoUpdateView.as_view( ) ,
name = "model_form_demo_update" ,
) ,
path( "<int:pk>/" , ModelFormDemoDetailView.as_view( ) , name = "model_form_demo_detail" ) ,
]
e n d e f
d e f i n e D J A N G O _ M O D E L _ F O R M _ D E M O _ V I E W S
f r o m d j a n g o . v i e w s . g e n e r i c i m p o r t L i s t V i e w , C r e a t e V i e w , U p d a t e V i e w , D e t a i l V i e w
f r o m . m o d e l s i m p o r t M o d e l F o r m D e m o
f r o m . f o r m s i m p o r t M o d e l F o r m D e m o F o r m
class ModelFormDemoListView(ListView) :
model = ModelFormDemo
template_name = "model_form_demo_list.html"
context_object_name = "model_form_demos"
class ModelFormDemoCreateView(CreateView) :
model = ModelFormDemo
form_class = ModelFormDemoForm
template_name = "model_form_demo_form.html"
def form_valid( self, form) :
form.instance.created_by = self.request.user
return super( ) .form_valid( form)
class ModelFormDemoUpdateView(UpdateView) :
model = ModelFormDemo
form_class = ModelFormDemoForm
template_name = "model_form_demo_form.html"
class ModelFormDemoDetailView(DetailView) :
model = ModelFormDemo
template_name = "model_form_demo_detail.html"
context_object_name = "model_form_demo"
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ A D M I N
f r o m d j a n g o . c o n t r i b i m p o r t a d m i n
f r o m . m o d e l s i m p o r t P r o d u c t , O r d e r
a d m i n . s i t e . r e g i s t e r ( P r o d u c t )
a d m i n . s i t e . r e g i s t e r ( O r d e r )
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ F O R M
f r o m d j a n g o i m p o r t f o r m s
class PaymentsForm(forms.Form) :
stripeToken = forms.CharField( widget = forms.HiddenInput( ) )
amount = forms.DecimalField(
max_digits = 10, decimal_places = 2, widget = forms.HiddenInput( )
)
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ M I G R A T I O N _ 0 0 0 2
f r o m d j a n g o . d b i m p o r t m i g r a t i o n s
i m p o r t o s
i m p o r t s e c r e t s
i m p o r t l o g g i n g
logger = logging.getLogger( __name__)
def generate_default_key() :
return "sk_test_" + secrets.token_hex( 24)
def set_stripe_api_keys(apps, schema_editor) :
# Get the Stripe API Key model
APIKey = apps.get_model( "djstripe" , "APIKey" )
# Fetch the keys from environment variables or generate default keys
test_secret_key = os.environ.get( "STRIPE_TEST_SECRET_KEY" , generate_default_key( ) )
live_secret_key = os.environ.get( "STRIPE_LIVE_SECRET_KEY" , generate_default_key( ) )
logger.info( "STRIPE_TEST_SECRET_KEY: %s" , test_secret_key)
logger.info( "STRIPE_LIVE_SECRET_KEY: %s" , live_secret_key)
# Check if the keys are not already in the database
if not APIKey.objects.filter( secret = test_secret_key) .exists( ) :
APIKey.objects.create( secret = test_secret_key, livemode = False)
logger.info( "Added test secret key to the database." )
else :
logger.info( "Test secret key already exists in the database." )
if not APIKey.objects.filter( secret = live_secret_key) .exists( ) :
APIKey.objects.create( secret = live_secret_key, livemode = True)
logger.info( "Added live secret key to the database." )
else :
logger.info( "Live secret key already exists in the database." )
class Migration(migrations.Migration) :
dependencies = [
( "payments" , "0001_initial" ) ,
]
operations = [
migrations.RunPython( set_stripe_api_keys) ,
]
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ M I G R A T I O N _ 0 0 0 3
f r o m d j a n g o . d b i m p o r t m i g r a t i o n s
def create_initial_products(apps, schema_editor) :
Product = apps.get_model( "payments" , "Product" )
Product.objects.create( name = "T-shirt" , description = "A cool T-shirt" , price = 20.00)
Product.objects.create( name = "Mug" , description = "A nice mug" , price = 10.00)
Product.objects.create( name = "Hat" , description = "A stylish hat" , price = 15.00)
class Migration(migrations.Migration) :
dependencies = [
(
"payments" ,
"0002_set_stripe_api_keys" ,
) ,
]
operations = [
migrations.RunPython( create_initial_products) ,
]
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ M O D E L S
f r o m d j a n g o . d b i m p o r t m o d e l s
class Product(models.Model) :
name = models.CharField( max_length = 100)
description = models.TextField( )
price = models.DecimalField( max_digits = 10, decimal_places = 2)
def __str__( self) :
return self.name
class Order(models.Model) :
product = models.ForeignKey( Product, on_delete = models.CASCADE)
created_at = models.DateTimeField( auto_now_add = True)
stripe_checkout_session_id = models.CharField( max_length = 200)
def __str__( self) :
return f"Order {self.id} for {self.product.name}"
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ T E M P L A T E _ C A N C E L
{ % e x t e n d s "base.html" % }
{ % b l o c k t i t l e % } C a n c e l { % e n d b l o c k % }
{ % b l o c k c o n t e n t % }
<h1>Payment Cancelled</h1>
<p>Your payment was cancelled.</p>
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ T E M P L A T E _ C H E C K O U T
{ % e x t e n d s "base.html" % }
{ % b l o c k t i t l e % } C h e c k o u t { % e n d b l o c k % }
{ % b l o c k c o n t e n t % }
<h1>Checkout</h1>
<form action = "{% url 'checkout' %}" method = "post" >
{ % csrf_token %}
<button type = "submit" >Pay</button>
</form>
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ T E M P L A T E _ P R O D U C T _ D E T A I L
{ % e x t e n d s "base.html" % }
{ % b l o c k t i t l e % } { { p r o d u c t . n a m e } } { % e n d b l o c k % }
{ % b l o c k c o n t e n t % }
<h1>{ { product.name } } </h1>
<p>{ { product.description } } </p>
<p>Price: ${ { product.price } } </p>
<form action = "{% url 'checkout' %}" method = "post" >
{ % csrf_token %}
<input type = "hidden" name = "product_id" value = "{{ product.id }}" >
<button type = "submit" >Buy Now</button>
</form>
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ T E M P L A T E _ P R O D U C T _ L I S T
{ % e x t e n d s "base.html" % }
{ % b l o c k t i t l e % } P r o d u c t s { % e n d b l o c k % }
{ % b l o c k c o n t e n t % }
<h1>Products</h1>
<ul>
{ % for product in products %}
<li>
<a href = "{% url 'product_detail' product.pk %}" >{ { product.name } } - { { product.price } } </a>
</li>
{ % endfor %}
</ul>
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ T E M P L A T E _ S U C C E S S
{ % e x t e n d s "base.html" % }
{ % b l o c k t i t l e % } S u c c e s s { % e n d b l o c k % }
{ % b l o c k c o n t e n t % }
<h1>Payment Successful</h1>
<p>Thank you for your purchase!</p>
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ U R L S
f r o m d j a n g o . u r l s i m p o r t p a t h
f r o m . v i e w s i m p o r t (
CheckoutView,
SuccessView,
CancelView,
ProductListView,
ProductDetailView,
)
urlpatterns = [
path( "" , ProductListView.as_view( ) , name = "product_list" ) ,
path( "product/<int:pk>/" , ProductDetailView.as_view( ) , name = "product_detail" ) ,
path( "checkout/" , CheckoutView.as_view( ) , name = "checkout" ) ,
path( "success/" , SuccessView.as_view( ) , name = "success" ) ,
path( "cancel/" , CancelView.as_view( ) , name = "cancel" ) ,
]
e n d e f
d e f i n e D J A N G O _ P A Y M E N T S _ V I E W
f r o m d j a n g o . c o n f i m p o r t s e t t i n g s
f r o m d j a n g o . s h o r t c u t s i m p o r t r e n d e r , r e d i r e c t , g e t _ o b j e c t _ o r _ 4 0 4
f r o m d j a n g o . v i e w s . g e n e r i c i m p o r t T e m p l a t e V i e w , V i e w , L i s t V i e w , D e t a i l V i e w
i m p o r t s t r i p e
f r o m . m o d e l s i m p o r t P r o d u c t , O r d e r
stripe.api_key = settings.STRIPE_TEST_SECRET_KEY
class ProductListView(ListView) :
model = Product
template_name = "payments/product_list.html"
context_object_name = "products"
class ProductDetailView(DetailView) :
model = Product
template_name = "payments/product_detail.html"
context_object_name = "product"
class CheckoutView(View) :
template_name = "payments/checkout.html"
def get( self, request, *args, **kwargs) :
products = Product.objects.all( )
return render( request, self.template_name, { "products" : products} )
def post( self, request, *args, **kwargs) :
product_id = request.POST.get( "product_id" )
product = get_object_or_404( Product, id = product_id)
session = stripe.checkout.Session.create(
payment_method_types = [ "card" ] ,
line_items = [
{
"price_data" : {
"currency" : "usd" ,
"product_data" : {
"name" : product.name,
} ,
"unit_amount" : int( product.price * 100) ,
} ,
"quantity" : 1,
}
] ,
mode = "payment" ,
success_url = "http://localhost:8000/payments/success/" ,
cancel_url = "http://localhost:8000/payments/cancel/" ,
)
Order.objects.create( product = product, stripe_checkout_session_id = session.id)
return redirect( session.url, code = 303)
class SuccessView(TemplateView) :
template_name = "payments/success.html"
class CancelView(TemplateView) :
template_name = "payments/cancel.html"
e n d e f
d e f i n e D J A N G O _ S E A R C H _ F O R M S
f r o m d j a n g o i m p o r t f o r m s
class SearchForm(forms.Form) :
query = forms.CharField( max_length = 100, required = True, label = "Search" )
e n d e f
d e f i n e D J A N G O _ S E A R C H _ S E T T I N G S
SEARCH_MODELS = [
# Add search models here.
]
e n d e f
d e f i n e D J A N G O _ S E A R C H _ T E M P L A T E
{ % e x t e n d s "base.html" % }
{ % b l o c k b o d y _ c l a s s % } t e m p l a t e - s e a r c h r e s u l t s { % e n d b l o c k % }
{ % b l o c k t i t l e % } S e a r c h { % e n d b l o c k % }
{ % b l o c k c o n t e n t % }
<h1>Search</h1>
<form action = "{% url 'search' %}" method = "get" >
<input type = "text"
name = "query"
{ % if search_query %} value = "{{ search_query }}" { % endif %} >
<input type = "submit" value = "Search" class = "button" >
</form>
{ % if search_results %}
<ul>
{ % for result in search_results %}
<li>
<h4>
<a href = "{% pageurl result %}" >{ { result } } </a>
</h4>
{ % if result.search_description %} { { result.search_description } } { % endif %}
</li>
{ % endfor %}
</ul>
{ % if search_results.has_previous %}
<a href = "{% url 'search' %}?query={{ search_query|urlencode }}&page={{ search_results.previous_page_number }}" >Previous</a>
{ % endif %}
{ % if search_results.has_next %}
<a href = "{% url 'search' %}?query={{ search_query|urlencode }}&page={{ search_results.next_page_number }}" >Next</a>
{ % endif %}
{ % elif search_query %}
No results found
{ % else %}
No results found. Try a <a href = "?query=test" >test query</a>?
{ % endif %}
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ S E A R C H _ U R L S
f r o m d j a n g o . u r l s i m p o r t p a t h
f r o m . v i e w s i m p o r t S e a r c h V i e w
urlpatterns = [
path( "search/" , SearchView.as_view( ) , name = "search" ) ,
]
e n d e f
d e f i n e D J A N G O _ S E A R C H _ U T I L S
f r o m d j a n g o . a p p s i m p o r t a p p s
f r o m d j a n g o . c o n f i m p o r t s e t t i n g s
def get_search_models() :
models = [ ]
for model_path in settings.SEARCH_MODELS:
app_label, model_name = model_path.split( "." )
model = apps.get_model( app_label, model_name)
models.append( model)
return models
e n d e f
d e f i n e D J A N G O _ S E A R C H _ V I E W S
f r o m d j a n g o . v i e w s . g e n e r i c i m p o r t L i s t V i e w
f r o m d j a n g o . d b i m p o r t m o d e l s
f r o m d j a n g o . d b . m o d e l s i m p o r t Q
f r o m . f o r m s i m p o r t S e a r c h F o r m
f r o m . u t i l s i m p o r t g e t _ s e a r c h _ m o d e l s
class SearchView(ListView) :
template_name = "your_app/search_results.html"
context_object_name = "results"
paginate_by = 10
def get_queryset( self) :
form = SearchForm( self.request.GET)
query = None
results = [ ]
if form.is_valid( ) :
query = form.cleaned_data[ "query" ]
search_models = get_search_models( )
for model in search_models:
fields = [ f.name for f in model._meta.fields if isinstance( f, ( models.CharField, models.TextField) ) ]
queries = [ Q( **{ f"{field}__icontains" : query} ) for field in fields]
model_results = model.objects.filter( queries.pop( ) )
for item in queries:
model_results = model_results.filter( item)
results.extend( model_results)
return results
def get_context_data( self, **kwargs) :
context = super( ) .get_context_data( **kwargs)
context[ "form" ] = SearchForm( self.request.GET)
context[ "query" ] = self.request.GET.get( "query" , "" )
return context
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ A U T H E N T I C A T I O N _ B A C K E N D S
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend" ,
"allauth.account.auth_backends.AuthenticationBackend" ,
]
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ B A S E
# $(PROJECT_NAME)
#
# Uncomment next two lines to enable custom admin
# INSTALLED_APPS = [app for app in INSTALLED_APPS if app != 'django.contrib.admin']
# INSTALLED_APPS.append('backend.apps.CustomAdminConfig')
i m p o r t o s # noqa
i m p o r t d j _ d a t a b a s e _ u r l # noqa
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
EXPLORER_CONNECTIONS = { "Default" : "default" }
EXPLORER_DEFAULT_CONNECTION = "default"
LOGIN_REDIRECT_URL = "/"
PROJECT_DIR = os.path.dirname( os.path.dirname( os.path.abspath( __file__) ) )
SILENCED_SYSTEM_CHECKS = [ "django_recaptcha.recaptcha_test_key_error" ]
BASE_DIR = os.path.dirname( PROJECT_DIR)
STATICFILES_DIRS = [ ]
WEBPACK_LOADER = {
"MANIFEST_FILE" : os.path.join( BASE_DIR, "frontend/build/manifest.json" ) ,
}
S T A T I C F I L E S _ D I R S . a p p e n d ( o s . p a t h . j o i n ( B A S E _ D I R , "frontend/build" ) )
T E M P L A T E S [ 0 ] [ "DIRS" ] . a p p e n d ( o s . p a t h . j o i n ( P R O J E C T _ D I R , "templates" ) )
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ B A S E _ M I N I M A L
# $(PROJECT_NAME)
i m p o r t o s # noqa
i m p o r t d j _ d a t a b a s e _ u r l # noqa
I N S T A L L E D _ A P P S . a p p e n d ( "debug_toolbar" )
I N S T A L L E D _ A P P S . a p p e n d ( "webpack_boilerplate" )
PROJECT_DIR = os.path.dirname( os.path.dirname( os.path.abspath( __file__) ) )
BASE_DIR = os.path.dirname( PROJECT_DIR)
STATICFILES_DIRS = [ ]
S T A T I C F I L E S _ D I R S . a p p e n d ( o s . p a t h . j o i n ( B A S E _ D I R , "frontend/build" ) )
T E M P L A T E S [ 0 ] [ "DIRS" ] . a p p e n d ( o s . p a t h . j o i n ( P R O J E C T _ D I R , "templates" ) )
WEBPACK_LOADER = {
"MANIFEST_FILE" : os.path.join( BASE_DIR, "frontend/build/manifest.json" ) ,
}
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ C R I S P Y _ F O R M S
CRISPY_TEMPLATE_PACK = "bootstrap5"
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ D A T A B A S E
DATABASE_URL = os.environ.get( "DATABASE_URL" , " postgres://:@:/ $( PROJECT_NAME) " )
D A T A B A S E S [ "default" ] = d j _ d a t a b a s e _ u r l . p a r s e ( D A T A B A S E _ U R L )
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ D E V
f r o m . b a s e i m p o r t * # noqa
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
# SECURITY WARNING: define the correct hosts in production!
ALLOWED_HOSTS = [ "*" ]
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
try :
from .local import * # noqa
except ImportError :
pass
LOGGING = {
"version" : 1,
"disable_existing_loggers" : False,
"formatters" : {
"verbose" : {
"format" : "{levelname} {asctime} {module} {message}" ,
"style" : "{" ,
} ,
"simple" : {
"format" : "{levelname} {message}" ,
"style" : "{" ,
} ,
} ,
"handlers" : {
"console" : {
"level" : "DEBUG" ,
"class" : "logging.StreamHandler" ,
"formatter" : "verbose" ,
} ,
} ,
"loggers" : {
"django" : {
"handlers" : [ "console" ] ,
"level" : "DEBUG" ,
"propagate" : True,
} ,
} ,
}
INTERNAL_IPS = [
"127.0.0.1" ,
]
M I D D L E W A R E . a p p e n d ( "debug_toolbar.middleware.DebugToolbarMiddleware" ) # noqa
M I D D L E W A R E . a p p e n d ( "hijack.middleware.HijackUserMiddleware" ) # noqa
I N S T A L L E D _ A P P S . a p p e n d ( "django.contrib.admindocs" ) # noqa
SECRET_KEY = " $( DJANGO_SETTINGS_SECRET_KEY) "
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ H O M E _ P A G E
I N S T A L L E D _ A P P S . a p p e n d ( "home" )
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ I N S T A L L E D _ A P P S
I N S T A L L E D _ A P P S . a p p e n d ( "allauth" )
I N S T A L L E D _ A P P S . a p p e n d ( "allauth.account" )
I N S T A L L E D _ A P P S . a p p e n d ( "allauth.socialaccount" )
I N S T A L L E D _ A P P S . a p p e n d ( "crispy_bootstrap5" )
I N S T A L L E D _ A P P S . a p p e n d ( "crispy_forms" )
I N S T A L L E D _ A P P S . a p p e n d ( "debug_toolbar" )
I N S T A L L E D _ A P P S . a p p e n d ( "django_extensions" )
I N S T A L L E D _ A P P S . a p p e n d ( "django_recaptcha" )
I N S T A L L E D _ A P P S . a p p e n d ( "rest_framework" )
I N S T A L L E D _ A P P S . a p p e n d ( "rest_framework.authtoken" )
I N S T A L L E D _ A P P S . a p p e n d ( "webpack_boilerplate" )
I N S T A L L E D _ A P P S . a p p e n d ( "explorer" )
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ M I D D L E W A R E
M I D D L E W A R E . a p p e n d ( "allauth.account.middleware.AccountMiddleware" )
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ M O D E L _ F O R M _ D E M O
I N S T A L L E D _ A P P S . a p p e n d ( "model_form_demo" ) # noqa
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ P A Y M E N T S
DJSTRIPE_FOREIGN_KEY_TO_FIELD = "id"
DJSTRIPE_WEBHOOK_VALIDATION = "retrieve_event"
STRIPE_PUBLISHABLE_KEY = os.environ.get( "STRIPE_PUBLISHABLE_KEY" )
STRIPE_SECRET_KEY = os.environ.get( "STRIPE_SECRET_KEY" )
STRIPE_TEST_SECRET_KEY = os.environ.get( "STRIPE_TEST_SECRET_KEY" )
I N S T A L L E D _ A P P S . a p p e n d ( "payments" ) # noqa
I N S T A L L E D _ A P P S . a p p e n d ( "djstripe" ) # noqa
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ R E S T _ F R A M E W O R K
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
"DEFAULT_PERMISSION_CLASSES" : [
"rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly"
]
}
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ S I T E U S E R
I N S T A L L E D _ A P P S . a p p e n d ( "siteuser" ) # noqa
AUTH_USER_MODEL = "siteuser.User"
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ P R O D
f r o m . b a s e i m p o r t * # noqa
f r o m b a c k e n d . u t i l s i m p o r t g e t _ e c 2 _ m e t a d a t a
DEBUG = False
try :
from .local import * # noqa
except ImportError :
pass
LOCAL_IPV4 = get_ec2_metadata( )
A L L O W E D _ H O S T S . a p p e n d ( L O C A L _ I P V 4 ) # noqa
e n d e f
d e f i n e D J A N G O _ S E T T I N G S _ T H E M E S
THEMES = [
( "light" , "Light Theme" ) ,
( "dark" , "Dark Theme" ) ,
]
e n d e f
d e f i n e D J A N G O _ S I T E U S E R _ A D M I N
f r o m d j a n g o . c o n t r i b . a u t h . a d m i n i m p o r t U s e r A d m i n
f r o m d j a n g o . c o n t r i b i m p o r t a d m i n
f r o m . m o d e l s i m p o r t U s e r
a d m i n . s i t e . r e g i s t e r ( U s e r , U s e r A d m i n )
e n d e f
d e f i n e D J A N G O _ S I T E U S E R _ E D I T _ T E M P L A T E
{ % e x t e n d s 'base.html' % }
{ % l o a d c r i s p y _ f o r m s _ t a g s % }
{ % b l o c k c o n t e n t % }
<h2>Edit User</h2>
{ % crispy form %}
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ S I T E U S E R _ F O R M
f r o m d j a n g o i m p o r t f o r m s
f r o m d j a n g o . c o n t r i b . a u t h . f o r m s i m p o r t U s e r C h a n g e F o r m
f r o m c r i s p y _ f o r m s . h e l p e r i m p o r t F o r m H e l p e r
f r o m c r i s p y _ f o r m s . l a y o u t i m p o r t L a y o u t , F i e l d s e t , B u t t o n H o l d e r , S u b m i t
f r o m . m o d e l s i m p o r t U s e r
class SiteUserForm(UserChangeForm) :
bio = forms.CharField( widget = forms.Textarea( attrs = { "id" : "editor" } ) , required = False)
class Meta( UserChangeForm.Meta) :
model = User
fields = ( "username" , "user_theme_preference" , "bio" , "rate" )
def __init__( self, *args, **kwargs) :
super( ) .__init__( *args, **kwargs)
self.helper = FormHelper( )
self.helper.form_method = "post"
self.helper.layout = Layout(
Fieldset(
"Edit Your Profile" ,
"username" ,
"user_theme_preference" ,
"bio" ,
"rate" ,
) ,
ButtonHolder( Submit( "submit" , "Save" , css_class = "btn btn-primary" ) ) ,
)
e n d e f
d e f i n e D J A N G O _ S I T E U S E R _ M O D E L
f r o m d j a n g o . d b i m p o r t m o d e l s
f r o m d j a n g o . c o n t r i b . a u t h . m o d e l s i m p o r t A b s t r a c t U s e r , G r o u p , P e r m i s s i o n
f r o m d j a n g o . c o n f i m p o r t s e t t i n g s
class User(AbstractUser) :
groups = models.ManyToManyField( Group, related_name = "siteuser_set" , blank = True)
user_permissions = models.ManyToManyField(
Permission, related_name = "siteuser_set" , blank = True
)
user_theme_preference = models.CharField(
max_length = 10, choices = settings.THEMES, default = "light"
)
bio = models.TextField( blank = True, null = True)
rate = models.FloatField( blank = True, null = True)
e n d e f
d e f i n e D J A N G O _ S I T E U S E R _ U R L S
f r o m d j a n g o . u r l s i m p o r t p a t h
f r o m . v i e w s i m p o r t U s e r P r o f i l e V i e w , U p d a t e T h e m e P r e f e r e n c e V i e w , U s e r E d i t V i e w
urlpatterns = [
path( "profile/" , UserProfileView.as_view( ) , name = "user-profile" ) ,
path(
"update_theme_preference/" ,
UpdateThemePreferenceView.as_view( ) ,
name = "update_theme_preference" ,
) ,
path( "<int:pk>/edit/" , UserEditView.as_view( ) , name = "user-edit" ) ,
]
e n d e f
d e f i n e D J A N G O _ S I T E U S E R _ V I E W
i m p o r t j s o n
f r o m d j a n g o . c o n t r i b . a u t h . m i x i n s i m p o r t L o g i n R e q u i r e d M i x i n
f r o m d j a n g o . h t t p i m p o r t J s o n R e s p o n s e
f r o m d j a n g o . u t i l s . d e c o r a t o r s i m p o r t m e t h o d _ d e c o r a t o r
f r o m d j a n g o . v i e w s i m p o r t V i e w
f r o m d j a n g o . v i e w s . d e c o r a t o r s . c s r f i m p o r t c s r f _ e x e m p t
f r o m d j a n g o . v i e w s . g e n e r i c i m p o r t D e t a i l V i e w
f r o m d j a n g o . v i e w s . g e n e r i c . e d i t i m p o r t U p d a t e V i e w
f r o m d j a n g o . u r l s i m p o r t r e v e r s e _ l a z y
f r o m . m o d e l s i m p o r t U s e r
f r o m . f o r m s i m p o r t S i t e U s e r F o r m
class UserProfileView(LoginRequiredMixin, DetailView) :
model = User
template_name = "profile.html"
def get_object( self, queryset = None) :
return self.request.user
@ m e t h o d _ d e c o r a t o r ( c s r f _ e x e m p t , name = "dispatch" )
class UpdateThemePreferenceView(View) :
def post( self, request, *args, **kwargs) :
try:
data = json.loads( request.body.decode( "utf-8" ) )
new_theme = data.get( "theme" )
user = request.user
user.user_theme_preference = new_theme
user.save( )
response_data = { "theme" : new_theme}
return JsonResponse( response_data)
except json.JSONDecodeError as e:
return JsonResponse( { "error" : e} , status = 400)
def http_method_not_allowed( self, request, *args, **kwargs) :
return JsonResponse( { "error" : "Invalid request method" } , status = 405)
class UserEditView(LoginRequiredMixin, UpdateView) :
model = User
template_name = "user_edit.html" # Create this template in your templates folder
form_class = SiteUserForm
def get_success_url( self) :
# return reverse_lazy("user-profile", kwargs={"pk": self.object.pk})
return reverse_lazy( "user-profile" )
e n d e f
d e f i n e D J A N G O _ S I T E U S E R _ V I E W _ T E M P L A T E
{ % e x t e n d s 'base.html' % }
{ % b l o c k c o n t e n t % }
<h2>User Profile</h2>
<div class = "d-flex justify-content-end" >
<a class = "btn btn-outline-secondary"
href = "{% url 'user-edit' pk=user.id %}" >Edit</a>
</div>
<p>Username: { { user.username } } </p>
<p>Theme: { { user.user_theme_preference } } </p>
<p>Bio: { { user.bio| default:"" | safe } } </p>
<p>Rate: { { user.rate| default:"" } } </p>
{ % e n d b l o c k % }
e n d e f
d e f i n e D J A N G O _ U R L S
f r o m d j a n g o . c o n t r i b i m p o r t a d m i n
f r o m d j a n g o . u r l s i m p o r t p a t h , i n c l u d e
f r o m d j a n g o . c o n f i m p o r t s e t t i n g s
urlpatterns = [
path( "django/" , admin.site.urls) ,
]
e n d e f
d e f i n e D J A N G O _ U R L S _ A L L A U T H
urlpatterns += [ path( "accounts/" , include( "allauth.urls" ) ) ]
e n d e f
d e f i n e D J A N G O _ U R L S _ A P I
f r o m r e s t _ f r a m e w o r k i m p o r t r o u t e r s # noqa
f r o m . a p i i m p o r t U s e r V i e w S e t , a p i # noqa
router = routers.DefaultRouter( )
r o u t e r . r e g i s t e r ( r "users" , U s e r V i e w S e t )
# urlpatterns += [path("api/", include(router.urls))]
urlpatterns += [ path( "api/" , api.urls) ]
e n d e f
d e f i n e D J A N G O _ U R L S _ D E B U G _ T O O L B A R
if settings.DEBUG :
urlpatterns += [ path( "__debug__/" , include( "debug_toolbar.urls" ) ) ]
e n d e f
d e f i n e D J A N G O _ U R L S _ H O M E _ P A G E
urlpatterns += [ path( "" , include( "home.urls" ) ) ]
e n d e f
d e f i n e D J A N G O _ U R L S _ L O G G I N G _ D E M O
urlpatterns += [ path( "logging-demo/" , include( "logging_demo.urls" ) ) ]
e n d e f
d e f i n e D J A N G O _ U R L S _ M O D E L _ F O R M _ D E M O
urlpatterns += [ path( "model-form-demo/" , include( "model_form_demo.urls" ) ) ]
e n d e f
d e f i n e D J A N G O _ U R L S _ P A Y M E N T S
urlpatterns += [ path( "payments/" , include( "payments.urls" ) ) ]
e n d e f
d e f i n e D J A N G O _ U R L S _ S I T E U S E R
urlpatterns += [ path( "user/" , include( "siteuser.urls" ) ) ]
e n d e f
d e f i n e D J A N G O _ U T I L S
f r o m d j a n g o . u r l s i m p o r t U R L R e s o l v e r
i m p o r t r e q u e s t s
def get_ec2_metadata() :
try:
# Step 1: Get the token
token_url = "http://169.254.169.254/latest/api/token"
headers = { "X-aws-ec2-metadata-token-ttl-seconds" : "21600" }
response = requests.put( token_url, headers = headers)
response.raise_for_status( ) # Raise an error for bad responses
token = response.text
# Step 2: Use the token to get the instance metadata
metadata_url = "http://169.254.169.254/latest/meta-data/local-ipv4"
headers = { "X-aws-ec2-metadata-token" : token}
response = requests.get( metadata_url, headers = headers)
response.raise_for_status( ) # Raise an error for bad responses
metadata = response.text
return metadata
except requests.RequestException as e:
print( f"Error retrieving EC2 metadata: {e}" )
return None
# Function to remove a specific URL pattern based on its route (including catch-all)
def remove_urlpattern(urlpatterns, route_to_remove) :
urlpatterns[ :] = [
urlpattern
for urlpattern in urlpatterns
if not (
isinstance( urlpattern, URLResolver)
and urlpattern.pattern._route = = route_to_remove
)
]
e n d e f
d e f i n e E B _ C U S T O M _ E N V _ E C 2 _ U S E R
files :
"/home/ec2-user/.bashrc" :
mode: "000644"
owner: ec2-user
group: ec2-user
content: |
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ] ; then
. /etc/bashrc
fi
# User specific aliases and functions
set -o vi
source <( sed -E -n 's/[^#]+/export &/ p' /opt/elasticbeanstalk/deployment/custom_env_var)
e n d e f
d e f i n e E B _ C U S T O M _ E N V _ V A R _ F I L E
#!/bin/bash
# Via https://aws.amazon.com/premiumsupport/knowledge-center/elastic-beanstalk-env-variables-linux2/
#Create a copy of the environment variable file.
c a t / o p t / e l a s t i c b e a n s t a l k / d e p l o y m e n t / e n v | p e r l - p - e 's/(.*)=(.*)/export $$1="$$2"/;' > / o p t / e l a s t i c b e a n s t a l k / d e p l o y m e n t / c u s t o m _ e n v _ v a r
#Set permissions to the custom_env_var file so this file can be accessed by any user on the instance. You can restrict permissions as per your requirements.
c h m o d 6 4 4 / o p t / e l a s t i c b e a n s t a l k / d e p l o y m e n t / c u s t o m _ e n v _ v a r
# add the virtual env path in.
VENV = /var/app/venv/` ls /var/app/venv`
c a t < < E O F > > / o p t / e l a s t i c b e a n s t a l k / d e p l o y m e n t / c u s t o m _ e n v _ v a r
VENV = $$ ENV
E O F
#Remove duplicate files upon deployment.
r m - f / o p t / e l a s t i c b e a n s t a l k / d e p l o y m e n t / * . b a k
e n d e f
d e f i n e G I T _ I G N O R E
_ _ p y c a c h e _ _
* . p y c
d i s t /
n o d e _ m o d u l e s /
_ b u i l d /
. e l a s t i c b e a n s t a l k /
d b . s q l i t e 3
s t a t i c /
b a c k e n d / v a r
e n d e f
d e f i n e J E N K I N S _ F I L E
p i p e l i n e {
agent any
stages {
stage( '' ) {
steps {
echo ''
}
}
}
}
e n d e f
d e f i n e M A K E F I L E _ C U S T O M
# Custom Makefile
# Add your custom makefile commands here
#
# PROJECT_NAME := my-new-project
e n d e f
d e f i n e P I P _ I N S T A L L _ R E Q U I R E M E N T S _ T E S T
p y t e s t
p y t e s t - r u n n e r
c o v e r a g e
p y t e s t - m o c k
p y t e s t - c o v
h y p o t h e s i s
s e l e n i u m
p y t e s t - d j a n g o
f a c t o r y - b o y
f l a k e 8
t o x
e n d e f
d e f i n e P R O G R A M M I N G _ I N T E R V I E W
f r o m r i c h i m p o r t p r i n t a s r p r i n t
f r o m r i c h . c o n s o l e i m p o r t C o n s o l e
f r o m r i c h . p a n e l i m p o r t P a n e l
i m p o r t a r g p a r s e
i m p o r t l o c a l e
i m p o r t m a t h
i m p o r t t i m e
i m p o r t c o d e # noqa
i m p o r t r e a d l i n e # noqa
i m p o r t r l c o m p l e t e r # noqa
l o c a l e . s e t l o c a l e ( l o c a l e . L C _ A L L , "en_US.UTF-8" )
class DataStructure :
# Data Structure: Binary Tree
class TreeNode:
def __init__( self, value = 0, left = None, right = None) :
self.value = value
self.left = left
self.right = right
# Data Structure: Stack
class Stack:
def __init__( self) :
self.items = [ ]
def push( self, item) :
self.items.append( item)
def pop( self) :
if not self.is_empty( ) :
return self.items.pop( )
return None
def peek( self) :
if not self.is_empty( ) :
return self.items[ -1]
return None
def is_empty( self) :
return len( self.items) = = 0
def size( self) :
return len( self.items)
# Data Structure: Queue
class Queue:
def __init__( self) :
self.items = [ ]
def enqueue( self, item) :
self.items.append( item)
def dequeue( self) :
if not self.is_empty( ) :
return self.items.pop( 0)
return None
def is_empty( self) :
return len( self.items) = = 0
def size( self) :
return len( self.items)
# Data Structure: Linked List
class ListNode:
def __init__( self, value = 0, next = None) :
self.value = value
self.next = next
class Interview(DataStructure) :
# Protected methods for factorial calculation
def _factorial_recursive( self, n) :
if n = = 0:
return 1
return n * self._factorial_recursive( n - 1)
def _factorial_divide_and_conquer( self, low, high) :
if low > high:
return 1
if low = = high:
return low
mid = ( low + high) // 2
return self._factorial_divide_and_conquer(
low, mid
) * self._factorial_divide_and_conquer( mid + 1, high)
# Recursive Factorial with Timing
def factorial_recursive( self, n) :
start_time = time.time( ) # Start timing
result = self._factorial_recursive( n) # Calculate factorial
end_time = time.time( ) # End timing
elapsed_time = end_time - start_time
return f" Factorial: {locale.format_string(" %.2f", result, grouping=True)} Elapsed time: {elapsed_time:.6f}"
# Iterative Factorial with Timing
def factorial_iterative( self, n) :
start_time = time.time( ) # Start timing
result = 1
for i in range( 1, n + 1) :
result *= i
end_time = time.time( ) # End timing
elapsed_time = end_time - start_time
return f" Factorial: {locale.format_string(" %.2f", result, grouping=True)} Elapsed time: {elapsed_time:.6f}"
# Divide and Conquer Factorial with Timing
def factorial_divide_and_conquer( self, n) :
start_time = time.time( ) # Start timing
result = self._factorial_divide_and_conquer( 1, n) # Calculate factorial
end_time = time.time( ) # End timing
elapsed_time = end_time - start_time
return f" Factorial: {locale.format_string(" %.2f", result, grouping=True)} Elapsed time: {elapsed_time:.6f}"
# Built-in Factorial with Timing
def factorial_builtin( self, n) :
start_time = time.time( ) # Start timing
result = math.factorial( n) # Calculate factorial using built-in
end_time = time.time( ) # End timing
# Calculate elapsed time
elapsed_time = end_time - start_time
# Print complexity and runtime
return f" Factorial: {locale.format_string(" %.2f", result, grouping=True)} Elapsed time: {elapsed_time:.6f}"
# Recursion: Fibonacci
def fibonacci_recursive( self, n) :
if n <= 1:
return n
return self.fibonacci_recursive( n - 1) + self.fibonacci_recursive( n - 2)
# Iteration: Fibonacci
def fibonacci_iterative( self, n) :
if n <= 1:
return n
a, b = 0, 1
for _ in range( n - 1) :
a, b = b, a + b
return b
# Searching: Linear Search
def linear_search( self, arr, target) :
for i, value in enumerate( arr) :
if value = = target:
return i
return -1
# Searching: Binary Search
def binary_search( self, arr, target) :
left, right = 0, len( arr) - 1
while left <= right:
mid = ( left + right) // 2
if arr[ mid] = = target:
return mid
elif arr[ mid] < target:
left = mid + 1
else :
right = mid - 1
return -1
# Sorting: Bubble Sort
def bubble_sort( self, arr) :
n = len( arr)
for i in range( n) :
for j in range( 0, n - i - 1) :
if arr[ j] > arr[ j + 1] :
arr[ j] , arr[ j + 1] = arr[ j + 1] , arr[ j]
return arr
# Sorting: Merge Sort
def merge_sort( self, arr) :
if len( arr) > 1:
mid = len( arr) // 2
left_half = arr[ :mid]
right_half = arr[ mid:]
self.merge_sort( left_half)
self.merge_sort( right_half)
i = j = k = 0
while i < len( left_half) and j < len( right_half) :
if left_half[ i] < right_half[ j] :
arr[ k] = left_half[ i]
i += 1
else :
arr[ k] = right_half[ j]
j += 1
k += 1
while i < len( left_half) :
arr[ k] = left_half[ i]
i += 1
k += 1
while j < len( right_half) :
arr[ k] = right_half[ j]
j += 1
k += 1
return arr
def insert_linked_list( self, head, value) :
new_node = self.ListNode( value)
if not head:
return new_node
current = head
while current.next:
current = current.next
current.next = new_node
return head
def print_linked_list( self, head) :
current = head
while current:
print( current.value, end = " -> " )
current = current.next
print( "None" )
def inorder_traversal( self, root) :
return (
self.inorder_traversal( root.left)
+ [ root.value]
+ self.inorder_traversal( root.right)
if root
else [ ]
)
def preorder_traversal( self, root) :
return (
[ root.value]
+ self.preorder_traversal( root.left)
+ self.preorder_traversal( root.right)
if root
else [ ]
)
def postorder_traversal( self, root) :
return (
self.postorder_traversal( root.left)
+ self.postorder_traversal( root.right)
+ [ root.value]
if root
else [ ]
)
# Graph Algorithms: Depth-First Search
def dfs( self, graph, start) :
visited, stack = set( ) , [ start]
while stack:
vertex = stack.pop( )
if vertex not in visited:
visited.add( vertex)
stack.extend( set( graph[ vertex] ) - visited)
return visited
# Graph Algorithms: Breadth-First Search
def bfs( self, graph, start) :
visited, queue = set( ) , [ start]
while queue:
vertex = queue.pop( 0)
if vertex not in visited:
visited.add( vertex)
queue.extend( set( graph[ vertex] ) - visited)
return visited
def setup_readline(local) :
# Enable tab completion
readline.parse_and_bind( "tab: complete" )
# Optionally, you can set the completer function manually
readline.set_completer( rlcompleter.Completer( local ) .complete)
def main() :
console = Console( )
interview = Interview( )
parser = argparse.ArgumentParser( description = "Programming Interview Questions" )
parser.add_argument(
"-f" , "--factorial" , type = int, help = "Factorial algorithm examples"
)
parser.add_argument( "--fibonacci" , type = int, help = "Fibonacci algorithm examples" )
parser.add_argument(
"--search" , action = "store_true" , help = "Search algorithm examples"
)
parser.add_argument( "--sort" , action = "store_true" , help = "Search algorithm examples" )
parser.add_argument( "--stack" , action = "store_true" , help = "Stack algorithm examples" )
parser.add_argument( "--queue" , action = "store_true" , help = "Queue algorithm examples" )
parser.add_argument(
"--list" , action = "store_true" , help = "Linked List algorithm examples"
)
parser.add_argument(
"--tree" , action = "store_true" , help = "Tree traversal algorithm examples"
)
parser.add_argument( "--graph" , action = "store_true" , help = "Graph algorithm examples" )
parser.add_argument(
"-i" , "--interactive" , action = "store_true" , help = "Interactive mode"
)
args = parser.parse_args( )
if args.factorial:
# Factorial examples
console.rule( "Factorial Examples" )
rprint(
Panel(
"[bold cyan]Recursive Factorial - Time Complexity: O(n)[/bold cyan]\n"
+ str( interview.factorial_recursive( args.factorial) ) ,
title = "Factorial Recursive" ,
)
)
rprint(
Panel(
"[bold cyan]Iterative Factorial - Time Complexity: O(n)[/bold cyan]\n"
+ str( interview.factorial_iterative( args.factorial) ) ,
title = "Factorial Iterative" ,
)
)
rprint(
Panel(
"[bold cyan]Built-in Factorial - Time Complexity: O(n)[/bold cyan]\n"
+ str( interview.factorial_builtin( args.factorial) ) ,
title = "Factorial Built-in" ,
)
)
rprint(
Panel(
"[bold cyan]Divide and Conquer Factorial - Time Complexity: O(n log n)[/bold cyan]\n"
+ str( interview.factorial_divide_and_conquer( args.factorial) ) ,
title = "Factorial Divide and Conquer" ,
)
)
exit( )
if args.fibonacci:
# Fibonacci examples
console.rule( "Fibonacci Examples" )
rprint(
Panel(
str( interview.fibonacci_recursive( args.fibonacci) ) ,
title = "Fibonacci Recursive" ,
)
)
rprint(
Panel(
str( interview.fibonacci_iterative( args.fibonacci) ) ,
title = "Fibonacci Iterative" ,
)
)
exit( )
if args.search:
# Searching examples
console.rule( "Searching Examples" )
array = [ 1, 3, 5, 7, 9]
rprint( Panel( str( interview.linear_search( array, 5) ) , title = "Linear Search" ) )
rprint( Panel( str( interview.binary_search( array, 5) ) , title = "Binary Search" ) )
exit( )
if args.sort:
# Sorting examples
console.rule( "Sorting Examples" )
unsorted_array = [ 64, 34, 25, 12, 22, 11, 90]
rprint(
Panel(
str( interview.bubble_sort( unsorted_array.copy( ) ) ) , title = "Bubble Sort"
)
)
rprint(
Panel( str( interview.merge_sort( unsorted_array.copy( ) ) ) , title = "Merge Sort" )
)
exit( )
if args.stack:
# Stack example
console.rule( "Stack Example" )
stack = interview.Stack( )
stack.push( 1)
stack.push( 2)
stack.push( 3)
rprint( Panel( str( stack.pop( ) ) , title = "Stack Pop" ) )
rprint( Panel( str( stack.peek( ) ) , title = "Stack Peek" ) )
rprint( Panel( str( stack.size( ) ) , title = "Stack Size" ) )
if args.queue:
# Queue example
console.rule( "Queue Example" )
queue = interview.Queue( )
queue.enqueue( 1)
queue.enqueue( 2)
queue.enqueue( 3)
rprint( Panel( str( queue.dequeue( ) ) , title = "Queue Dequeue" ) )
rprint( Panel( str( queue.is_empty( ) ) , title = "Queue Is Empty" ) )
rprint( Panel( str( queue.size( ) ) , title = "Queue Size" ) )
if args.list:
# Linked List example
console.rule( "Linked List Example" )
head = None
head = interview.insert_linked_list( head, 1)
head = interview.insert_linked_list( head, 2)
head = interview.insert_linked_list( head, 3)
interview.print_linked_list( head) # Output: 1 -> 2 -> 3 -> None
if args.tree:
# Tree Traversal example
console.rule( "Tree Traversal Example" )
root = interview.TreeNode( 1)
root.left = interview.TreeNode( 2)
root.right = interview.TreeNode( 3)
root.left.left = interview.TreeNode( 4)
root.left.right = interview.TreeNode( 5)
rprint( Panel( str( interview.inorder_traversal( root) ) , title = "Inorder Traversal" ) )
rprint(
Panel( str( interview.preorder_traversal( root) ) , title = "Preorder Traversal" )
)
rprint(
Panel( str( interview.postorder_traversal( root) ) , title = "Postorder Traversal" )
)
if args.graph:
# Graph Algorithms example
console.rule( "Graph Algorithms Example" )
graph = {
"A" : [ "B" , "C" ] ,
"B" : [ "A" , "D" , "E" ] ,
"C" : [ "A" , "F" ] ,
"D" : [ "B" ] ,
"E" : [ "B" , "F" ] ,
"F" : [ "C" , "E" ] ,
}
rprint( Panel( str( interview.dfs( graph, "A" ) ) , title = "DFS" ) )
rprint( Panel( str( interview.bfs( graph, "A" ) ) , title = "BFS" ) )
if args.interactive:
# Starting interactive session with tab completion
setup_readline( locals( ) )
banner = "Interactive programming interview session started. Type 'exit()' or 'Ctrl-D' to exit."
code.interact(
banner = banner,
local = locals( ) ,
exitmsg = "Great interview!" ,
)
if __name__ == "__main__" :
main( )
e n d e f
d e f i n e P Y T H O N _ C I _ Y A M L
name : Build Wheels
e n d e f
d e f i n e P Y T H O N _ L I C E N S E _ T X T
M I T L i c e n s e
C o p y r i g h t ( c ) [ Y E A R ] [ O W N E R N A M E ]
P e r m i s s i o n i s h e r e b y g r a n t e d , f r e e o f c h a r g e , t o a n y p e r s o n o b t a i n i n g a c o p y
o f t h i s s o f t w a r e a n d a s s o c i a t e d d o c u m e n t a t i o n f i l e s ( t h e "Software" ) , t o d e a l
i n t h e S o f t w a r e w i t h o u t r e s t r i c t i o n , i n c l u d i n g w i t h o u t l i m i t a t i o n t h e r i g h t s
t o u s e , c o p y , m o d i f y , m e r g e , p u b l i s h , d i s t r i b u t e , s u b l i c e n s e , a n d / o r s e l l
c o p i e s o f t h e S o f t w a r e , a n d t o p e r m i t p e r s o n s t o w h o m t h e S o f t w a r e i s
furnished to do so, subject to the following conditions :
T h e a b o v e c o p y r i g h t n o t i c e a n d t h i s p e r m i s s i o n n o t i c e s h a l l b e i n c l u d e d i n a l l
c o p i e s o r s u b s t a n t i a l p o r t i o n s o f t h e S o f t w a r e .
T H E S O F T W A R E I S P R O V I D E D "AS IS" , W I T H O U T W A R R A N T Y O F A N Y K I N D , E X P R E S S O R
I M P L I E D , I N C L U D I N G B U T N O T L I M I T E D T O T H E W A R R A N T I E S O F M E R C H A N T A B I L I T Y ,
F I T N E S S F O R A P A R T I C U L A R P U R P O S E A N D N O N I N F R I N G E M E N T . I N N O E V E N T S H A L L T H E
A U T H O R S O R C O P Y R I G H T H O L D E R S B E L I A B L E F O R A N Y C L A I M , D A M A G E S O R O T H E R
L I A B I L I T Y , W H E T H E R I N A N A C T I O N O F C O N T R A C T , T O R T O R O T H E R W I S E , A R I S I N G F R O M ,
O U T O F O R I N C O N N E C T I O N W I T H T H E S O F T W A R E O R T H E U S E O R O T H E R D E A L I N G S I N T H E
S O F T W A R E .
e n d e f
d e f i n e P Y T H O N _ P R O J E C T _ T O M L
[ b u i l d - s y s t e m ]
e n d e f
d e f i n e S E P A R A T O R
. = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = .
| |
| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | |
| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | |
| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | |
| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | |
| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | |
| _ | |
| _ | |
` = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = '
e n d e f
d e f i n e T I N Y M C E _ J S
i m p o r t t i n y m c e f r o m 'tinymce' ;
i m p o r t 'tinymce/icons/default' ;
i m p o r t 'tinymce/themes/silver' ;
i m p o r t 'tinymce/skins/ui/oxide/skin.css' ;
i m p o r t 'tinymce/plugins/advlist' ;
i m p o r t 'tinymce/plugins/code' ;
i m p o r t 'tinymce/plugins/emoticons' ;
i m p o r t 'tinymce/plugins/emoticons/js/emojis' ;
i m p o r t 'tinymce/plugins/link' ;
i m p o r t 'tinymce/plugins/lists' ;
i m p o r t 'tinymce/plugins/table' ;
i m p o r t 'tinymce/models/dom' ;
t i n y m c e . i n i t ( {
selector: 'textarea#editor' ,
plugins: 'advlist code emoticons link lists table' ,
toolbar: 'bold italic | bullist numlist | link emoticons' ,
skin: false,
content_css: false,
} ) ;
e n d e f
d e f i n e W A G T A I L _ B A S E _ T E M P L A T E
{ % l o a d s t a t i c w a g t a i l c o r e _ t a g s w a g t a i l u s e r b a r w e b p a c k _ l o a d e r % }
< ! D O C T Y P E h t m l >
< h t m l lang = "en"
class = "h-100"
data-bs-theme= "{{ request.user.user_theme_preference|default:'light' }}" >
<head>
<meta charset = "utf-8" />
<title>
{ % block title %}
{ % if page.seo_title %}
{ { page.seo_title } }
{ % else %}
{ { page.title } }
{ % endif %}
{ % endblock %}
{ % block title_suffix %}
{ % wagtail_site as current_site %}
{ % if current_site and current_site.site_name %} - { { current_site.site_name } } { % endif %}
{ % endblock %}
</title>
{ % if page.search_description %} <meta name = "description" content = "{{ page.search_description }}" />{ % endif %}
<meta name = "viewport" content = "width=device-width, initial-scale=1" />
{ # Force all links in the live preview panel to be opened in a new tab #}
{ % if request.in_preview_panel %} <base target = "_blank" >{ % endif %}
{ % stylesheet_pack 'app' %}
{ % block extra_css %} { # Override this in templates to add extra stylesheets #}{% endblock %}
<style>
.success {
background-color: #d4edda;
border-color: #c3e6cb;
color: #155724;
}
.info {
background-color: #d1ecf1;
border-color: #bee5eb;
color: #0c5460;
}
.warning {
background-color: #fff3cd;
border-color: #ffeeba;
color: #856404;
}
.danger {
background-color: #f8d7da;
border-color: #f5c6cb;
color: #721c24;
}
</style>
{ % include 'favicon.html' %}
{ % csrf_token %}
</head>
<body class = "{% block body_class %}{% endblock %} d-flex flex-column h-100" >
<main class = "flex-shrink-0" >
{ % wagtailuserbar %}
<div id = "app" ></div>
{ % include 'header.html' %}
{ % if messages %}
<div class = "messages container" >
{ % for message in messages %}
<div class = "alert {{ message.tags }} alert-dismissible fade show"
role = "alert" >
{ { message } }
<button type = "button"
class = "btn-close"
data-bs-dismiss= "alert"
aria-label= "Close" ></button>
</div>
{ % endfor %}
</div>
{ % endif %}
<div class = "container" >
{ % block content %} { % endblock %}
</div>
</main>
{ % include 'footer.html' %}
{ % include 'offcanvas.html' %}
{ % javascript_pack 'app' %}
{ % block extra_js %} { # Override this in templates to add extra javascript #}{% endblock %}
</body>
< / h t m l >
e n d e f
d e f i n e W A G T A I L _ B L O C K _ C A R O U S E L
< d i v id = "carouselExampleCaptions" class = "carousel slide" >
<div class = "carousel-indicators" >
{ % for image in block.value.images %}
<button type = "button"
data-bs-target= "#carouselExampleCaptions"
data-bs-slide-to= "{{ forloop.counter0 }}"
{ % if forloop.first %} class = "active" aria-current= "true" { % endif %}
aria-label= "Slide {{ forloop.counter }}" ></button>
{ % endfor %}
</div>
<div class = "carousel-inner" >
{ % for image in block.value.images %}
<div class = "carousel-item {% if forloop.first %}active{% endif %}" >
<img src = "{{ image.file.url }}" class = "d-block w-100" alt = "..." >
<div class = "carousel-caption d-none d-md-block" >
<h5>{ { image.title } } </h5>
</div>
</div>
{ % endfor %}
</div>
<button class = "carousel-control-prev"
type = "button"
data-bs-target= "#carouselExampleCaptions"
data-bs-slide= "prev" >
<span class = "carousel-control-prev-icon" aria-hidden= "true" ></span>
<span class = "visually-hidden" >Previous</span>
</button>
<button class = "carousel-control-next"
type = "button"
data-bs-target= "#carouselExampleCaptions"
data-bs-slide= "next" >
<span class = "carousel-control-next-icon" aria-hidden= "true" ></span>
<span class = "visually-hidden" >Next</span>
</button>
< / d i v >
e n d e f
d e f i n e W A G T A I L _ B L O C K _ M A R K E T I N G
{ % l o a d w a g t a i l c o r e _ t a g s % }
< d i v class = "{{ self.block_class }}" >
{ % if block.value.images.0 %}
{ % include 'blocks/carousel_block.html' %}
{ % else %}
{ { self.title } }
{ { self.content } }
{ % endif %}
< / d i v >
e n d e f
d e f i n e W A G T A I L _ C O N T A C T _ P A G E _ L A N D I N G
{ % e x t e n d s 'base.html' % }
{ % b l o c k c o n t e n t % } < d i v class = "container" ><h1>Thank you!</h1></div>{ % endblock %}
e n d e f
d e f i n e W A G T A I L _ C O N T A C T _ P A G E _ M O D E L
f r o m d j a n g o . d b i m p o r t m o d e l s
f r o m m o d e l c l u s t e r . f i e l d s i m p o r t P a r e n t a l K e y
f r o m w a g t a i l . a d m i n . p a n e l s i m p o r t (
FieldPanel, FieldRowPanel,
InlinePanel, MultiFieldPanel
)
f r o m w a g t a i l . f i e l d s i m p o r t R i c h T e x t F i e l d
f r o m w a g t a i l . c o n t r i b . f o r m s . m o d e l s i m p o r t A b s t r a c t E m a i l F o r m , A b s t r a c t F o r m F i e l d
class FormField(AbstractFormField) :
page = ParentalKey( 'ContactPage' , on_delete = models.CASCADE, related_name = 'form_fields' )
class ContactPage(AbstractEmailForm) :
intro = RichTextField( blank = True)
thank_you_text = RichTextField( blank = True)
content_panels = AbstractEmailForm.content_panels + [
FieldPanel( 'intro' ) ,
InlinePanel( 'form_fields' , label = "Form fields" ) ,
FieldPanel( 'thank_you_text' ) ,
MultiFieldPanel( [
FieldRowPanel( [
FieldPanel( 'from_address' , classname = "col6" ) ,
FieldPanel( 'to_address' , classname = "col6" ) ,
] ) ,
FieldPanel( 'subject' ) ,
] , "Email" ) ,
]
class Meta:
verbose_name = "Contact Page"
e n d e f
d e f i n e W A G T A I L _ C O N T A C T _ P A G E _ T E M P L A T E
{ % e x t e n d s 'base.html' % }
{ % l o a d c r i s p y _ f o r m s _ t a g s s t a t i c w a g t a i l c o r e _ t a g s % }
{ % b l o c k c o n t e n t % }
<h1>{ { page.title } } </h1>
{ { page.intro| richtext } }
<form action = "{% pageurl page %}" method = "POST" >
{ % csrf_token %}
{ { form.as_p } }
<input type = "submit" >
</form>
{ % e n d b l o c k % }
e n d e f
d e f i n e W A G T A I L _ C O N T A C T _ P A G E _ T E S T
f r o m d j a n g o . t e s t i m p o r t T e s t C a s e
f r o m w a g t a i l . t e s t . u t i l s i m p o r t W a g t a i l P a g e T e s t C a s e
f r o m w a g t a i l . m o d e l s i m p o r t P a g e
f r o m c o n t a c t p a g e . m o d e l s i m p o r t C o n t a c t P a g e , F o r m F i e l d
class ContactPageTest(TestCase, WagtailPageTestCase) :
def test_contact_page_creation( self) :
# Create a ContactPage instance
contact_page = ContactPage(
title = 'Contact' ,
intro = 'Welcome to our contact page!' ,
thank_you_text = 'Thank you for reaching out.'
)
# Save the ContactPage instance
self.assertEqual( contact_page.save_revision( ) .publish( ) .get_latest_revision_as_page( ) , contact_page)
def test_form_field_creation( self) :
# Create a ContactPage instance
contact_page = ContactPage(
title = 'Contact' ,
intro = 'Welcome to our contact page!' ,
thank_you_text = 'Thank you for reaching out.'
)
# Save the ContactPage instance
contact_page_revision = contact_page.save_revision( )
contact_page_revision.publish( )
# Create a FormField associated with the ContactPage
form_field = FormField(
page = contact_page,
label = 'Your Name' ,
field_type = 'singleline' ,
required = True
)
form_field.save( )
# Retrieve the ContactPage from the database
contact_page_from_db = Page.objects.get( id = contact_page.id) .specific
# Check if the FormField is associated with the ContactPage
self.assertEqual( contact_page_from_db.form_fields.first( ) , form_field)
def test_contact_page_form_submission( self) :
# Create a ContactPage instance
contact_page = ContactPage(
title = 'Contact' ,
intro = 'Welcome to our contact page!' ,
thank_you_text = 'Thank you for reaching out.'
)
# Save the ContactPage instance
contact_page_revision = contact_page.save_revision( )
contact_page_revision.publish( )
# Simulate a form submission
form_data = {
'your_name' : 'John Doe' ,
# Add other form fields as needed
}
response = self.client.post( contact_page.url, form_data)
# Check if the form submission is successful (assuming a 302 redirect)
self.assertEqual( response.status_code, 302)
# You may add more assertions based on your specific requirements
e n d e f
d e f i n e W A G T A I L _ H E A D E R _ P R E F I X
{ % l o a d w a g t a i l c o r e _ t a g s % }
{ % w a g t a i l _ s i t e a s c u r r e n t _ s i t e % }
e n d e f
d e f i n e W A G T A I L _ H O M E _ P A G E _ M O D E L
f r o m w a g t a i l . m o d e l s i m p o r t P a g e
f r o m w a g t a i l . f i e l d s i m p o r t S t r e a m F i e l d
f r o m w a g t a i l i m p o r t b l o c k s
f r o m w a g t a i l . a d m i n . p a n e l s i m p o r t F i e l d P a n e l
f r o m w a g t a i l . i m a g e s . b l o c k s i m p o r t I m a g e C h o o s e r B l o c k
class MarketingBlock(blocks.StructBlock) :
title = blocks.CharBlock( required = False, help_text = "Enter the block title" )
content = blocks.RichTextBlock( required = False, help_text = "Enter the block content" )
images = blocks.ListBlock(
ImageChooserBlock( required = False) ,
help_text = "Select one or two images for column display. Select three or more images for carousel display." ,
)
image = ImageChooserBlock(
required = False, help_text = "Select one image for background display."
)
block_class = blocks.CharBlock(
required = False,
help_text = "Enter a CSS class for styling the marketing block" ,
classname = "full title" ,
default = "vh-100 bg-secondary" ,
)
image_class = blocks.CharBlock(
required = False,
help_text = "Enter a CSS class for styling the column display image(s)" ,
classname = "full title" ,
default = "img-thumbnail p-5" ,
)
layout_class = blocks.CharBlock(
required = False,
help_text = "Enter a CSS class for styling the layout." ,
classname = "full title" ,
default = "d-flex flex-row" ,
)
class Meta:
icon = "placeholder"
template = "blocks/marketing_block.html"
class HomePage(Page) :
template = "home/home_page.html" # Create a template for rendering the home page
marketing_blocks = StreamField(
[
( "marketing_block" , MarketingBlock( ) ) ,
] ,
blank = True,
null = True,
use_json_field = True,
)
content_panels = Page.content_panels + [
FieldPanel( "marketing_blocks" ) ,
]
class Meta:
verbose_name = "Home Page"
e n d e f
d e f i n e W A G T A I L _ H O M E _ P A G E _ T E M P L A T E
{ % e x t e n d s "base.html" % }
{ % l o a d w a g t a i l c o r e _ t a g s % }
{ % b l o c k c o n t e n t % }
<main class = "{% block main_class %}{% endblock %}" >
{ % for block in page.marketing_blocks %}
{ % include_block block %}
{ % endfor %}
</main>
{ % e n d b l o c k % }
e n d e f
d e f i n e W A G T A I L _ P R I V A C Y _ P A G E _ M O D E L
f r o m w a g t a i l . m o d e l s i m p o r t P a g e
f r o m w a g t a i l . a d m i n . p a n e l s i m p o r t F i e l d P a n e l
f r o m w a g t a i l m a r k d o w n . f i e l d s i m p o r t M a r k d o w n F i e l d
class PrivacyPage(Page) :
"" "
A Wagtail Page model for the Privacy Policy page.
"" "
template = "privacy_page.html"
body = MarkdownField( )
content_panels = Page.content_panels + [
FieldPanel( "body" , classname = "full" ) ,
]
class Meta:
verbose_name = "Privacy Page"
e n d e f
d e f i n e W A G T A I L _ P R I V A C Y _ P A G E _ T E M P L A T E
{ % e x t e n d s 'base.html' % }
{ % l o a d w a g t a i l m a r k d o w n % }
{ % b l o c k c o n t e n t % } < d i v class = "container" >{ { page.body| markdown } } </div>{ % endblock %}
e n d e f
d e f i n e W A G T A I L _ S E A R C H _ T E M P L A T E
{ % e x t e n d s "base.html" % }
{ % l o a d s t a t i c w a g t a i l c o r e _ t a g s % }
{ % b l o c k b o d y _ c l a s s % } t e m p l a t e - s e a r c h r e s u l t s { % e n d b l o c k % }
{ % b l o c k t i t l e % } S e a r c h { % e n d b l o c k % }
{ % b l o c k c o n t e n t % }
<h1>Search</h1>
<form action = "{% url 'search' %}" method = "get" >
<input type = "text"
name = "query"
{ % if search_query %} value = "{{ search_query }}" { % endif %} >
<input type = "submit" value = "Search" class = "button" >
</form>
{ % if search_results %}
<ul>
{ % for result in search_results %}
<li>
<h4>
<a href = "{% pageurl result %}" >{ { result } } </a>
</h4>
{ % if result.search_description %} { { result.search_description } } { % endif %}
</li>
{ % endfor %}
</ul>
{ % if search_results.has_previous %}
<a href = "{% url 'search' %}?query={{ search_query|urlencode }}&page={{ search_results.previous_page_number }}" >Previous</a>
{ % endif %}
{ % if search_results.has_next %}
<a href = "{% url 'search' %}?query={{ search_query|urlencode }}&page={{ search_results.next_page_number }}" >Next</a>
{ % endif %}
{ % elif search_query %}
No results found
{ % else %}
No results found. Try a <a href = "?query=test" >test query</a>?
{ % endif %}
{ % e n d b l o c k % }
e n d e f
d e f i n e W A G T A I L _ S E A R C H _ U R L S
f r o m d j a n g o . u r l s i m p o r t p a t h
f r o m . v i e w s i m p o r t s e a r c h
urlpatterns = [ path( "" , search, name = "search" ) ]
e n d e f
d e f i n e W A G T A I L _ S E T T I N G S
I N S T A L L E D _ A P P S . a p p e n d ( "wagtail_color_panel" )
I N S T A L L E D _ A P P S . a p p e n d ( "wagtail_modeladmin" )
I N S T A L L E D _ A P P S . a p p e n d ( "wagtail.contrib.settings" )
I N S T A L L E D _ A P P S . a p p e n d ( "wagtailmarkdown" )
I N S T A L L E D _ A P P S . a p p e n d ( "wagtailmenus" )
I N S T A L L E D _ A P P S . a p p e n d ( "wagtailseo" )
T E M P L A T E S [ 0 ] [ "OPTIONS" ] [ "context_processors" ] . a p p e n d (
"wagtail.contrib.settings.context_processors.settings"
)
T E M P L A T E S [ 0 ] [ "OPTIONS" ] [ "context_processors" ] . a p p e n d (
"wagtailmenus.context_processors.wagtailmenus"
)
e n d e f
d e f i n e W A G T A I L _ S I T E P A G E _ M O D E L
f r o m w a g t a i l . m o d e l s i m p o r t P a g e
class SitePage(Page) :
template = "sitepage/site_page.html"
class Meta:
verbose_name = "Site Page"
e n d e f
d e f i n e W A G T A I L _ S I T E P A G E _ T E M P L A T E
{ % e x t e n d s 'base.html' % }
{ % b l o c k c o n t e n t % }
<h1>{ { page.title } } </h1>
{ % e n d b l o c k % }
e n d e f
d e f i n e W A G T A I L _ U R L S
f r o m d j a n g o . c o n f i m p o r t s e t t i n g s
f r o m d j a n g o . u r l s i m p o r t i n c l u d e , p a t h
f r o m d j a n g o . c o n t r i b i m p o r t a d m i n
f r o m w a g t a i l . a d m i n i m p o r t u r l s a s w a g t a i l a d m i n _ u r l s
f r o m w a g t a i l . d o c u m e n t s i m p o r t u r l s a s w a g t a i l d o c s _ u r l s
f r o m s e a r c h i m p o r t v i e w s a s s e a r c h _ v i e w s
urlpatterns = [
path( "django/" , admin.site.urls) ,
path( "wagtail/" , include( wagtailadmin_urls) ) ,
path( "documents/" , include( wagtaildocs_urls) ) ,
path( "search/" , search_views.search, name = "search" ) ,
]
if settings.DEBUG :
from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
# Serve static and media files from development server
urlpatterns += staticfiles_urlpatterns( )
urlpatterns += static( settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
e n d e f
d e f i n e W A G T A I L _ U R L S _ H O M E
urlpatterns += [
# For anything not caught by a more specific rule above, hand over to
# Wagtail's page serving mechanism. This should be the last pattern in
# the list:
path( "" , include( "wagtail.urls" ) ) ,
# Alternatively, if you want Wagtail pages to be served from a subpath
# of your site, rather than the site root:
# path("pages/", include("wagtail.urls"),
]
e n d e f
d e f i n e W E B P A C K _ C O N F I G _ J S
c o n s t path = require( 'path' ) ;
module.exports = {
mode: 'development' ,
entry: './src/index.js' ,
output: {
filename: 'bundle.js' ,
path: path.resolve( __dirname, 'dist' ) ,
} ,
} ;
e n d e f
d e f i n e W E B P A C K _ I N D E X _ H T M L
< ! D O C T Y P E h t m l >
< h t m l lang = "en" >
< h e a d >
<meta charset = "UTF-8" >
<meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
<title>Hello, Webpack!</title>
< / h e a d >
< b o d y >
<script src = "dist/bundle.js" ></script>
< / b o d y >
< / h t m l >
e n d e f
d e f i n e W E B P A C K _ I N D E X _ J S
c o n s t message = "Hello, World!" ;
c o n s o l e . l o g ( m e s s a g e ) ;
e n d e f
d e f i n e W E B P A C K _ R E V E A L _ C O N F I G _ J S
c o n s t path = require( 'path' ) ;
c o n s t MiniCssExtractPlugin = require( 'mini-css-extract-plugin' ) ;
module.exports = {
mode: 'development' ,
entry: './src/index.js' ,
output: {
filename: 'bundle.js' ,
path: path.resolve( __dirname, 'dist' ) ,
} ,
module: {
rules: [
{
test: /\. css$$ /,
use: [ MiniCssExtractPlugin.loader, 'css-loader' ] ,
} ,
] ,
} ,
plugins: [
new MiniCssExtractPlugin( {
filename: 'bundle.css' ,
} ) ,
] ,
} ;
e n d e f
d e f i n e W E B P A C K _ R E V E A L _ I N D E X _ H T M L
< ! D O C T Y P E h t m l >
< h t m l >
<head>
<meta charset = "UTF-8" >
<meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
<title>Project Makefile</title>
<link rel = "stylesheet" href = "dist/bundle.css" >
</head>
<div class = "reveal" >
<div class = "slides" >
<section>
Slide 1: Draw some circles
</section>
<section>
Slide 2: Draw the rest of the owl
</section>
</div>
</div>
<script src = "dist/bundle.js" ></script>
< / h t m l >
e n d e f
d e f i n e W E B P A C K _ R E V E A L _ I N D E X _ J S
i m p o r t 'reveal.js/dist/reveal.css' ;
i m p o r t 'reveal.js/dist/theme/black.css' ;
i m p o r t R e v e a l f r o m 'reveal.js' ;
i m p o r t R e v e a l N o t e s f r o m 'reveal.js/plugin/notes/notes.js' ;
Reveal.initialize({ slideNumber : true , plugins : [ RevealNotes ]});
e n d e f
# ------------------------------------------------------------------------------
# Export variables used by phony target rules
# ------------------------------------------------------------------------------
export DJANGO_ALLAUTH_BASE_TEMPLATE
export DJANGO_API_SERIALIZERS
export DJANGO_API_VIEWS
export DJANGO_APP_TESTS
export DJANGO_BACKEND_APPS
export DJANGO_BASE_TEMPLATE
export DJANGO_CUSTOM_ADMIN
export DJANGO_DOCKERCOMPOSE
export DJANGO_DOCKERFILE
export DJANGO_FAVICON_TEMPLATE
export DJANGO_FOOTER_TEMPLATE
export DJANGO_FRONTEND_APP
export DJANGO_FRONTEND_APP_CONFIG
export DJANGO_FRONTEND_BABELRC
export DJANGO_FRONTEND_COMPONENTS
export DJANGO_FRONTEND_COMPONENT_CLOCK
export DJANGO_FRONTEND_COMPONENT_ERROR
export DJANGO_FRONTEND_COMPONENT_USER_MENU
export DJANGO_FRONTEND_CONTEXT_INDEX
export DJANGO_FRONTEND_CONTEXT_USER_PROVIDER
export DJANGO_FRONTEND_ESLINTRC
export DJANGO_FRONTEND_OFFCANVAS_TEMPLATE
export DJANGO_FRONTEND_PORTAL
export DJANGO_FRONTEND_STYLES
export DJANGO_FRONTEND_THEME_BLUE
export DJANGO_FRONTEND_THEME_TOGGLER
export DJANGO_HEADER_TEMPLATE
export DJANGO_HOME_PAGE_ADMIN
export DJANGO_HOME_PAGE_MODELS
export DJANGO_HOME_PAGE_TEMPLATE
export DJANGO_HOME_PAGE_URLS
export DJANGO_HOME_PAGE_VIEWS
export DJANGO_LOGGING_DEMO_ADMIN
export DJANGO_LOGGING_DEMO_MODELS
export DJANGO_LOGGING_DEMO_SETTINGS
export DJANGO_LOGGING_DEMO_URLS
export DJANGO_LOGGING_DEMO_VIEWS
export DJANGO_MANAGE_PY
export DJANGO_MODEL_FORM_DEMO_ADMIN
export DJANGO_MODEL_FORM_DEMO_FORMS
export DJANGO_MODEL_FORM_DEMO_MODEL
export DJANGO_MODEL_FORM_DEMO_TEMPLATE_DETAIL
export DJANGO_MODEL_FORM_DEMO_TEMPLATE_FORM
export DJANGO_MODEL_FORM_DEMO_TEMPLATE_LIST
export DJANGO_MODEL_FORM_DEMO_URLS
export DJANGO_MODEL_FORM_DEMO_VIEWS
export DJANGO_PAYMENTS_ADMIN
export DJANGO_PAYMENTS_FORM
export DJANGO_PAYMENTS_MIGRATION_0002
export DJANGO_PAYMENTS_MIGRATION_0003
export DJANGO_PAYMENTS_MODELS
export DJANGO_PAYMENTS_TEMPLATE_CANCEL
export DJANGO_PAYMENTS_TEMPLATE_CHECKOUT
export DJANGO_PAYMENTS_TEMPLATE_PRODUCT_DETAIL
export DJANGO_PAYMENTS_TEMPLATE_PRODUCT_LIST
export DJANGO_PAYMENTS_TEMPLATE_SUCCESS
export DJANGO_PAYMENTS_URLS
export DJANGO_PAYMENTS_VIEW
export DJANGO_SEARCH_FORMS
export DJANGO_SEARCH_SETTINGS
export DJANGO_SEARCH_TEMPLATE
export DJANGO_SEARCH_URLS
export DJANGO_SEARCH_UTILS
export DJANGO_SEARCH_VIEWS
export DJANGO_SETTINGS_AUTHENTICATION_BACKENDS
export DJANGO_SETTINGS_BASE
export DJANGO_SETTINGS_BASE_MINIMAL
export DJANGO_SETTINGS_CRISPY_FORMS
export DJANGO_SETTINGS_DATABASE
export DJANGO_SETTINGS_DEV
export DJANGO_SETTINGS_HOME_PAGE
export DJANGO_SETTINGS_INSTALLED_APPS
export DJANGO_SETTINGS_MIDDLEWARE
export DJANGO_SETTINGS_MODEL_FORM_DEMO
export DJANGO_SETTINGS_PAYMENTS
export DJANGO_SETTINGS_PROD
export DJANGO_SETTINGS_REST_FRAMEWORK
export DJANGO_SETTINGS_SITEUSER
export DJANGO_SETTINGS_THEMES
export DJANGO_SITEUSER_ADMIN
export DJANGO_SITEUSER_EDIT_TEMPLATE
export DJANGO_SITEUSER_FORM
export DJANGO_SITEUSER_MODEL
export DJANGO_SITEUSER_URLS
export DJANGO_SITEUSER_VIEW
export DJANGO_SITEUSER_VIEW_TEMPLATE
export DJANGO_URLS
export DJANGO_URLS_ALLAUTH
export DJANGO_URLS_API
export DJANGO_URLS_DEBUG_TOOLBAR
export DJANGO_URLS_HOME_PAGE
export DJANGO_URLS_LOGGING_DEMO
export DJANGO_URLS_MODEL_FORM_DEMO
export DJANGO_URLS_SITEUSER
export DJANGO_UTILS
export EB_CUSTOM_ENV_EC2_USER
export EB_CUSTOM_ENV_VAR_FILE
export GIT_IGNORE
export JENKINS_FILE
export MAKEFILE_CUSTOM
export PIP_INSTALL_REQUIREMENTS_TEST
export PROGRAMMING_INTERVIEW
export PYTHON_CI_YAML
export PYTHON_LICENSE_TXT
export PYTHON_PROJECT_TOML
export SEPARATOR
export TINYMCE_JS
export WAGTAIL_BASE_TEMPLATE
export WAGTAIL_BLOCK_CAROUSEL
export WAGTAIL_BLOCK_MARKETING
export WAGTAIL_CONTACT_PAGE_LANDING
export WAGTAIL_CONTACT_PAGE_MODEL
export WAGTAIL_CONTACT_PAGE_TEMPLATE
export WAGTAIL_CONTACT_PAGE_TEST
export WAGTAIL_HOME_PAGE_MODEL
export WAGTAIL_HOME_PAGE_TEMPLATE
export WAGTAIL_HOME_PAGE_URLS
export WAGTAIL_HOME_PAGE_VIEWS
export WAGTAIL_PRIVACY_PAGE_MODEL
export WAGTAIL_PRIVACY_PAGE_MODEL
export WAGTAIL_PRIVACY_PAGE_TEMPLATE
export WAGTAIL_SEARCH_TEMPLATE
export WAGTAIL_SEARCH_URLS
export WAGTAIL_SETTINGS
export WAGTAIL_SITEPAGE_MODEL
export WAGTAIL_SITEPAGE_TEMPLATE
export WAGTAIL_URLS
export WAGTAIL_URLS_HOME
export WEBPACK_CONFIG_JS
export WEBPACK_INDEX_HTML
export WEBPACK_INDEX_JS
export WEBPACK_REVEAL_CONFIG_JS
export WEBPACK_REVEAL_INDEX_HTML
export WEBPACK_REVEAL_INDEX_JS
# ------------------------------------------------------------------------------
# Multi-line phony target rules
# ------------------------------------------------------------------------------
.PHONY : aws -check -env -profile -default
aws-check-env-profile-default :
i f n d e f A W S _ P R O F I L E
$( error AWS_PROFILE is undefined)
e n d i f
.PHONY : aws -check -env -region -default
aws-check-env-region-default :
i f n d e f A W S _ R E G I O N
$( error AWS_REGION is undefined)
e n d i f
.PHONY : aws -secret -default
aws-secret-default : aws -check -env
@SECRET_KEY= $$ ( openssl rand -base64 48) ; aws ssm put-parameter --name "SECRET_KEY" --value " $$ SECRET_KEY " --type String
.PHONY : aws -sg -default
aws-sg-default : aws -check -env
aws ec2 describe-security-groups $( AWS_OPTS)
.PHONY : aws -ssm -default
aws-ssm-default : aws -check -env
aws ssm describe-parameters $( AWS_OPTS)
@echo "Get parameter values with: aws ssm getparameter --name <Name>."
.PHONY : aws -subnet -default
aws-subnet-default : aws -check -env
aws ec2 describe-subnets $( AWS_OPTS)
.PHONY : aws -vol -available -default
aws-vol-available-default : aws -check -env
aws ec2 describe-volumes --filters Name = status,Values= available --query "Volumes[*].{ID:VolumeId,Size:Size}" --output table
.PHONY : aws -vol -default
aws-vol-default : aws -check -env
aws ec2 describe-volumes --output table
.PHONY : aws -vpc -default
aws-vpc-default : aws -check -env
aws ec2 describe-vpcs $( AWS_OPTS)
.PHONY : db -import -default
db-import-default :
@psql $( DJANGO_DB_NAME) < $( DJANGO_DB_NAME) .sql
.PHONY : db -init -default
db-init-default :
-dropdb $( PROJECT_NAME)
-createdb $( PROJECT_NAME)
.PHONY : db -init -mysql -default
db-init-mysql-default :
-mysqladmin -u root drop $( PROJECT_NAME)
-mysqladmin -u root create $( PROJECT_NAME)
.PHONY : db -init -test -default
db-init-test-default :
-dropdb test_$( PROJECT_NAME)
-createdb test_$( PROJECT_NAME)
.PHONY : django -allauth -default
django-allauth-default :
$( ADD_DIR) backend/templates/allauth/layouts
@echo " $$ DJANGO_ALLAUTH_BASE_TEMPLATE " > backend/templates/allauth/layouts/base.html
@echo " $$ DJANGO_URLS_ALLAUTH " >> $( DJANGO_URLS_FILE)
-$( GIT_ADD) backend/templates/allauth/layouts/base.html
.PHONY : django -app -tests -default
django-app-tests-default :
@echo " $$ DJANGO_APP_TESTS " > $( APP_DIR) /tests.py
.PHONY : django -base -template -default
django-base-template-default :
@$( ADD_DIR) backend/templates
@echo " $$ DJANGO_BASE_TEMPLATE " > backend/templates/base.html
-$( GIT_ADD) backend/templates/base.html
.PHONY : django -custom -admin -default
django-custom-admin-default :
@echo " $$ DJANGO_CUSTOM_ADMIN " > $( DJANGO_CUSTOM_ADMIN_FILE)
@echo " $$ DJANGO_BACKEND_APPS " > $( DJANGO_BACKEND_APPS_FILE)
-$( GIT_ADD) backend/*.py
.PHONY : django -db -shell -default
django-db-shell-default :
python manage.py dbshell
.PHONY : django -dockerfile -default
django-dockerfile-default :
@echo " $$ DJANGO_DOCKERFILE " > Dockerfile
-$( GIT_ADD) Dockerfile
@echo " $$ DJANGO_DOCKERCOMPOSE " > docker-compose.yml
-$( GIT_ADD) docker-compose.yml
.PHONY : django -favicon -default
django-favicon-default :
@echo " $$ DJANGO_FAVICON_TEMPLATE " > backend/templates/favicon.html
-$( GIT_ADD) backend/templates/favicon.html
.PHONY : django -footer -template -default
django-footer-template-default :
@echo " $$ DJANGO_FOOTER_TEMPLATE " > backend/templates/footer.html
-$( GIT_ADD) backend/templates/footer.html
.PHONY : django -frontend -default
django-frontend-default : python -webpack -init
$( ADD_DIR) frontend/src/context
$( ADD_DIR) frontend/src/images
$( ADD_DIR) frontend/src/utils
@echo " $$ DJANGO_FRONTEND_APP " > frontend/src/application/app.js
@echo " $$ DJANGO_FRONTEND_APP_CONFIG " > frontend/src/application/config.js
@echo " $$ DJANGO_FRONTEND_BABELRC " > frontend/.babelrc
@echo " $$ DJANGO_FRONTEND_COMPONENT_CLOCK " > frontend/src/components/Clock.js
@echo " $$ DJANGO_FRONTEND_COMPONENT_ERROR " > frontend/src/components/ErrorBoundary.js
@echo " $$ DJANGO_FRONTEND_CONTEXT_INDEX " > frontend/src/context/index.js
@echo " $$ DJANGO_FRONTEND_CONTEXT_USER_PROVIDER " > frontend/src/context/UserContextProvider.js
@echo " $$ DJANGO_FRONTEND_COMPONENT_USER_MENU " > frontend/src/components/UserMenu.js
@echo " $$ DJANGO_FRONTEND_COMPONENTS " > frontend/src/components/index.js
@echo " $$ DJANGO_FRONTEND_ESLINTRC " > frontend/.eslintrc
@echo " $$ DJANGO_FRONTEND_PORTAL " > frontend/src/dataComponents.js
@echo " $$ DJANGO_FRONTEND_STYLES " > frontend/src/styles/index.scss
@echo " $$ DJANGO_FRONTEND_THEME_BLUE " > frontend/src/styles/theme-blue.scss
@echo " $$ DJANGO_FRONTEND_THEME_TOGGLER " > frontend/src/utils/themeToggler.js
# @echo "$$TINYMCE_JS" > frontend/src/utils/tinymce.js
@$( MAKE) npm-install-django
@$( MAKE) npm-install-django-dev
-$( GIT_ADD) $( DJANGO_FRONTEND_FILES)
.PHONY : django -graph -default
django-graph-default :
python manage.py graph_models -a -o $( PROJECT_NAME) .png
.PHONY : django -header -template -default
django-header-template-default :
@echo " $$ DJANGO_HEADER_TEMPLATE " > backend/templates/header.html
-$( GIT_ADD) backend/templates/header.html
.PHONY : django -home -default
django-home-default :
python manage.py startapp home
$( ADD_DIR) home/templates
@echo " $$ DJANGO_HOME_PAGE_ADMIN " > home/admin.py
@echo " $$ DJANGO_HOME_PAGE_MODELS " > home/models.py
@echo " $$ DJANGO_HOME_PAGE_TEMPLATE " > home/templates/home.html
@echo " $$ DJANGO_HOME_PAGE_VIEWS " > home/views.py
@echo " $$ DJANGO_HOME_PAGE_URLS " > home/urls.py
@echo " $$ DJANGO_URLS_HOME_PAGE " >> $( DJANGO_URLS_FILE)
@echo " $$ DJANGO_SETTINGS_HOME_PAGE " >> $( DJANGO_SETTINGS_BASE_FILE)
export APP_DIR = "home" ; $( MAKE) django-app-tests
-$( GIT_ADD) home/templates
-$( GIT_ADD) home/*.py
-$( GIT_ADD) home/migrations/*.py
.PHONY : django -init -default
django-init-default : separator \
db-init \
django-install \
django-project \
django-utils \
pip-freeze \
pip-init-test \
django-settings-directory \
django-custom-admin \
django-dockerfile \
django-offcanvas-template \
django-header-template \
django-footer-template \
django-base-template \
django-manage-py \
django-urls \
django-urls-debug-toolbar \
django-allauth \
django-favicon \
git-ignore \
django-settings-base \
django-settings-dev \
django-settings-prod \
django-siteuser \
django-home \
django-rest-serializers \
django-rest-views \
django-urls-api \
django-frontend \
django-migrate \
django-su
.PHONY : django -init -minimal -default
django-init-minimal-default : separator \
db-init \
django-install-minimal \
django-project \
django-settings-directory \
django-settings-base-minimal \
django-settings-dev \
pip-freeze \
pip-init-test \
django-custom-admin \
django-dockerfile \
django-offcanvas-template \
django-header-template \
django-footer-template \
django-base-template \
django-manage-py \
django-urls \
django-urls-debug-toolbar \
django-favicon \
django-settings-prod \
django-home \
django-utils \
django-frontend \
django-migrate \
git-ignore \
django-su
.PHONY : django -init -wagtail -default
django-init-wagtail-default : separator \
db-init \
django-install \
wagtail-install \
wagtail-project \
django-utils \
pip-freeze \
pip-init-test \
django-custom-admin \
django-dockerfile \
django-offcanvas-template \
wagtail-header-prefix-template \
django-header-template \
wagtail-base-template \
django-footer-template \
django-manage-py \
wagtail-home \
wagtail-urls \
django-urls-debug-toolbar \
django-allauth \
django-favicon \
git-ignore \
wagtail-search \
django-settings-base \
django-settings-dev \
django-settings-prod \
wagtail-settings \
django-siteuser \
django-model-form-demo \
django-logging-demo \
django-payments-demo-default \
django-rest-serializers \
django-rest-views \
django-urls-api \
wagtail-urls-home \
django-frontend \
django-migrate \
django-su
.PHONY : django -install -default
django-install-default :
$( PIP_ENSURE)
python -m pip install \
Django \
Faker \
boto3 \
crispy-bootstrap5 \
djangorestframework \
django-allauth \
django-after-response \
django-ckeditor \
django-colorful \
django-cors-headers \
django-countries \
django-crispy-forms \
django-debug-toolbar \
django-extensions \
django-hijack \
django-honeypot \
django-imagekit \
django-import-export \
django-ipware \
django-multiselectfield \
django-ninja \
django-phonenumber-field \
django-recurrence \
django-recaptcha \
django-registration \
django-richtextfield \
django-sendgrid-v5 \
django-social-share \
django-sql-explorer \
django-storages \
django-tables2 \
django-timezone-field \
django-widget-tweaks \
dj-database-url \
dj-rest-auth \
dj-stripe \
docutils \
enmerkar \
gunicorn \
html2docx \
icalendar \
mailchimp-marketing \
mailchimp-transactional \
phonenumbers \
pipdeptree \
psycopg2-binary \
pydotplus \
python-webpack-boilerplate \
python-docx \
reportlab \
texttable
.PHONY : django -install -minimal -default
django-install-minimal-default :
$( PIP_ENSURE)
python -m pip install \
Django \
dj-database-url \
django-debug-toolbar \
python-webpack-boilerplate
.PHONY : django -lint -default
django-lint-default :
-ruff format -v
-djlint --reformat --format-css --format-js .
-ruff check -v --fix
.PHONY : django -loaddata -default
django-loaddata-default :
python manage.py loaddata
.PHONY : django -logging -demo -default
django-logging-demo-default :
python manage.py startapp logging_demo
@echo " $$ DJANGO_LOGGING_DEMO_ADMIN " > logging_demo/admin.py
@echo " $$ DJANGO_LOGGING_DEMO_MODELS " > logging_demo/models.py
@echo " $$ DJANGO_LOGGING_DEMO_SETTINGS " >> $( DJANGO_SETTINGS_BASE_FILE)
@echo " $$ DJANGO_LOGGING_DEMO_URLS " > logging_demo/urls.py
@echo " $$ DJANGO_LOGGING_DEMO_VIEWS " > logging_demo/views.py
@echo " $$ DJANGO_URLS_LOGGING_DEMO " >> $( DJANGO_URLS_FILE)
export APP_DIR = "logging_demo" ; $( MAKE) django-app-tests
-$( GIT_ADD) logging_demo/*.py
-$( GIT_ADD) logging_demo/migrations/*.py
.PHONY : django -manage -py -default
django-manage-py-default :
@echo " $$ DJANGO_MANAGE_PY " > manage.py
-$( GIT_ADD) manage.py
.PHONY : django -migrate -default
django-migrate-default :
python manage.py migrate
.PHONY : django -migrations -make -default
django-migrations-make-default :
python manage.py makemigrations
.PHONY : django -migrations -show -default
django-migrations-show-default :
python manage.py showmigrations
.PHONY : django -model -form -demo -default
django-model-form-demo-default :
python manage.py startapp model_form_demo
@echo " $$ DJANGO_MODEL_FORM_DEMO_ADMIN " > model_form_demo/admin.py
@echo " $$ DJANGO_MODEL_FORM_DEMO_FORMS " > model_form_demo/forms.py
@echo " $$ DJANGO_MODEL_FORM_DEMO_MODEL " > model_form_demo/models.py
@echo " $$ DJANGO_MODEL_FORM_DEMO_URLS " > model_form_demo/urls.py
@echo " $$ DJANGO_MODEL_FORM_DEMO_VIEWS " > model_form_demo/views.py
$( ADD_DIR) model_form_demo/templates
@echo " $$ DJANGO_MODEL_FORM_DEMO_TEMPLATE_DETAIL " > model_form_demo/templates/model_form_demo_detail.html
@echo " $$ DJANGO_MODEL_FORM_DEMO_TEMPLATE_FORM " > model_form_demo/templates/model_form_demo_form.html
@echo " $$ DJANGO_MODEL_FORM_DEMO_TEMPLATE_LIST " > model_form_demo/templates/model_form_demo_list.html
@echo " $$ DJANGO_SETTINGS_MODEL_FORM_DEMO " >> $( DJANGO_SETTINGS_BASE_FILE)
@echo " $$ DJANGO_URLS_MODEL_FORM_DEMO " >> $( DJANGO_URLS_FILE)
export APP_DIR = "model_form_demo" ; $( MAKE) django-app-tests
python manage.py makemigrations
-$( GIT_ADD) model_form_demo/*.py
-$( GIT_ADD) model_form_demo/templates
-$( GIT_ADD) model_form_demo/migrations
.PHONY : django -offcanvas -template -default
django-offcanvas-template-default :
-$( ADD_DIR) backend/templates
@echo " $$ DJANGO_FRONTEND_OFFCANVAS_TEMPLATE " > backend/templates/offcanvas.html
-$( GIT_ADD) backend/templates/offcanvas.html
.PHONY : django -open -default
django-open-default :
i f e q ( $( UNAME ) , L i n u x )
@echo "Opening on Linux."
xdg-open http://0.0.0.0:8000
e l s e i f e q ( $( UNAME ) , D a r w i n )
@echo "Opening on macOS (Darwin)."
open http://0.0.0.0:8000
e l s e
@echo " Unable to open on: $( UNAME) "
e n d i f
.PHONY : django -payments -demo -default
django-payments-demo-default :
python manage.py startapp payments
@echo " $$ DJANGO_PAYMENTS_FORM " > payments/forms.py
@echo " $$ DJANGO_PAYMENTS_MODELS " > payments/models.py
@echo " $$ DJANGO_PAYMENTS_ADMIN " > payments/admin.py
@echo " $$ DJANGO_PAYMENTS_VIEW " > payments/views.py
@echo " $$ DJANGO_PAYMENTS_URLS " > payments/urls.py
$( ADD_DIR) payments/templates/payments
$( ADD_DIR) payments/management/commands
@echo " $$ DJANGO_PAYMENTS_TEMPLATE_CANCEL " > payments/templates/payments/cancel.html
@echo " $$ DJANGO_PAYMENTS_TEMPLATE_CHECKOUT " > payments/templates/payments/checkout.html
@echo " $$ DJANGO_PAYMENTS_TEMPLATE_SUCCESS " > payments/templates/payments/success.html
@echo " $$ DJANGO_PAYMENTS_TEMPLATE_PRODUCT_LIST " > payments/templates/payments/product_list.html
@echo " $$ DJANGO_PAYMENTS_TEMPLATE_PRODUCT_DETAIL " > payments/templates/payments/product_detail.html
@echo " $$ DJANGO_SETTINGS_PAYMENTS " >> $( DJANGO_SETTINGS_BASE_FILE)
@echo " $$ DJANGO_URLS_PAYMENTS " >> $( DJANGO_URLS_FILE)
export APP_DIR = "payments" ; $( MAKE) django-app-tests
python manage.py makemigrations payments
@echo " $$ DJANGO_PAYMENTS_MIGRATION_0002 " > payments/migrations/0002_set_stripe_api_keys.py
@echo " $$ DJANGO_PAYMENTS_MIGRATION_0003 " > payments/migrations/0003_create_initial_products.py
-$( GIT_ADD) payments/
.PHONY : django -project -default
django-project-default :
django-admin startproject backend .
-$( GIT_ADD) backend
.PHONY : django -rest -serializers -default
django-rest-serializers-default :
@echo " $$ DJANGO_API_SERIALIZERS " > backend/serializers.py
-$( GIT_ADD) backend/serializers.py
.PHONY : django -rest -views -default
django-rest-views-default :
@echo " $$ DJANGO_API_VIEWS " > backend/api.py
-$( GIT_ADD) backend/api.py
.PHONY : django -search -default
django-search-default :
python manage.py startapp search
$( ADD_DIR) search/templates
@echo " $$ DJANGO_SEARCH_TEMPLATE " > search/templates/search.html
@echo " $$ DJANGO_SEARCH_FORMS " > search/forms.py
@echo " $$ DJANGO_SEARCH_URLS " > search/urls.py
@echo " $$ DJANGO_SEARCH_UTILS " > search/utils.py
@echo " $$ DJANGO_SEARCH_VIEWS " > search/views.py
@echo " $$ DJANGO_SEARCH_SETTINGS " >> $( DJANGO_SETTINGS_BASE_FILE)
@echo "INSTALLED_APPS.append('search')" >> $( DJANGO_SETTINGS_BASE_FILE)
@echo "urlpatterns += [path('search/', include('search.urls'))]" >> $( DJANGO_URLS_FILE)
-$( GIT_ADD) search/templates
-$( GIT_ADD) search/*.py
.PHONY : django -secret -key -default
django-secret-key-default :
@python -c "from secrets import token_urlsafe; print(token_urlsafe(50))"
.PHONY : django -serve -default
django-serve-default :
npm run watch &
python manage.py runserver 0.0.0.0:8000
.PHONY : django -settings -base -default
django-settings-base-default :
@echo " $$ DJANGO_SETTINGS_BASE " >> $( DJANGO_SETTINGS_BASE_FILE)
@echo " $$ DJANGO_SETTINGS_AUTHENTICATION_BACKENDS " >> $( DJANGO_SETTINGS_BASE_FILE)
@echo " $$ DJANGO_SETTINGS_REST_FRAMEWORK " >> $( DJANGO_SETTINGS_BASE_FILE)
@echo " $$ DJANGO_SETTINGS_THEMES " >> $( DJANGO_SETTINGS_BASE_FILE)
@echo " $$ DJANGO_SETTINGS_DATABASE " >> $( DJANGO_SETTINGS_BASE_FILE)
@echo " $$ DJANGO_SETTINGS_INSTALLED_APPS " >> $( DJANGO_SETTINGS_BASE_FILE)
@echo " $$ DJANGO_SETTINGS_MIDDLEWARE " >> $( DJANGO_SETTINGS_BASE_FILE)
@echo " $$ DJANGO_SETTINGS_CRISPY_FORMS " >> $( DJANGO_SETTINGS_BASE_FILE)
.PHONY : django -settings -base -minimal -default
django-settings-base-minimal-default :
@echo " $$ DJANGO_SETTINGS_BASE_MINIMAL " >> $( DJANGO_SETTINGS_BASE_FILE)
.PHONY : django -settings -dev -default
django-settings-dev-default :
@echo " # $( PROJECT_NAME) " > $( DJANGO_SETTINGS_DEV_FILE)
@echo " $$ DJANGO_SETTINGS_DEV " >> backend/settings/dev.py
-$( GIT_ADD) $( DJANGO_SETTINGS_DEV_FILE)
.PHONY : django -settings -directory -default
django-settings-directory-default :
@$( ADD_DIR) $( DJANGO_SETTINGS_DIR)
@$( COPY_FILE) backend/settings.py backend/settings/base.py
@$( DEL_FILE) backend/settings.py
-$( GIT_ADD) backend/settings/*.py
.PHONY : django -settings -prod -default
django-settings-prod-default :
@echo " $$ DJANGO_SETTINGS_PROD " > $( DJANGO_SETTINGS_PROD_FILE)
-$( GIT_ADD) $( DJANGO_SETTINGS_PROD_FILE)
.PHONY : django -shell -default
django-shell-default :
python manage.py shell
.PHONY : django -siteuser -default
django-siteuser-default :
python manage.py startapp siteuser
$( ADD_DIR) siteuser/templates/
@echo " $$ DJANGO_SITEUSER_FORM " > siteuser/forms.py
@echo " $$ DJANGO_SITEUSER_MODEL " > siteuser/models.py
@echo " $$ DJANGO_SITEUSER_ADMIN " > siteuser/admin.py
@echo " $$ DJANGO_SITEUSER_VIEW " > siteuser/views.py
@echo " $$ DJANGO_SITEUSER_URLS " > siteuser/urls.py
@echo " $$ DJANGO_SITEUSER_VIEW_TEMPLATE " > siteuser/templates/profile.html
@echo " $$ DJANGO_SITEUSER_TEMPLATE " > siteuser/templates/user.html
@echo " $$ DJANGO_SITEUSER_EDIT_TEMPLATE " > siteuser/templates/user_edit.html
@echo " $$ DJANGO_URLS_SITEUSER " >> $( DJANGO_URLS_FILE)
@echo " $$ DJANGO_SETTINGS_SITEUSER " >> $( DJANGO_SETTINGS_BASE_FILE)
export APP_DIR = "siteuser" ; $( MAKE) django-app-tests
-$( GIT_ADD) siteuser/templates
-$( GIT_ADD) siteuser/*.py
python manage.py makemigrations siteuser
-$( GIT_ADD) siteuser/migrations/*.py
.PHONY : django -static -default
django-static-default :
python manage.py collectstatic --noinput
.PHONY : django -su -default
django-su-default :
DJANGO_SUPERUSER_PASSWORD = admin python manage.py createsuperuser --noinput --username= admin --email= $( PROJECT_EMAIL)
.PHONY : django -test -default
django-test-default : npm -install django -static
-$( MAKE) pip-install-test
python manage.py test
.PHONY : django -urls -api -default
django-urls-api-default :
@echo " $$ DJANGO_URLS_API " >> $( DJANGO_URLS_FILE)
-$( GIT_ADD) $( DJANGO_URLS_FILE)
.PHONY : django -urls -debug -toolbar -default
django-urls-debug-toolbar-default :
@echo " $$ DJANGO_URLS_DEBUG_TOOLBAR " >> $( DJANGO_URLS_FILE)
.PHONY : django -urls -default
django-urls-default :
@echo " $$ DJANGO_URLS " > $( DJANGO_URLS_FILE)
-$( GIT_ADD) $( DJANGO_URLS_FILE)
.PHONY : django -urls -show -default
django-urls-show-default :
python manage.py show_urls
.PHONY : django -user -default
django-user-default :
python manage.py shell -c " from django.contrib.auth.models import User; \
User.objects.create_user( 'user' , '' , 'user' ) "
.PHONY : django -utils -default
django-utils-default :
@echo " $$ DJANGO_UTILS " > backend/utils.py
-$( GIT_ADD) backend/utils.py
.PHONY : docker -build -default
docker-build-default :
podman build -t $( PROJECT_NAME) .
.PHONY : docker -compose -default
docker-compose-default :
podman compose up
.PHONY : docker -list -default
docker-list-default :
podman container list --all
podman images --all
.PHONY : docker -run -default
docker-run-default :
podman run $( PROJECT_NAME)
.PHONY : docker -serve -default
docker-serve-default :
podman run -p 8000:8000 $( PROJECT_NAME)
.PHONY : docker -shell -default
docker-shell-default :
podman run -it $( PROJECT_NAME) /bin/bash
.PHONY : eb -check -env -default
eb-check-env-default : # https://stackoverflow.com/a/4731504/185820
i f n d e f E B _ S S H _ K E Y
$( error EB_SSH_KEY is undefined)
e n d i f
i f n d e f V P C _ I D
$( error VPC_ID is undefined)
e n d i f
i f n d e f V P C _ S G
$( error VPC_SG is undefined)
e n d i f
i f n d e f V P C _ S U B N E T _ E C 2
$( error VPC_SUBNET_EC2 is undefined)
e n d i f
i f n d e f V P C _ S U B N E T _ E L B
$( error VPC_SUBNET_ELB is undefined)
e n d i f
.PHONY : eb -create -default
eb-create-default : aws -check -env eb -check -env
eb create $( EB_ENV_NAME) \
-im $( EC2_INSTANCE_MIN) \
-ix $( EC2_INSTANCE_MAX) \
-ip $( EC2_INSTANCE_PROFILE) \
-i $( EC2_INSTANCE_TYPE) \
-k $( EB_SSH_KEY) \
-p $( EB_PLATFORM) \
--elb-type $( EC2_LB_TYPE) \
--vpc \
--vpc.id $( VPC_ID) \
--vpc.elbpublic \
--vpc.publicip \
--vpc.ec2subnets $( VPC_SUBNET_EC2) \
--vpc.elbsubnets $( VPC_SUBNET_ELB) \
--vpc.securitygroups $( VPC_SG)
.PHONY : eb -custom -env -default
eb-custom-env-default :
$( ADD_DIR) .ebextensions
@echo " $$ EB_CUSTOM_ENV_EC2_USER " > .ebextensions/bash.config
-$( GIT_ADD) .ebextensions/bash.config
$( ADD_DIR) .platform/hooks/postdeploy
@echo " $$ EB_CUSTOM_ENV_VAR_FILE " > .platform/hooks/postdeploy/setenv.sh
-$( GIT_ADD) .platform/hooks/postdeploy/setenv.sh
.PHONY : eb -deploy -default
eb-deploy-default :
eb deploy
.PHONY : eb -export -default
eb-export-default :
@if [ ! -d $( EB_DIR_NAME) ] ; then \
echo " Directory $( EB_DIR_NAME) does not exist " ; \
else \
echo " Directory $( EB_DIR_NAME) does exist! " ; \
eb ssh --quiet -c " export PGPASSWORD= $( DJANGO_DB_PASS) ; pg_dump -U $( DJANGO_DB_USER) -h $( DJANGO_DB_HOST) $( DJANGO_DB_NAME) " > $( DJANGO_DB_NAME) .sql; \
echo " Wrote $( DJANGO_DB_NAME) .sql " ; \
fi
.PHONY : eb -restart -default
eb-restart-default :
eb ssh -c "systemctl restart web"
.PHONY : eb -rebuild -default
eb-rebuild-default :
aws elasticbeanstalk rebuild-environment --environment-name $( ENV_NAME)
.PHONY : eb -upgrade -default
eb-upgrade-default :
eb upgrade
.PHONY : eb -init -default
eb-init-default : aws -check -env -profile
eb init --profile= $( AWS_PROFILE)
.PHONY : eb -list -default
eb-list-platforms-default :
aws elasticbeanstalk list-platform-versions
.PHONY : eb -list -databases -default
eb-list-databases-default :
@eb ssh --quiet -c " export PGPASSWORD= $( DJANGO_DB_PASS) ; psql -l -U $( DJANGO_DB_USER) -h $( DJANGO_DB_HOST) $( DJANGO_DB_NAME) "
.PHONY : eb -logs -default
eb-logs-default :
eb logs
.PHONY : eb -print -env -default
eb-print-env-default :
eb printenv
.PHONY : favicon -default
favicon-init-default :
dd if = /dev/urandom bs = 64 count = 1 status = none | base64 | convert -size 16x16 -depth 8 -background none -fill white label:@- favicon.png
convert favicon.png favicon.ico
-$( GIT_ADD) favicon.ico
$( DEL_FILE) favicon.png
.PHONY : git -ignore -default
git-ignore-default :
@echo " $$ GIT_IGNORE " > .gitignore
-$( GIT_ADD) .gitignore
.PHONY : git -branches -default
git-branches-default :
-for i in $( GIT_BRANCHES) ; do \
-@$( GIT_CHECKOUT) -t $$ i ; done
.PHONY : git -commit -message -clean -default
git-commit-message-clean-default :
-@$( GIT_COMMIT) -a -m "Clean"
.PHONY : git -commit -message -default
git-commit-message-default :
-@$( GIT_COMMIT) -a -m $( GIT_COMMIT_MSG)
.PHONY : git -commit -message -empty -default
git-commit-message-empty-default :
-@$( GIT_COMMIT) --allow-empty -m "Empty-Commit"
.PHONY : git -commit -message -init -default
git-commit-message-init-default :
-@$( GIT_COMMIT) -a -m "Init"
.PHONY : git -commit -message -last -default
git-commit-message-last-default :
git log -1 --pretty= %B > $( TMPDIR) /commit.txt
-$( GIT_COMMIT) -a -F $( TMPDIR) /commit.txt
.PHONY : git -commit -message -lint -default
git-commit-message-lint-default :
-@$( GIT_COMMIT) -a -m "Lint"
.PHONY : git -commit -message -mk -default
git-commit-message-mk-default :
-@$( GIT_COMMIT) project.mk -m " Add/update $( MAKEFILE_CUSTOM_FILE) "
.PHONY : git -commit -message -rename -default
git-commit-message-rename-default :
-@$( GIT_COMMIT) -a -m "Rename"
.PHONY : git -commit -message -sort -default
git-commit-message-sort-default :
-@$( GIT_COMMIT) -a -m "Sort"
.PHONY : git -push -default
git-push-default :
-@$( GIT_PUSH)
.PHONY : git -push -force -default
git-push-force-default :
-@$( GIT_PUSH_FORCE)
.PHONY : git -commit -edit -default
git-commit-edit-default :
-$( GIT_COMMIT) -a
.PHONY : git -prune -default
git-prune-default :
git remote update origin --prune
.PHONY : git -set -upstream -default
git-set-upstream-default :
git push --set-upstream origin main
.PHONY : git -set -default -default
git-set-default-default :
gh repo set-default
.PHONY : git -short -default
git-short-default :
@echo $( GIT_REV)
.PHONY : help -default
help-default :
@echo "Project Makefile 🤷"
@echo "Usage: make [options] [target] ..."
@echo "Examples:"
@echo " make help Print this message"
@echo " make list-defines list all defines in the Makefile"
@echo " make list-commands list all targets in the Makefile"
.PHONY : jenkins -init -default
jenkins-init-default :
@echo " $$ JENKINS_FILE " > Jenkinsfile
.PHONY : makefile -list -commands -default
makefile-list-commands-default :
@for makefile in $( MAKEFILE_LIST) ; do \
echo " Commands from $$ makefile: " ; \
$( MAKE) -pRrq -f $$ makefile : 2>/dev/null | \
awk -v RS = -F: ' /^# File/,/^# Finished Make data base/ { \
if ( $$ 1 !~ "^[#.]" ) { sub( /-default$$ /, "" , $$ 1) ; print $$ 1 } } ' | \
egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | \
tr ' ' '\n' | \
sort | \
awk '{print $$0}' ; \
echo; \
done | $( PAGER)
.PHONY : makefile -list -defines -default
makefile-list-defines-default :
@grep '^define [A-Za-z_][A-Za-z0-9_]*' Makefile
.PHONY : makefile -list -exports -default
makefile-list-exports-default :
@grep '^export [A-Z][A-Z_]*' Makefile
.PHONY : makefile -list -targets -default
makefile-list-targets-default :
@perl -ne 'print if /^\s*\.PHONY:/ .. /^[a-zA-Z0-9_-]+:/;' Makefile | grep -v .PHONY
.PHONY : make -default
make-default :
-$( GIT_ADD) Makefile
-$( GIT_COMMIT) Makefile -m "Add/update project-makefile files"
-git push
.PHONY : npm -init -default
npm-init-default :
npm init -y
-$( GIT_ADD) package.json
-$( GIT_ADD) package-lock.json
.PHONY : npm -build -default
npm-build-default :
npm run build
.PHONY : npm -install -default
npm-install-default :
npm install
-$( GIT_ADD) package-lock.json
.PHONY : npm -install -django -default
npm-install-django-default :
npm install \
@fortawesome/fontawesome-free \
@fortawesome/fontawesome-svg-core \
@fortawesome/free-brands-svg-icons \
@fortawesome/free-solid-svg-icons \
@fortawesome/react-fontawesome \
bootstrap \
camelize \
date-fns \
history \
mapbox-gl \
query-string \
react-animate-height \
react-chartjs-2 \
react-copy-to-clipboard \
react-date-range \
react-dom \
react-dropzone \
react-hook-form \
react-image-crop \
react-map-gl \
react-modal \
react-resize-detector \
react-select \
react-swipeable \
snakeize \
striptags \
url-join \
viewport-mercator-project
.PHONY : npm -install -django -dev -default
npm-install-django-dev-default :
npm install \
eslint-plugin-react \
eslint-config-standard \
eslint-config-standard-jsx \
@babel/core \
@babel/preset-env \
@babel/preset-react \
--save-dev
.PHONY : npm -serve -default
npm-serve-default :
npm run start
.PHONY : npm -test -default
npm-test-default :
npm run test
.PHONY : pip -deps -default
pip-deps-default :
$( PIP_ENSURE)
python -m pip install pipdeptree
python -m pipdeptree
pipdeptree
.PHONY : pip -freeze -default
pip-freeze-default :
$( PIP_ENSURE)
python -m pip freeze | sort > $( TMPDIR) /requirements.txt
mv -f $( TMPDIR) /requirements.txt .
-$( GIT_ADD) requirements.txt
.PHONY : pip -init -default
pip-init-default :
touch requirements.txt
-$( GIT_ADD) requirements.txt
.PHONY : pip -init -test -default
pip-init-test-default :
@echo " $$ PIP_INSTALL_REQUIREMENTS_TEST " > requirements-test.txt
-$( GIT_ADD) requirements-test.txt
.PHONY : pip -install -default
pip-install-default :
$( PIP_ENSURE)
$( MAKE) pip-upgrade
python -m pip install wheel
python -m pip install -r requirements.txt
.PHONY : pip -install -dev -default
pip-install-dev-default :
$( PIP_ENSURE)
python -m pip install -r requirements-dev.txt
.PHONY : pip -install -test -default
pip-install-test-default :
$( PIP_ENSURE)
python -m pip install -r requirements-test.txt
.PHONY : pip -install -upgrade -default
pip-install-upgrade-default :
cat requirements.txt | awk -F\= '{print $$1}' > $( TMPDIR) /requirements.txt
mv -f $( TMPDIR) /requirements.txt .
$( PIP_ENSURE)
python -m pip install -U -r requirements.txt
python -m pip freeze | sort > $( TMPDIR) /requirements.txt
mv -f $( TMPDIR) /requirements.txt .
.PHONY : pip -upgrade -default
pip-upgrade-default :
$( PIP_ENSURE)
python -m pip install -U pip
.PHONY : pip -uninstall -default
pip-uninstall-default :
$( PIP_ENSURE)
python -m pip freeze | xargs python -m pip uninstall -y
.PHONY : plone -clean -default
plone-clean-default :
$( DEL_DIR) $( PROJECT_NAME)
$( DEL_DIR) $( PACKAGE_NAME)
.PHONY : plone -init -default
plone-init-default : git -ignore plone -install plone -instance plone -serve
.PHONY : plone -install -default
plone-install-default :
$( PIP_ENSURE)
python -m pip install plone -c $( PIP_INSTALL_PLONE_CONSTRAINTS)
.PHONY : plone -instance -default
plone-instance-default :
mkwsgiinstance -d backend -u admin:admin
cat backend/etc/zope.ini | sed -e 's/host = 127.0.0.1/host = 0.0.0.0/; s/port = 8080/port = 8000/' > $( TMPDIR) /zope.ini
mv -f $( TMPDIR) /zope.ini backend/etc/zope.ini
-$( GIT_ADD) backend/etc/site.zcml
-$( GIT_ADD) backend/etc/zope.conf
-$( GIT_ADD) backend/etc/zope.ini
.PHONY : plone -serve -default
plone-serve-default :
runwsgi backend/etc/zope.ini
.PHONY : plone -build -default
plone-build-default :
buildout
.PHONY : programming -interview -default
programming-interview-default :
@echo " $$ PROGRAMMING_INTERVIEW " > interview.py
@echo "Created interview.py!"
-@$( GIT_ADD) interview.py > /dev/null 2>& 1
# .NOT_PHONY!
$(MAKEFILE_CUSTOM_FILE) :
@echo " $$ MAKEFILE_CUSTOM " > $( MAKEFILE_CUSTOM_FILE)
-$( GIT_ADD) $( MAKEFILE_CUSTOM_FILE)
.PHONY : python -license -default
python-license-default :
@echo " $( PYTHON_LICENSE_TXT) " > LICENSE.txt
-$( GIT_ADD) LICENSE.txt
.PHONY : python -project -default
python-project-default :
@echo " $( PYTHON_PROJECT_TOML) " > pyproject.toml
-$( GIT_ADD) pyproject.toml
.PHONY : python -serve -default
python-serve-default :
@echo "\n\tServing HTTP on http://0.0.0.0:8000\n"
python3 -m http.server
.PHONY : python -sdist -default
python-sdist-default :
$( PIP_ENSURE)
python setup.py sdist --format= zip
.PHONY : python -webpack -init -default
python-webpack-init-default :
python manage.py webpack_init --no-input
.PHONY : python -ci -default
python-ci-default :
$( ADD_DIR) .github/workflows
@echo " $( PYTHON_CI_YAML) " > .github/workflows/build_wheels.yml
-$( GIT_ADD) .github/workflows/build_wheels.yml
.PHONY : rand -default
rand-default :
@openssl rand -base64 12 | sed 's/\///g'
.PHONY : readme -init -default
readme-init-default :
@echo " # $( PROJECT_NAME) " > README.md
-$( GIT_ADD) README.md
.PHONY : readme -edit -default
readme-edit-default :
$( EDITOR) README.md
.PHONY : reveal -init -default
reveal-init-default : webpack -init -reveal
npm install \
css-loader \
mini-css-extract-plugin \
reveal.js \
style-loader
jq '.scripts += {"build": "webpack"}' package.json > \
$( TMPDIR) /tmp.json && mv $( TMPDIR) /tmp.json package.json
jq '.scripts += {"start": "webpack serve --mode development --port 8000 --static"}' package.json > \
$( TMPDIR) /tmp.json && mv $( TMPDIR) /tmp.json package.json
jq '.scripts += {"watch": "webpack watch --mode development"}' package.json > \
$( TMPDIR) /tmp.json && mv $( TMPDIR) /tmp.json package.json
.PHONY : reveal -serve -default
reveal-serve-default :
npm run watch &
python -m http.server
.PHONY : review -default
review-default :
i f e q ( $( UNAME ) , D a r w i n )
$( EDITOR_REVIEW) ` find backend/ -name \* .py` ` find backend/ -name \* .html` ` find frontend/ -name \* .js` ` find frontend/ -name \* .js`
e l s e
@echo "Unsupported"
e n d i f
.PHONY : separator -default
separator-default :
@echo " $$ SEPARATOR "
.PHONY : sphinx -init -default
sphinx-init-default : sphinx -install
sphinx-quickstart -q -p $( PROJECT_NAME) -a $( USER) -v 0.0.1 $( RANDIR)
$( COPY_DIR) $( RANDIR) /* .
$( DEL_DIR) $( RANDIR)
-$( GIT_ADD) index.rst
-$( GIT_ADD) conf.py
$( DEL_FILE) make.bat
-@$( GIT_CHECKOUT) Makefile
$( MAKE) git-ignore
.PHONY : sphinx -theme -init -default
sphinx-theme-init-default :
export DJANGO_FRONTEND_THEME_NAME = $( PROJECT_NAME) _theme; \
$( ADD_DIR) $$ DJANGO_FRONTEND_THEME_NAME ; \
$( ADD_FILE) $$ DJANGO_FRONTEND_THEME_NAME/__init__.py ; \
-$( GIT_ADD) $$ DJANGO_FRONTEND_THEME_NAME/__init__.py ; \
$( ADD_FILE) $$ DJANGO_FRONTEND_THEME_NAME/theme.conf ; \
-$( GIT_ADD) $$ DJANGO_FRONTEND_THEME_NAME/theme.conf ; \
$( ADD_FILE) $$ DJANGO_FRONTEND_THEME_NAME/layout.html ; \
-$( GIT_ADD) $$ DJANGO_FRONTEND_THEME_NAME/layout.html ; \
$( ADD_DIR) $$ DJANGO_FRONTEND_THEME_NAME/static/css ; \
$( ADD_FILE) $$ DJANGO_FRONTEND_THEME_NAME/static/css/style.css ; \
$( ADD_DIR) $$ DJANGO_FRONTEND_THEME_NAME/static/js ; \
$( ADD_FILE) $$ DJANGO_FRONTEND_THEME_NAME/static/js/script.js ; \
-$( GIT_ADD) $$ DJANGO_FRONTEND_THEME_NAME/static
.PHONY : sphinx -install -default
sphinx-install-default :
echo "Sphinx\n" > requirements.txt
@$( MAKE) pip-install
@$( MAKE) pip-freeze
-$( GIT_ADD) requirements.txt
.PHONY : sphinx -build -default
sphinx-build-default :
sphinx-build -b html -d _build/doctrees . _build/html
sphinx-build -b rinoh . _build/rinoh
.PHONY : sphinx -serve -default
sphinx-serve-default :
cd _build/html; python3 -m http.server
.PHONY : wagtail -base -template -default
wagtail-base-template-default :
@echo " $$ WAGTAIL_BASE_TEMPLATE " > backend/templates/base.html
.PHONY : wagtail -clean -default
wagtail-clean-default :
-@for dir in $( shell echo " $( WAGTAIL_CLEAN_DIRS) " ) ; do \
echo " Cleaning $$ dir " ; \
$( DEL_DIR) $$ dir >/dev/null 2>& 1; \
done
-@for file in $( shell echo " $( WAGTAIL_CLEAN_FILES) " ) ; do \
echo " Cleaning $$ file " ; \
$( DEL_FILE) $$ file >/dev/null 2>& 1; \
done
.PHONY : wagtail -contactpage -default
wagtail-contactpage-default :
python manage.py startapp contactpage
@echo " $$ WAGTAIL_CONTACT_PAGE_MODEL " > contactpage/models.py
@echo " $$ WAGTAIL_CONTACT_PAGE_TEST " > contactpage/tests.py
$( ADD_DIR) contactpage/templates/contactpage/
@echo " $$ WAGTAIL_CONTACT_PAGE_TEMPLATE " > contactpage/templates/contactpage/contact_page.html
@echo " $$ WAGTAIL_CONTACT_PAGE_LANDING " > contactpage/templates/contactpage/contact_page_landing.html
@echo "INSTALLED_APPS.append('contactpage')" >> $( DJANGO_SETTINGS_BASE_FILE)
python manage.py makemigrations contactpage
-$( GIT_ADD) contactpage/templates
-$( GIT_ADD) contactpage/*.py
-$( GIT_ADD) contactpage/migrations/*.py
.PHONY : wagtail -header -prefix -template -default
wagtail-header-prefix-template-default :
@echo " $$ WAGTAIL_HEADER_PREFIX " > backend/templates/header.html
.PHONY : wagtail -home -default
wagtail-home-default :
@echo " $$ WAGTAIL_HOME_PAGE_MODEL " > home/models.py
@echo " $$ WAGTAIL_HOME_PAGE_TEMPLATE " > home/templates/home/home_page.html
$( ADD_DIR) home/templates/blocks
@echo " $$ WAGTAIL_BLOCK_MARKETING " > home/templates/blocks/marketing_block.html
@echo " $$ WAGTAIL_BLOCK_CAROUSEL " > home/templates/blocks/carousel_block.html
-$( GIT_ADD) home/templates
-$( GIT_ADD) home/*.py
python manage.py makemigrations home
-$( GIT_ADD) home/migrations/*.py
.PHONY : wagtail -install -default
wagtail-install-default :
$( PIP_ENSURE)
python -m pip install \
wagtail \
wagtailmenus \
wagtail-color-panel \
wagtail-django-recaptcha \
wagtail-markdown \
wagtail-modeladmin \
wagtail-seo \
weasyprint \
whitenoise \
xhtml2pdf
.PHONY : wagtail -private -default
wagtail-privacy-default :
python manage.py startapp privacy
@echo " $$ WAGTAIL_PRIVACY_PAGE_MODEL " > privacy/models.py
$( ADD_DIR) privacy/templates
@echo " $$ WAGTAIL_PRIVACY_PAGE_TEMPLATE " > privacy/templates/privacy_page.html
@echo "INSTALLED_APPS.append('privacy')" >> $( DJANGO_SETTINGS_BASE_FILE)
python manage.py makemigrations privacy
-$( GIT_ADD) privacy/templates
-$( GIT_ADD) privacy/*.py
-$( GIT_ADD) privacy/migrations/*.py
.PHONY : wagtail -project -default
wagtail-project-default :
wagtail start backend .
$( DEL_FILE) home/templates/home/welcome_page.html
-$( GIT_ADD) backend/
-$( GIT_ADD) .dockerignore
-$( GIT_ADD) Dockerfile
-$( GIT_ADD) manage.py
-$( GIT_ADD) requirements.txt
.PHONY : wagtail -search -default
wagtail-search-default :
@echo " $$ WAGTAIL_SEARCH_TEMPLATE " > search/templates/search/search.html
@echo " $$ WAGTAIL_SEARCH_URLS " > search/urls.py
-$( GIT_ADD) search/templates
-$( GIT_ADD) search/*.py
.PHONY : wagtail -settings -default
wagtail-settings-default :
@echo " $$ WAGTAIL_SETTINGS " >> $( DJANGO_SETTINGS_BASE_FILE)
.PHONY : wagtail -sitepage -default
wagtail-sitepage-default :
python manage.py startapp sitepage
@echo " $$ WAGTAIL_SITEPAGE_MODEL " > sitepage/models.py
$( ADD_DIR) sitepage/templates/sitepage/
@echo " $$ WAGTAIL_SITEPAGE_TEMPLATE " > sitepage/templates/sitepage/site_page.html
@echo "INSTALLED_APPS.append('sitepage')" >> $( DJANGO_SETTINGS_BASE_FILE)
python manage.py makemigrations sitepage
-$( GIT_ADD) sitepage/templates
-$( GIT_ADD) sitepage/*.py
-$( GIT_ADD) sitepage/migrations/*.py
.PHONY : wagtail -urls -default
wagtail-urls-default :
@echo " $$ WAGTAIL_URLS " > $( DJANGO_URLS_FILE)
.PHONY : wagtail -urls -home -default
wagtail-urls-home-default :
@echo " $$ WAGTAIL_URLS_HOME " >> $( DJANGO_URLS_FILE)
.PHONY : webpack -init -default
webpack-init-default : npm -init
@echo " $$ WEBPACK_CONFIG_JS " > webpack.config.js
-$( GIT_ADD) webpack.config.js
npm install --save-dev webpack webpack-cli webpack-dev-server
$( ADD_DIR) src/
@echo " $$ WEBPACK_INDEX_JS " > src/index.js
-$( GIT_ADD) src/index.js
@echo " $$ WEBPACK_INDEX_HTML " > index.html
-$( GIT_ADD) index.html
$( MAKE) git-ignore
.PHONY : webpack -init -reveal -default
webpack-init-reveal-default : npm -init
@echo " $$ WEBPACK_REVEAL_CONFIG_JS " > webpack.config.js
-$( GIT_ADD) webpack.config.js
npm install --save-dev webpack webpack-cli webpack-dev-server
$( ADD_DIR) src/
@echo " $$ WEBPACK_REVEAL_INDEX_JS " > src/index.js
-$( GIT_ADD) src/index.js
@echo " $$ WEBPACK_REVEAL_INDEX_HTML " > index.html
-$( GIT_ADD) index.html
$( MAKE) git-ignore
# --------------------------------------------------------------------------------
# Single-line phony target rules
# --------------------------------------------------------------------------------
.PHONY : aws -check -env -default
aws-check-env-default : aws -check -env -profile aws -check -env -region
.PHONY : ce -default
ce-default : git -commit -edit git -push
.PHONY : clean -default
clean-default : wagtail -clean
.PHONY : cp -default
cp-default : git -commit -message git -push
.PHONY : db -dump -default
db-dump-default : eb -export
.PHONY : dbshell -default
dbshell-default : django -db -shell
2024-08-21 23:08:26 +03:00
.PHONY : d -default
d-default : deploy
2024-08-21 22:32:06 +03:00
.PHONY : deploy -default
deploy-default : eb -deploy
.PHONY : deps -default
deps-default : pip -deps
.PHONY : edit -default
edit-default : readme -edit
2024-08-21 23:08:26 +03:00
.PHONY : e -default
e-default : edit
2024-08-21 22:32:06 +03:00
.PHONY : empty -default
empty-default : git -commit -message -empty git -push
.PHONY : fp -default
fp-default : git -push -force
.PHONY : freeze -default
freeze-default : pip -freeze git -push
.PHONY : git -commit -default
git-commit-default : git -commit -message git -push
.PHONY : git -commit -clean -default
git-commit-clean-default : git -commit -message -clean git -push
.PHONY : git -commit -init -default
git-commit-init-default : git -commit -message -init git -push
.PHONY : git -commit -lint -default
git-commit-lint-default : git -commit -message -lint git -push
.PHONY : gitignore -default
gitignore-default : git -ignore
.PHONY : h -default
h-default : help
.PHONY : init -default
init-default : django -init -wagtail django -serve
.PHONY : init -wagtail -default
init-wagtail-default : django -init -wagtail
.PHONY : install -default
install-default : pip -install
.PHONY : l -default
l-default : makefile -list -commands
.PHONY : last -default
last-default : git -commit -message -last git -push
.PHONY : lint -default
lint-default : django -lint
.PHONY : list -commands -default
list-commands-default : makefile -list -commands
.PHONY : list -defines -default
list-defines-default : makefile -list -defines
.PHONY : list -exports -default
list-exports-default : makefile -list -exports
.PHONY : list -targets -default
list-targets-default : makefile -list -targets
.PHONY : migrate -default
migrate-default : django -migrate
.PHONY : migrations -default
migrations-default : django -migrations -make
.PHONY : migrations -show -default
migrations-show-default : django -migrations -show
.PHONY : mk -default
mk-default : project .mk git -commit -message -mk git -push
.PHONY : o -default
o-default : django -open
2024-08-21 23:08:26 +03:00
.PHONY : open -default
open-default : open
2024-08-21 22:32:06 +03:00
.PHONY : readme -default
readme-default : readme -init
.PHONY : rename -default
rename-default : git -commit -message -rename git -push
.PHONY : s -default
2024-08-21 23:08:26 +03:00
s-default : serve
2024-08-21 22:32:06 +03:00
.PHONY : serve -default
serve-default : django -serve
2024-08-21 23:08:26 +03:00
.PHONY : shell -default
shell-default : django -shell
2024-08-21 22:32:06 +03:00
.PHONY : static -default
static-default : django -static
.PHONY : sort -default
sort-default : git -commit -message -sort git -push
.PHONY : su -default
su-default : django -su
.PHONY : test -default
test-default : django -test
.PHONY : t -default
2024-08-21 23:08:26 +03:00
t-default : test
2024-08-21 22:32:06 +03:00
.PHONY : u -default
u-default : help
.PHONY : urls -default
urls-default : django -urls -show
# --------------------------------------------------------------------------------
# Allow customizing rules defined in this Makefile with rules defined in
# $(MAKEFILE_CUSTOM_FILE)
# --------------------------------------------------------------------------------
% : %-default # https://stackoverflow.com/a/49804748
@ true