initial commit
This commit is contained in:
commit
6c3937ae6a
72
README.md
Normal file
72
README.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# Calendar Generator Flask App
|
||||||
|
|
||||||
|
This Flask application generates a web-based calendar view with images and associated texts for each day. It's designed to read images and texts from specified directories and display them in a calendar format. The web-based pages can be printed to a pdf-file. This is easy, because every browser application has the option to print a page to a pdf-file. Per DIN A4 page 4 days get displayed.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
### Prerequisites
|
||||||
|
- Python 3.x
|
||||||
|
- Flask
|
||||||
|
- Basic understanding of Python and Flask
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. **Clone or Download the Repository**
|
||||||
|
|
||||||
|
Clone this repository if you're familiar with Git, or simply download it as a ZIP file and extract it.
|
||||||
|
|
||||||
|
2. **Install Required Packages**
|
||||||
|
|
||||||
|
Flask is required and can be installed via pip:
|
||||||
|
```bash
|
||||||
|
pip install Flask
|
||||||
|
``````
|
||||||
|
|
||||||
|
### Folder Structure
|
||||||
|
Your project directory should be structured as follows:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
|
||||||
|
project_folder/
|
||||||
|
│ app.py
|
||||||
|
│ README.md
|
||||||
|
│
|
||||||
|
├───static/
|
||||||
|
│ └───images/
|
||||||
|
│ │ 2024-01-15.jpg
|
||||||
|
│ │ 2024-01-16.png
|
||||||
|
│ ...
|
||||||
|
│
|
||||||
|
└───texts/
|
||||||
|
│ 2024-01-15.txt
|
||||||
|
│ 2024-01-16.txt
|
||||||
|
...
|
||||||
|
```
|
||||||
|
- `app.py`: Main Flask application file.
|
||||||
|
- `static/images/`: Directory for storing calendar images. Images should be named `YYYY-MM-DD.extension`.
|
||||||
|
- `texts/`: Directory for storing text files corresponding to each day, named `YYYY-MM-DD.txt`.
|
||||||
|
|
||||||
|
### Running the Application
|
||||||
|
|
||||||
|
1. Navigate to the project folder in your command line or terminal.
|
||||||
|
2. Execute the Flask application:
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
``````
|
||||||
|
3. Access the calendar in a web browser at `http://127.0.0.1:5000/`.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
- The application serves a web page displaying a calendar.
|
||||||
|
- Each day on the calendar can have an image and a text associated with it.
|
||||||
|
- Images are read from `static/images/`, and text files are from `texts/`.
|
||||||
|
- Files are matched based on their filenames, formatted as `YYYY-MM-DD`.
|
||||||
|
- The calendar is dynamically generated based on available images and texts.
|
||||||
|
- If an image or text is not available for a specific day, it will be displayed without that content.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Text for each day should be concise. Lengthy texts may be truncated in the display to maintain the layout of the calendar.
|
||||||
|
- You can customize the appearance and behavior of the calendar by modifying the HTML, CSS, and Flask application logic.
|
||||||
|
|
||||||
|
## Copyright and license
|
||||||
|
This code is for my very good friend Felix W.. Code copyright 2024 Lorenz B.
|
89
app.py
Normal file
89
app.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
from flask import Flask, render_template
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
def get_dates_from_images(image_directory):
|
||||||
|
# Regular expression to match files with a date format YYYY-MM-DD
|
||||||
|
date_pattern = re.compile(r'(\d{4}-\d{2}-\d{2})')
|
||||||
|
|
||||||
|
# List to store the dates
|
||||||
|
dates = []
|
||||||
|
|
||||||
|
# Iterate over each file in the images directory
|
||||||
|
for filename in os.listdir(image_directory):
|
||||||
|
# Search for the pattern in the filename
|
||||||
|
match = date_pattern.search(filename)
|
||||||
|
if match:
|
||||||
|
# Extract the date from the filename
|
||||||
|
try:
|
||||||
|
date = datetime.strptime(match.group(), '%Y-%m-%d')
|
||||||
|
dates.append(date)
|
||||||
|
except ValueError:
|
||||||
|
# If the date format is incorrect, ignore the file
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Return the sorted list of dates
|
||||||
|
return sorted(dates)
|
||||||
|
|
||||||
|
def get_image_and_text_for_date(date, image_directory, text_directory):
|
||||||
|
formatted_date = date.strftime('%Y-%m-%d')
|
||||||
|
image_file = None
|
||||||
|
text_content = None
|
||||||
|
|
||||||
|
# Check for image file
|
||||||
|
for ext in ['jpg', 'jpeg', 'png']:
|
||||||
|
# Check using full path, but store relative path for HTML
|
||||||
|
image_full_path = os.path.join(image_directory, f"{formatted_date}.{ext}")
|
||||||
|
if os.path.isfile(image_full_path):
|
||||||
|
# Store the relative path from within the 'static' directory
|
||||||
|
image_file = f"images/{formatted_date}.{ext}"
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check for text file
|
||||||
|
text_path = os.path.join(text_directory, f"{formatted_date}.txt")
|
||||||
|
if os.path.isfile(text_path):
|
||||||
|
with open(text_path, 'r') as file:
|
||||||
|
text_content = file.read()
|
||||||
|
|
||||||
|
return image_file, text_content
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def calendar():
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
image_directory = os.path.join(BASE_DIR, 'static', 'images')
|
||||||
|
text_directory = os.path.join(BASE_DIR, 'texts')
|
||||||
|
|
||||||
|
# Get sorted dates from image files
|
||||||
|
dates = get_dates_from_images(image_directory)
|
||||||
|
if not dates:
|
||||||
|
return "No images found with the correct date format in the name.", 404
|
||||||
|
|
||||||
|
# Generate data for each date
|
||||||
|
pages_data = []
|
||||||
|
days_data = []
|
||||||
|
for date in dates:
|
||||||
|
image_file, text_content = get_image_and_text_for_date(date, image_directory, text_directory)
|
||||||
|
days_data.append({
|
||||||
|
'date': date,
|
||||||
|
'image_file': image_file,
|
||||||
|
'text_content': text_content
|
||||||
|
})
|
||||||
|
|
||||||
|
# Check if we have collected data for 4 days, then start a new page
|
||||||
|
if len(days_data) == 4:
|
||||||
|
pages_data.append(days_data)
|
||||||
|
days_data = []
|
||||||
|
|
||||||
|
# Add any remaining days to the last page
|
||||||
|
if days_data:
|
||||||
|
pages_data.append(days_data)
|
||||||
|
|
||||||
|
return render_template('index.html', pages=pages_data)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
BIN
static/images/2024-01-15.jpg
Executable file
BIN
static/images/2024-01-15.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 209 KiB |
107
templates/index.html
Normal file
107
templates/index.html
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
@media print,
|
||||||
|
screen {
|
||||||
|
body {
|
||||||
|
width: 210mm;
|
||||||
|
height: 297mm;
|
||||||
|
margin: 0mm 0mm 0mm 0mm;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
width: 210mm;
|
||||||
|
height: 297mm;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
page-break-after: always;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
width: 10mm;
|
||||||
|
height: 10mm;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid black;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle.top-left {
|
||||||
|
top: 10mm;
|
||||||
|
left: 10mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle.top-right {
|
||||||
|
top: 10mm;
|
||||||
|
right: 10mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
margin-top: 28mm;
|
||||||
|
text-align: center;
|
||||||
|
height: 60mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 60mm;
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-info {
|
||||||
|
text-align: center;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
padding-left: 5mm;
|
||||||
|
padding-right: 5mm;
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{% for page in pages %}
|
||||||
|
<div class="page">
|
||||||
|
{% for day in page %}
|
||||||
|
<div class="day" style="{{ 'border-right: 1px dashed grey; border-bottom: 1px dashed grey;' if loop.index0 == 0 else '' }}
|
||||||
|
{{ 'border-bottom: 1px dashed grey;' if loop.index0 == 1 else '' }}
|
||||||
|
{{ 'border-right: 1px dashed grey;' if loop.index0 == 2 else '' }}">
|
||||||
|
<div class="circle top-left"></div>
|
||||||
|
<div class="circle top-right"></div>
|
||||||
|
<div class="image-container">
|
||||||
|
{% if day.image_file %}
|
||||||
|
<img src="{{ url_for('static', filename=day.image_file) }}" alt="Bild für {{ day.date.strftime('%d. %B %Y') }}" />
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="date-info">
|
||||||
|
<h1>{{ day.date.strftime('%A %d.%m.%Y') }}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
{% if day.text_content %}
|
||||||
|
<p>{{ day.text_content }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
1
texts/2024-01-15.txt
Executable file
1
texts/2024-01-15.txt
Executable file
@ -0,0 +1 @@
|
|||||||
|
Beispiel Text
|
Loading…
Reference in New Issue
Block a user