Compare commits
10 Commits
a0498a3774
...
368f6b645b
| Author | SHA1 | Date | |
|---|---|---|---|
| 368f6b645b | |||
| 4fca9b53d3 | |||
| 36273d7151 | |||
| 5db1626ef1 | |||
| b6b7ac2418 | |||
| 273de237e1 | |||
| be2f212fd1 | |||
| b6f7f7a2b7 | |||
| 9610d995de | |||
| 61809acf44 |
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
all_paw_care/invoice_ninja
|
||||||
|
all_paw_care/templates/markdown_content
|
||||||
|
__pycache__
|
||||||
|
*.py[cod]
|
||||||
|
|
||||||
|
.env*
|
||||||
|
!.env.project
|
||||||
|
!.env.vault
|
||||||
38
Makefile
@ -1,33 +1,21 @@
|
|||||||
IP_ADDR = allpawcare.com
|
# Scripts directory variable
|
||||||
DOMAIN = allpawcare.com
|
SCRIPTS_DIR = "scripts"
|
||||||
USER = awkawb
|
|
||||||
IMG_ORIGINAL = "img_original"
|
# Bootstrap variables
|
||||||
IMG_PUBLIC = "all_paw_care/static/img"
|
BOOTSTRAP_MODULE = "bootstrap-custom/boostrap/scss"
|
||||||
LOCAL_ARCHIVE = $(shell pwd)/public.tar
|
CUSTOM_SCSS = "bootstrap-custom/custom.scss"
|
||||||
PUBLIC_DIR = "$(shell pwd)/public"
|
PUBLIC_CSS = "all_paw_care/static/css/custom.css"
|
||||||
REMOTE_DIR = /home/awkawb
|
|
||||||
|
|
||||||
.PHONY: dev-server
|
.PHONY: dev-server
|
||||||
dev-server:
|
dev-server:
|
||||||
flask run --no-reload
|
flask run --no-reload
|
||||||
|
|
||||||
|
.PHONY: build-custom-bootstrap
|
||||||
|
build-custom-bootstrap:
|
||||||
|
sass -I $(BOOTSTRAP_MODULE) $(CUSTOM_SCSS) $(PUBLIC_CSS)
|
||||||
|
|
||||||
.PHONY: process-images
|
.PHONY: process-images
|
||||||
process-images:
|
process-images:
|
||||||
for
|
./$(SCRIPTS_DIR)/process-images.sh
|
||||||
|
|
||||||
.PHONY: push-archive
|
|
||||||
push-archive: archive
|
|
||||||
scp $(LOCAL_ARCHIVE) root@$(DOMAIN):/srv/www && rm $(LOCAL_ARCHIVE)
|
|
||||||
|
|
||||||
.PHONY: archive
|
|
||||||
archive:
|
|
||||||
tar -cf $(LOCAL_ARCHIVE) $(PUBLIC_DIR)
|
|
||||||
|
|
||||||
.PHONY: ssh
|
|
||||||
ssh:
|
|
||||||
ssh $(USER)@$(DOMAIN)
|
|
||||||
|
|
||||||
.PHONY: ssh-root
|
|
||||||
ssh-root:
|
|
||||||
ssh root@$(DOMAIN)
|
|
||||||
|
|
||||||
|
|||||||
21
TODO.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Code clean-up
|
||||||
|
|
||||||
|
|
||||||
|
## scripts dir
|
||||||
|
|
||||||
|
move process-images.sh here
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Services/<service>
|
||||||
|
|
||||||
|
Route to service documentation page.
|
||||||
|
|
||||||
|
# Hand written typography
|
||||||
|
|
||||||
|
# Hand drawn doodles
|
||||||
|
|
||||||
|
# Banner for accepting/not accepting clients
|
||||||
|
|
||||||
|
Let users of the site know if I'm seeking new clients.
|
||||||
@ -4,9 +4,6 @@ from all_paw_care.pages import pages
|
|||||||
from all_paw_care.users import users
|
from all_paw_care.users import users
|
||||||
from all_paw_care.db.actions import ensure_tables
|
from all_paw_care.db.actions import ensure_tables
|
||||||
|
|
||||||
# App config
|
|
||||||
from config import Config
|
|
||||||
|
|
||||||
# Flask
|
# Flask
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
@ -16,10 +13,10 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
# Initialize app
|
# Initialize app
|
||||||
all_paw_care = Flask(__name__)
|
#
|
||||||
|
# Learn more about instance folders here:
|
||||||
# Import config
|
# https://flask.palletsprojects.com/en/3.0.x/config/#instance-folders
|
||||||
all_paw_care.config.from_object(Config)
|
all_paw_care = Flask(__name__, instance_relative_config=True)
|
||||||
|
|
||||||
# Register app blueprints
|
# Register app blueprints
|
||||||
all_paw_care.register_blueprint(pages)
|
all_paw_care.register_blueprint(pages)
|
||||||
|
|||||||
1
all_paw_care/content/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
4
all_paw_care/content/pages/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from all_paw_care.content.pages import home
|
||||||
|
from all_paw_care.content.pages import faqs
|
||||||
|
from all_paw_care.content.pages import services
|
||||||
|
|
||||||
28
all_paw_care/content/pages/faqs/__init__.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
from markdown import markdown
|
||||||
|
import frontmatter
|
||||||
|
|
||||||
|
markdown_dir = '{}/markdown'.format(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
def __get_content_dict():
|
||||||
|
faqs = dict()
|
||||||
|
for f in os.listdir(markdown_dir):
|
||||||
|
f_abspath = '{}/{}'.format(markdown_dir, f)
|
||||||
|
matter = frontmatter.load(f_abspath)
|
||||||
|
faq = {matter.metadata['question']: {
|
||||||
|
'tag': matter.metadata['tag'],
|
||||||
|
'answer': markdown(matter.content)}}
|
||||||
|
|
||||||
|
faqs.update(faq)
|
||||||
|
|
||||||
|
return faqs
|
||||||
|
|
||||||
|
content = __get_content_dict()
|
||||||
|
def get_content():
|
||||||
|
return content
|
||||||
|
|
||||||
|
title = 'Frequently asked questions'
|
||||||
|
def get_title():
|
||||||
|
return title
|
||||||
|
|
||||||
23
all_paw_care/content/pages/faqs/faq.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from markdown import markdown
|
||||||
|
import frontmatter
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
class FAQ(object):
|
||||||
|
def __init__(self, markdown_file: str):
|
||||||
|
with frontmatter.load(markdown_file) as matter:
|
||||||
|
metadata = matter.metadata
|
||||||
|
self.answer = markdown(matter.content)
|
||||||
|
|
||||||
|
self.question = metadata['question']
|
||||||
|
self.tag = metadata['tag']
|
||||||
|
|
||||||
|
def get_question(self):
|
||||||
|
return self.question
|
||||||
|
|
||||||
|
def get_answer_html(self):
|
||||||
|
return self.answer
|
||||||
|
|
||||||
|
def get_tag(self):
|
||||||
|
return self.tag
|
||||||
|
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
tag: accepted-payments-faq
|
||||||
|
question: What types of payments do you accept?
|
||||||
|
---
|
||||||
|
|
||||||
|
A broad range of payment types are accepted to make the process
|
||||||
|
easy. Below is a list of what is accepted:
|
||||||
|
|
||||||
|
- cash
|
||||||
|
- credit/debit
|
||||||
|
- ACH (bank transfers)
|
||||||
|
- Paypal
|
||||||
|
- Apple Pay
|
||||||
|
- Zelle
|
||||||
|
|
||||||
11
all_paw_care/content/pages/faqs/markdown/animal-types-faq.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
tag: animal-types-faq
|
||||||
|
question: What kind of animals do you caretake?
|
||||||
|
---
|
||||||
|
|
||||||
|
I have experience caring for many types of animals. Below is a
|
||||||
|
non-exhaustive list of animals I have experience with:
|
||||||
|
|
||||||
|
- reptiles (e.g. snakes, lizards, and spiders)
|
||||||
|
- mammals (e.g. cats, dogs, and guinea pigs)
|
||||||
|
|
||||||
10
all_paw_care/content/pages/faqs/markdown/basics-faq.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
tag: basics-faq
|
||||||
|
question: How does this process work?
|
||||||
|
---
|
||||||
|
|
||||||
|
First go to the [services](/pages/services#request-meet-greet) page. On the bottom
|
||||||
|
of the page, you'll find a "Request meet and & greet" button. After
|
||||||
|
filling out the form, I'll get an email with the form information.
|
||||||
|
I will contact you about the request within 24 hours.
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
tag: house-sitting-faq
|
||||||
|
question: Can you watch my pets while I'm away?
|
||||||
|
---
|
||||||
|
|
||||||
|
I offer house sitting services. The service includes overnight
|
||||||
|
in-home stays with your pet.
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
tag: payment-terms-faq
|
||||||
|
question: When is payment for a service due?
|
||||||
|
---
|
||||||
|
|
||||||
|
Payments are due 7 days after invoicing. Invoices are typically sent
|
||||||
|
after services are completed.
|
||||||
|
|
||||||
8
all_paw_care/content/pages/faqs/markdown/plant-faq.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
tag: plant-faq
|
||||||
|
question: I have plants, can you care for them while I'm away?
|
||||||
|
---
|
||||||
|
|
||||||
|
I'll gladly make sure your plants stay healthy while your gone. I love
|
||||||
|
plants and have a collection of my own.
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
tag: poop-scoop-faq
|
||||||
|
question: My yard is a mess, can you remove the dog poop?
|
||||||
|
---
|
||||||
|
|
||||||
|
I do offer dog poop clean-up as an add-on services. I can provide
|
||||||
|
this service along with a scheduled primary service.
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
tag: special-needs-faq
|
||||||
|
question: Can you care for special needs pets?
|
||||||
|
---
|
||||||
|
|
||||||
|
I have experience caring for animals with special needs, including
|
||||||
|
older pets.
|
||||||
|
|
||||||
31
all_paw_care/content/pages/home/__init__.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from flask import url_for
|
||||||
|
from markdown import markdown
|
||||||
|
import frontmatter
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
markdown_dir = '{}/markdown'.format(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
title = 'About me'
|
||||||
|
def get_title():
|
||||||
|
return title
|
||||||
|
|
||||||
|
def __get_content_dict():
|
||||||
|
sections = dict()
|
||||||
|
for file in os.listdir(markdown_dir):
|
||||||
|
images = list()
|
||||||
|
matter = frontmatter.load('{}/{}'.format(markdown_dir, file))
|
||||||
|
for img in matter.metadata['images']:
|
||||||
|
images.append('{}/{}'.format(
|
||||||
|
matter.metadata['image-dir'], img))
|
||||||
|
|
||||||
|
sections[file.removesuffix('.md')] = {'images':
|
||||||
|
images, 'content': markdown(matter.content)}
|
||||||
|
|
||||||
|
return sections
|
||||||
|
|
||||||
|
content = __get_content_dict()
|
||||||
|
def get_content():
|
||||||
|
return content.values()
|
||||||
|
|
||||||
16
all_paw_care/content/pages/home/markdown/section1.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
image-dir: img/about_me
|
||||||
|
images:
|
||||||
|
- about_me_1_sm.jpg
|
||||||
|
- about_me_1_md.jpg
|
||||||
|
- about_me_1_lg.jpg
|
||||||
|
- about_me_1_xl.jpg
|
||||||
|
- about_me_1_xxl.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
Before I provided animal caretaking services, I focused on my interest
|
||||||
|
in computer technologies. I studied computer networking and computer
|
||||||
|
science at [AACC](https://www.aacc.edu). The knowledge I gained from my
|
||||||
|
studies was harnessed writing the code that powers this website and
|
||||||
|
configuring the servers that the code runs on.
|
||||||
|
|
||||||
18
all_paw_care/content/pages/home/markdown/section2.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
image-dir: img/about_me
|
||||||
|
images:
|
||||||
|
- about_me_2_sm.jpg
|
||||||
|
- about_me_2_md.jpg
|
||||||
|
- about_me_2_lg.jpg
|
||||||
|
- about_me_2_xl.jpg
|
||||||
|
- about_me_2_xxl.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
Plants and horticulture are a passion of mine, too. My interest has
|
||||||
|
actively grown since starting caretaking services. Caring for
|
||||||
|
animals in the Baltimore area, I have the opportunity of observing
|
||||||
|
a broad range of vegetation. I've collected cuttings for propagation
|
||||||
|
and many survive and thrive enough to make it to my garden or potted
|
||||||
|
house plants. Succulent type plants are a favorite of mine and I have
|
||||||
|
a ever growing collection.
|
||||||
|
|
||||||
17
all_paw_care/content/pages/home/markdown/section3.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
image-dir: img/about_me
|
||||||
|
images:
|
||||||
|
- about_me_3_sm.jpg
|
||||||
|
- about_me_3_md.jpg
|
||||||
|
- about_me_3_lg.jpg
|
||||||
|
- about_me_3_xl.jpg
|
||||||
|
- about_me_3_xxl.jpg
|
||||||
|
---
|
||||||
|
|
||||||
|
I've been lucky enough to have the opportunity to grow my animal
|
||||||
|
caretaking services to my primary source of income. I started
|
||||||
|
listing availability on the [Rover](https://www.rover.com/) platform
|
||||||
|
in 2018. After gaining loyal clients and reputation, moving my services
|
||||||
|
to being independent was a logical move. I really love providing great
|
||||||
|
and affordable caretaking services.
|
||||||
|
|
||||||
25
all_paw_care/content/pages/services/__init__.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from all_paw_care.content.pages.services.cards import primary_services
|
||||||
|
|
||||||
|
#def __get_content_dict():
|
||||||
|
# content = {'primary-services': dict(), 'addon-services': dict()}
|
||||||
|
# for file in os.listdir(markdown_dir):
|
||||||
|
# images = list()
|
||||||
|
# matter = frontmatter.load('{}/{}'.format(markdown_dir, file))
|
||||||
|
# if matter.metadata['type'] == 'primary':
|
||||||
|
# for img in matter.metadata['images']:
|
||||||
|
# images.append('{}/{}'.format(
|
||||||
|
# matter.metadata['image-dir'], img))
|
||||||
|
#
|
||||||
|
# content['primary-services'].update()
|
||||||
|
#
|
||||||
|
# return content
|
||||||
|
#
|
||||||
|
#content = __get_content_dict()
|
||||||
|
#
|
||||||
|
__title = 'Services & Pricing'
|
||||||
|
def get_title():
|
||||||
|
return __title
|
||||||
|
|
||||||
|
def get_primary_service_cards():
|
||||||
|
return primary_services
|
||||||
|
|
||||||
6
all_paw_care/content/pages/services/cards/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from all_paw_care.content.pages.services.cards.drop_ins import drop_ins
|
||||||
|
from all_paw_care.content.pages.services.cards.house_sitting import house_sitting
|
||||||
|
from all_paw_care.content.pages.services.cards.walking import walking
|
||||||
|
|
||||||
|
primary_services = [drop_ins, house_sitting, walking]
|
||||||
|
|
||||||
37
all_paw_care/content/pages/services/cards/card.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from markdown import markdown
|
||||||
|
import frontmatter
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
class Card(object):
|
||||||
|
def __init__(self, card_file: str):
|
||||||
|
metadata = frontmatter.load(card_file).metadata
|
||||||
|
self.service_type = metadata['type']
|
||||||
|
self.title = metadata['title']
|
||||||
|
self.images = self._get_image_list(
|
||||||
|
metadata['image-dir'], metadata['images'])
|
||||||
|
self.price = metadata['price']
|
||||||
|
self.included = metadata['included']
|
||||||
|
|
||||||
|
def _get_image_list(self, base_dir: str, images: list):
|
||||||
|
image_list = list()
|
||||||
|
for img in images:
|
||||||
|
image_list.append(os.path.join(base_dir, img))
|
||||||
|
|
||||||
|
return image_list
|
||||||
|
|
||||||
|
def get_title(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
def get_price(self):
|
||||||
|
return self.price
|
||||||
|
|
||||||
|
def get_included(self):
|
||||||
|
return self.included
|
||||||
|
|
||||||
|
def get_images(self):
|
||||||
|
return self.images
|
||||||
|
|
||||||
|
def get_default_image(self):
|
||||||
|
return self.images[1]
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
from all_paw_care.content.pages.services.cards.card import Card
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
__markdown_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'drop-ins.md')
|
||||||
|
|
||||||
|
drop_ins = Card(__markdown_file)
|
||||||
|
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
type: primary
|
||||||
|
title: Drop-ins
|
||||||
|
image-dir: img/services
|
||||||
|
images:
|
||||||
|
- drop_in_card_sm.png
|
||||||
|
- drop_in_card_md.png
|
||||||
|
- drop_in_card_lg.png
|
||||||
|
- drop_in_card_xl.png
|
||||||
|
- drop_in_card_xxl.png
|
||||||
|
price: $20/visit
|
||||||
|
included:
|
||||||
|
- 30 minute at home visit
|
||||||
|
- Bags for poop clean-up
|
||||||
|
---
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
from all_paw_care.content.pages.services.cards.card import Card
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
__markdown_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'house-sitting.md')
|
||||||
|
|
||||||
|
house_sitting = Card(__markdown_file)
|
||||||
|
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
type: primary
|
||||||
|
title: House sitting
|
||||||
|
image-dir: img/services
|
||||||
|
images:
|
||||||
|
- house_sitting_card_sm.png
|
||||||
|
- house_sitting_card_md.png
|
||||||
|
- house_sitting_card_lg.png
|
||||||
|
- house_sitting_card_xl.png
|
||||||
|
- house_sitting_card_xxl.png
|
||||||
|
price: $38/night
|
||||||
|
included:
|
||||||
|
- Animal care
|
||||||
|
- At home overnight stays
|
||||||
|
- Bags for poop clean-up
|
||||||
|
---
|
||||||
|
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
from all_paw_care.content.pages.services.cards.card import Card
|
||||||
|
|
||||||
|
from markdown import markdown
|
||||||
|
import frontmatter
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
__markdown_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'walking.md')
|
||||||
|
|
||||||
|
walking = Card(__markdown_file)
|
||||||
|
|
||||||
16
all_paw_care/content/pages/services/cards/walking/walking.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
type: primary
|
||||||
|
title: Walking
|
||||||
|
image-dir: img/services
|
||||||
|
images:
|
||||||
|
- dog_walking_card_sm.png
|
||||||
|
- dog_walking_card_md.png
|
||||||
|
- dog_walking_card_lg.png
|
||||||
|
- dog_walking_card_xl.png
|
||||||
|
- dog_walking_card_xxl.png
|
||||||
|
price: $20/visit
|
||||||
|
included:
|
||||||
|
- 30 minute walk
|
||||||
|
- Bags for poop clean-up
|
||||||
|
---
|
||||||
|
|
||||||
7
all_paw_care/content/pages/services/services.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: Services & Pricing
|
||||||
|
---
|
||||||
|
|
||||||
|
Below are animal caretaking services I provide. New clients should
|
||||||
|
fill out the [meet & greet request](/forms/meet_and_gree) form
|
||||||
|
|
||||||
@ -1,25 +1,10 @@
|
|||||||
# Python Invoice Ninja library
|
|
||||||
#from all_paw_care.invoice_ninja import swagger_client
|
|
||||||
#from all_paw_care.swagger_client.rest import ApiException
|
|
||||||
|
|
||||||
# Invoice Ninja library dependencies
|
|
||||||
#from __future__ import print_function
|
|
||||||
#import time
|
|
||||||
#from pprint import pprint
|
|
||||||
|
|
||||||
BASE_URL='https://invoice.allpawcare.com/api/v1'
|
BASE_URL='https://invoice.allpawcare.com/api/v1'
|
||||||
|
|
||||||
API_TOKEN='r4AKEz9xRgk2SA7IotAvLrj64f7s0BczLDVjVwmjiPWeyG0fpu2eyib4VKI23QNO'
|
API_TOKEN='r4AKEz9xRgk2SA7IotAvLrj64f7s0BczLDVjVwmjiPWeyG0fpu2eyib4VKI23QNO'
|
||||||
|
|
||||||
INVOICE_NINJA_USERNAME = 'billing@allpawcare.com'
|
INVOICE_NINJA_USERNAME = 'billing@allpawcare.com'
|
||||||
INVOICE_NINJA_PASSWORD = """sw'1eMqN6#9fO!3"RY$L"""
|
INVOICE_NINJA_PASSWORD = """sw'1eMqN6#9fO!3"RY$L"""
|
||||||
|
|
||||||
INVOICE_NINJA_LOGIN_DICT = {
|
INVOICE_NINJA_LOGIN_DICT = {
|
||||||
'email': INVOICE_NINJA_USERNAME,
|
'email': INVOICE_NINJA_USERNAME,
|
||||||
'pasword': INVOICE_NINJA_PASSWORD
|
'pasword': INVOICE_NINJA_PASSWORD
|
||||||
}
|
}
|
||||||
|
|
||||||
#invoice_ninja = swagger_client()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
94
all_paw_care/invoice_ninja/clients/__init__.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
from all_paw_care.invoice_ninja import API_TOKEN
|
||||||
|
from all_paw_care.invoice_ninja import BASE_URL
|
||||||
|
from all_paw_care.invoice_ninja.types.client import Client
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class Clients(object):
|
||||||
|
CLIENTS_URL = '{}/clients'.format(BASE_URL)
|
||||||
|
HEADERS = {'X-API-TOKEN': API_TOKEN}
|
||||||
|
|
||||||
|
def __build_sort_params(self, sort: dict):
|
||||||
|
sort_params = {'sort': str()}
|
||||||
|
is_first_entry = True
|
||||||
|
for option in sort.keys():
|
||||||
|
if is_first_entry:
|
||||||
|
sort_params['sort'] += '{}|{}'.format(option, sort[option])
|
||||||
|
is_first_entry = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
sort_params['sort'] += ' {}|{}'.format(option, sort[option])
|
||||||
|
|
||||||
|
return sort_params
|
||||||
|
|
||||||
|
def __client_from_dict(self, client: dict):
|
||||||
|
return Client(client_id=client['id'],
|
||||||
|
name=client['name'],
|
||||||
|
address=client['address1'],
|
||||||
|
city=client['city'],
|
||||||
|
state=client['state'],
|
||||||
|
postal_code=client['postal_code'],
|
||||||
|
phone=client['phone'],
|
||||||
|
email=client['contacts'][0]['email'],
|
||||||
|
pets=client['custom_value1'])
|
||||||
|
|
||||||
|
def __clients_from_response(self, response: requests.Response):
|
||||||
|
clients = list()
|
||||||
|
for client_dict in response.json()['data']:
|
||||||
|
clients.append(self.__client_from_dict(client_dict))
|
||||||
|
|
||||||
|
return clients
|
||||||
|
|
||||||
|
def list_client(self, client_id: str = None):
|
||||||
|
"""
|
||||||
|
Get client based on client id.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if client_id:
|
||||||
|
request_url = '{}/{}'.format(self.CLIENTS_URL, client_id)
|
||||||
|
response = requests.get(url=request_url,
|
||||||
|
headers=self.HEADERS)
|
||||||
|
|
||||||
|
if response.ok:
|
||||||
|
return self.__client_from_dict(response.json()['data'])
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def list_clients(self, include: str = 'activities',
|
||||||
|
sort: dict = dict(), status: str = 'active',
|
||||||
|
name: str = None):
|
||||||
|
"""
|
||||||
|
Finds Invoice Ninja clients.
|
||||||
|
"""
|
||||||
|
|
||||||
|
request_params = dict()
|
||||||
|
|
||||||
|
# Add sort parameters to request
|
||||||
|
if len(sort) > 0:
|
||||||
|
request_params.update(self.__build_sort_params(sort))
|
||||||
|
|
||||||
|
# Add include parameters to request
|
||||||
|
request_params.update({'include': include})
|
||||||
|
|
||||||
|
# Add status parameters to request
|
||||||
|
request_params.update({'status': status})
|
||||||
|
|
||||||
|
# Add name parameters to request
|
||||||
|
if name:
|
||||||
|
request_params.update({'name': name})
|
||||||
|
|
||||||
|
# Check is request should be sent with parameters
|
||||||
|
if len(request_params) > 0:
|
||||||
|
response = requests.get(url=self.CLIENTS_URL,
|
||||||
|
params=request_params,
|
||||||
|
headers=self.HEADERS)
|
||||||
|
else:
|
||||||
|
response = requests.get(url=self.CLIENTS_URL,
|
||||||
|
headers=self.HEADERS)
|
||||||
|
|
||||||
|
if response.ok:
|
||||||
|
return self.__clients_from_response(response)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
class Sort(object):
|
|
||||||
def __init__(self, id: str = None,
|
|
||||||
name: str = None,
|
|
||||||
balance: str = None):
|
|
||||||
self.sort_params = {'sort': str()}
|
|
||||||
is_first_entry = True
|
|
||||||
if id:
|
|
||||||
if is_first_entry:
|
|
||||||
self.sort_params['sort'] += '{}|{}'.format(option, sort[option])
|
|
||||||
is_first_entry = False
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.sort_params['sort'] += ' {}|{}'.format(option, sort[option])
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
from all_paw_care.invoice_ninja import API_TOKEN
|
from all_paw_care.invoice_ninja import API_TOKEN
|
||||||
from all_paw_care.invoice_ninja import BASE_URL
|
from all_paw_care.invoice_ninja import BASE_URL
|
||||||
from all_paw_care.invoice_ninja.mappings.client import Client
|
from all_paw_care.invoice_ninja.types.product import Product
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
class Clients(object):
|
class Products(object):
|
||||||
CLIENTS_URL = '{}/clients'.format(BASE_URL)
|
CLIENTS_URL = '{}/clients'.format(BASE_URL)
|
||||||
HEADERS = {'X-API-TOKEN': API_TOKEN}
|
HEADERS = {'X-API-TOKEN': API_TOKEN}
|
||||||
|
|
||||||
@ -1,17 +1,18 @@
|
|||||||
from all_paw_care.pages import pages
|
from all_paw_care.pages import pages
|
||||||
|
from all_paw_care.content import pages as site_pages
|
||||||
|
|
||||||
from flask import render_template,url_for
|
from flask import render_template,url_for
|
||||||
|
|
||||||
@pages.route('/')
|
@pages.route('/')
|
||||||
@pages.route('/home')
|
@pages.route('/home')
|
||||||
def home():
|
def home():
|
||||||
return render_template("pages/index.html")
|
return render_template("pages/index.html", page=site_pages.home)
|
||||||
|
|
||||||
@pages.route('/faq')
|
@pages.route('/faq')
|
||||||
def faq():
|
def faq():
|
||||||
return render_template("pages/faq.html")
|
return render_template("pages/faq.html", page=site_pages.faqs)
|
||||||
|
|
||||||
@pages.route('/services')
|
@pages.route('/services')
|
||||||
def services():
|
def services():
|
||||||
return render_template("pages/services.html")
|
return render_template("pages/services.html", page=site_pages.services)
|
||||||
|
|
||||||
|
|||||||
9617
all_paw_care/static/css/custom.css
Normal file
1
all_paw_care/static/css/custom.css.map
Normal file
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 132 KiB |
BIN
all_paw_care/static/img/about_me_1_lg.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
all_paw_care/static/img/about_me_1_md.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
all_paw_care/static/img/about_me_1_sm.jpg
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
all_paw_care/static/img/about_me_1_xl.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
all_paw_care/static/img/about_me_1_xxl.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
all_paw_care/static/img/about_me_2_lg.jpg
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
all_paw_care/static/img/about_me_2_md.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
all_paw_care/static/img/about_me_2_sm.jpg
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
all_paw_care/static/img/about_me_2_xl.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
all_paw_care/static/img/about_me_2_xxl.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
all_paw_care/static/img/about_me_3_lg.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
all_paw_care/static/img/about_me_3_md.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
all_paw_care/static/img/about_me_3_sm.jpg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
all_paw_care/static/img/about_me_3_xl.jpg
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
all_paw_care/static/img/about_me_3_xxl.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
all_paw_care/static/img/logo_instagram_lg.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
all_paw_care/static/img/logo_instagram_md.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
all_paw_care/static/img/logo_instagram_sm.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
all_paw_care/static/img/logo_instagram_xl.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
all_paw_care/static/img/logo_instagram_xxl.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
all_paw_care/static/img/services_dog_walking_card_lg.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
all_paw_care/static/img/services_dog_walking_card_md.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
all_paw_care/static/img/services_dog_walking_card_sm.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
all_paw_care/static/img/services_dog_walking_card_xl.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
all_paw_care/static/img/services_dog_walking_card_xxl.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
all_paw_care/static/img/services_drop_in_card_lg.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
all_paw_care/static/img/services_drop_in_card_md.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
all_paw_care/static/img/services_drop_in_card_sm.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
all_paw_care/static/img/services_drop_in_card_xl.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
all_paw_care/static/img/services_drop_in_card_xxl.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
all_paw_care/static/img/services_house_sitting_card_lg.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
all_paw_care/static/img/services_house_sitting_card_md.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
all_paw_care/static/img/services_house_sitting_card_sm.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
all_paw_care/static/img/services_house_sitting_card_xl.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
all_paw_care/static/img/services_house_sitting_card_xxl.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
@ -1,10 +1,14 @@
|
|||||||
<footer class="d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top">
|
<footer class="footer mt-5 bg-tertiary">
|
||||||
<p class="col-md-4 mb-0 text-muted">
|
<div class="container">
|
||||||
© {{ current_year }} Andrew Bryant
|
<nav class="navbar navbar-expand-sm text-light">
|
||||||
</p>
|
<div class="container">
|
||||||
|
<p class="col-md-4 mb-0 text-muted">
|
||||||
|
© {{ current_year }} Andrew Bryant
|
||||||
|
</p>
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
{% include "jinja/menu.html" %}
|
||||||
{% include "jinja/menu.html" %}
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,17 @@
|
|||||||
<header>
|
<header>
|
||||||
<nav class="navbar navbar-expand-md sticky-top navbar-dark bg-dark">
|
<nav class="navbar navbar-expand-sm sticky-top bg-light" data-bs-theme="light">
|
||||||
<div class="container-fluid">
|
<div class="container">
|
||||||
{% include "jinja/menu.html" %}
|
<button class="navbar-toggler" type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#menu-bar-items"
|
||||||
|
aria-controls="menu-bar-items"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle menu bar">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="menu-bar-items">
|
||||||
|
{% include "jinja/menu.html" %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
19
all_paw_care/templates/jinja/menu (copy).html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{% for page in pages %}
|
||||||
|
<li>
|
||||||
|
{% if title == page %}
|
||||||
|
<a href="{{ pages[page] }}" class="nav-link text-secondary">
|
||||||
|
<svg class="bi d-block mx-auto mb-1" width="24" height="24">
|
||||||
|
<use xlink:href="#home"></use>
|
||||||
|
</svg>
|
||||||
|
{{ page }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ pages[page] }}" class="nav-link text-white">
|
||||||
|
<svg class="bi d-block mx-auto mb-1" width="24" height="24">
|
||||||
|
<use xlink:href="#home"></use>
|
||||||
|
</svg>
|
||||||
|
{{ page }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
@ -1,30 +1,30 @@
|
|||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{ url_for('pages.home') }}" class="nav-link text-secondary">
|
<a href="{{ url_for('pages.home') }}" class="nav-link link-white">
|
||||||
Home
|
Home
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{ url_for('pages.faq') }}" class="nav-link text-secondary">
|
<a href="{{ url_for('pages.faq') }}" class="nav-link link-white">
|
||||||
FAQ
|
FAQ
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{ url_for('pages.services') }}" class="nav-link text-secondary">
|
<a href="{{ url_for('pages.services') }}" class="nav-link link-white">
|
||||||
Services
|
Services
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{ url_for('users.user_login') }}" class="nav-link text-secondary">
|
<a href="{{ url_for('users.user_login') }}" class="nav-link link-white">
|
||||||
Login
|
Login
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{ url_for('users.user_create') }}" class="nav-link text-secondary">
|
<a href="{{ url_for('users.user_create') }}" class="nav-link link-white">
|
||||||
Create account
|
Create account
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -6,8 +6,7 @@
|
|||||||
<title>
|
<title>
|
||||||
{% block title %}{% endblock %}
|
{% block title %}{% endblock %}
|
||||||
</title>
|
</title>
|
||||||
|
<link href="{{ url_for('static', filename="css/custom.css") }}" rel="stylesheet">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% include "jinja/header.html" %}
|
{% include "jinja/header.html" %}
|
||||||
|
|||||||
@ -4,15 +4,12 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
<link href="{{ url_for('static', filename="css/custom.css") }}" rel="stylesheet">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="bg-white">
|
||||||
{% include "jinja/header.html" %}
|
{% include "jinja/header.html" %}
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<hr class="invisible pb-3">
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,36 +1,33 @@
|
|||||||
{% extends "jinja/types/page.html" %}
|
{% extends "jinja/types/page.html" %}
|
||||||
|
|
||||||
{% set title %}FAQ{% endset %}
|
{% set title %}{{ page.get_title() }}{% endset %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row text-center mb-2">
|
<div class="row text-center mb-2">
|
||||||
<h1 class="text-primary display-1">
|
<h1 class="text-primary display-1">
|
||||||
Frequently asked questions
|
{{ page.get_title() }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for faq in config.FAQS.items() %}
|
<div class="d-grid gap-3 btn-group-vertical" role="group" id="faq-group">
|
||||||
<div class="row">
|
{% for faq in page.get_content() %}
|
||||||
<div class="col-1">
|
<button class="btn btn-secondary text-light text-center"
|
||||||
</div>
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#{{ page.get_content()[faq]['tag'] }}"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="{{ page.get_content()[faq]['tag'] }}">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
{{ faq }}
|
||||||
|
</h2>
|
||||||
|
</button>
|
||||||
|
|
||||||
<a class="btn btn-primary text-center" data-bs-toggle="collapse"
|
<div class="collapse" id="{{ page.get_content()[faq]['tag'] }}">
|
||||||
href="#{{ faq[1][0] }}" role="button" aria-expanded="false"
|
<div class="card card-body text-center text-secondary">
|
||||||
aria-controls="{{ faq[1][0] }}">
|
{{ page.get_content()[faq]['answer']|safe }}
|
||||||
<h2>{{ faq[0] }}</h2>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="collapse" id="{{ faq[1][0] }}">
|
|
||||||
<div class="card card-body text-center">
|
|
||||||
{{ faq[1][1] }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-1">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="invisible">
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@ -1,37 +1,47 @@
|
|||||||
{% extends "jinja/types/page.html" %}
|
{% extends "jinja/types/page.html" %}
|
||||||
|
|
||||||
{% set title %}About me{% endset %}
|
{% set title %}{{ page.get_title() }}{% endset %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row text-center">
|
<div class="row text-center">
|
||||||
<h1 class="text-primary display-1">
|
<h1 class="text-primary display-1">
|
||||||
About me
|
{{ page.get_title() }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for row in config.ABOUT_ME %}
|
<div class="d-grid gap-3">
|
||||||
<div class="row pb-3">
|
{% for section in page.get_content() %}
|
||||||
<div class="col-8 text-center {{ loop.cycle('order-first','order-last') }}">
|
<div class="card border-0 d-flex rounded-1 bg-dark">
|
||||||
<p class="text-secondary lead">
|
<div class="card-body">
|
||||||
{{ row[1] }}
|
<div class="row">
|
||||||
</p>
|
<div class="col-sm-8 {{ loop.cycle('order-first','order-last') }}">
|
||||||
</div>
|
<div class="card-text text-light text-center lead">
|
||||||
|
{{ section['content']|safe }}
|
||||||
<div class="col-4 {{ loop.cycle('order-last','order-first') }}">
|
</div>
|
||||||
<picture>
|
</div>
|
||||||
{% for img in row[0] %}
|
|
||||||
{% if not loop.last %}
|
<div class="col-sm-4 {{ loop.cycle('order-last','order-first') }}">
|
||||||
<source srcset="{{ img }}"
|
<div class="card-img">
|
||||||
media="(max-width: {{ config.IMG_BREAKPOINTS[loop.index] }})" />
|
<picture>
|
||||||
{% else %}
|
{% for img in section['images'] %}
|
||||||
<source srcset="{{ img }}"
|
{% if not loop.last %}
|
||||||
media="(min-width: {{ config.IMG_BREAKPOINTS[loop.index] }})" />
|
<source srcset="{{ url_for('static', filename=img) }}"
|
||||||
{% endif %}
|
media="(max-width: {{ config.IMG_BREAKPOINTS[loop.index] }})" />
|
||||||
{% endfor %}
|
{% else %}
|
||||||
|
<source srcset="{{ url_for('static', filename=img) }}"
|
||||||
|
media="(min-width: {{ config.IMG_BREAKPOINTS[loop.index] }})" />
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<img class="img-fluid" src="{{ row[0][1] }}">
|
<img class="img-fluid rounded-1"
|
||||||
</picture>
|
src="{{ url_for('static', filename=section['images'][1]) }}">
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||