Compare commits

...

27 Commits
0.5.1 ... 0.6.2

Author SHA1 Message Date
vabene1111
4cf6a3b219 Merge pull request #45 from tourn/improved-meal-plan
Add buttons to add a meal plan to a specific point in time
2020-04-05 14:47:55 +02:00
tourn
de145b6b18 Add some styling 2020-04-05 11:10:39 +02:00
vabene1111
84a8308bf3 updated tabulator js to 4.6 2020-03-31 01:20:49 +02:00
vabene1111
8d191fa1a1 set recipe name as page title 2020-03-31 01:11:14 +02:00
vabene1111
b47a0197e2 imroved recipe printing and view 2020-03-31 01:10:30 +02:00
tourn
4e7c5f9495 Add buttons to add a meal plan to a specific point in time 2020-03-30 22:06:17 +02:00
vabene1111
d704ddacdd fixed shopping list format 2020-03-27 21:47:00 +01:00
vabene1111
2c3140248c fixed broken links 2020-03-27 21:41:55 +01:00
vabene1111
6d5ea31f8e re added mistakingly removed command 2020-03-26 18:55:35 +01:00
vabene1111
b538761746 run container as root for now
since i want to realease this we will for now continue to run this as root inside the containerr. this can be fixed later, PR's welcome
2020-03-26 18:20:44 +01:00
vabene1111
913d858473 updated boot script to fix permission 2020-03-25 22:30:44 +01:00
vabene1111
574d088cdd Create docker-publish.yml 2020-03-24 18:04:52 +01:00
vabene1111
ed360ca1c7 updated readme 2020-03-24 17:37:16 +01:00
vabene1111
08848da4a3 Merge branch 'feature/docker-rewrite' into develop 2020-03-24 17:23:04 +01:00
vabene1111
3f1f63d7e0 remove image tag as it is default 2020-03-24 17:22:51 +01:00
vabene1111
3bd6557e59 use stable image 2020-03-24 17:18:27 +01:00
vabene1111
23cb98f631 Create docker-release-publish.yml 2020-03-24 17:16:24 +01:00
vabene1111
6e91c30245 fixed tests 2020-03-24 17:03:52 +01:00
vabene1111
d7e0fa821b updated examples 2020-03-24 16:44:19 +01:00
vabene1111
c67342df26 wip changes 2020-03-24 12:57:45 +01:00
vabene1111
e3b71d47f4 Merge pull request #39 from h4llow3En/develop
Run as alpine docker image and server static files with gunicorn
2020-03-23 20:03:09 +01:00
h4llow3En
1e3e03e4af Simplify first user creation 2020-03-20 10:50:34 +01:00
h4llow3En
391ab5ddac Don't use "latest" images 2020-03-20 10:02:59 +01:00
h4llow3En
e0c560c2d7 Remove debug output from Dockerfile 2020-03-20 09:28:33 +01:00
h4llow3En
be942bcb79 Fix "Update" description in readme 2020-03-19 18:02:28 +01:00
h4llow3En
6b27f0c8ab Cleanup and simplify deployment 2020-03-19 15:08:53 +01:00
h4llow3En
cc931189e8 Run as alpine docker image and server static files with gunicorn 2020-03-19 10:13:49 +01:00
25 changed files with 228 additions and 146 deletions

View File

@@ -7,4 +7,12 @@ docker-compose*
.gitignore
README.md
LICENSE
.vscode
.vscode
.env
.env.template
.github
.idea
LICENSE.md
docs
nginx
update.sh

View File

@@ -1,7 +1,3 @@
VIRTUAL_HOST=
LETSENCRYPT_HOST=
LETSENCRYPT_EMAIL=
DEBUG=1
ALLOWED_HOSTS=*
SECRET_KEY=

13
.github/workflows/docker-publish.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Publish Docker
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@2.13
with:
name: vabene1111/recipes
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -0,0 +1,18 @@
name: Deploy Docker Image
on:
push:
tags:
- '*'
jobs:
build-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Publish to Registry
uses: AhnSeongHyun/action-tag-docker-build-push@v1.0.0
with:
repo_name: vabene1111/recipes
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -21,6 +21,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
python3 manage.py collectstatic --noinput
- name: Django Testing project
run: |
python3 manage.py test

2
.gitignore vendored
View File

@@ -76,4 +76,4 @@ staticfiles/
postgresql/
/docker-compose.yml
/docker-compose.override.yml

View File

@@ -1,24 +1,18 @@
FROM python:3.8-slim-buster
RUN mkdir /Recipes
WORKDIR /Recipes
ADD . /Recipes/
RUN apt-get update
RUN apt-get -y upgrade
RUN apt-get install -y \
python3 \
python3-pip \
postgresql-client \
gettext
RUN pip3 install --upgrade pip
RUN pip3 install -r requirements.txt
RUN apt-get autoremove -y
FROM python:3.8-alpine
RUN apk add --no-cache postgresql-libs gettext zlib libjpeg libxml2-dev libxslt-dev
ENV PYTHONUNBUFFERED 1
EXPOSE 8080
EXPOSE 8080
RUN mkdir /opt/recipes
WORKDIR /opt/recipes
COPY . ./
RUN chmod +x boot.sh setup.sh
RUN ln -s /opt/recipes/setup.sh /usr/local/bin/createsuperuser
RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev && \
python -m venv venv && \
venv/bin/pip install -r requirements.txt --no-cache-dir &&\
apk --purge del .build-deps
ENTRYPOINT ["/opt/recipes/boot.sh"]

View File

@@ -19,11 +19,38 @@ Recipes is a Django application to manage, tag and search recipes using either b
This application is meant for people with a collection of recipes they want to share with family and friends or simply store them in a nicely organized way. A basic permission system exists but this application is not meant to be run as a public page.
# Installation
The docker image (`vabene1111/recipes`) simply exposes the application on port `8080`. You may choose any preferred installation method, the following are just examples to make it easier.
### Docker-Compose
2. Choose one of the included configurations [here](https://github.com/vabene1111/recipes/tree/develop/docs/docker).
2. Download the environment (config) file template and fill it out `wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O .env `
3. Start the container `docker-compose up -d`
4. Create a default user by running `docker-compose exec web_recipes createsuperuser`.
### Manual
Copy `.env.template` to `.env` and fill in the missing values accordingly.
Make sure all variables are available to whatever serves your application.
Otherwise simply follow the instructions for any django based deployment
(for example [this one](http://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html)).
## Updating
While intermediate updates can be skipped when updating please make sure to **read the release notes** in case some special action is required to update.
0. Before updating it is recommended to **create a backup!**
1. Stop the container using `docker-compose down`
2. Pull the latest image using `docker-compose pull`
3. Start the container again using `docker-compose up -d`
# Documentation
Most things should be straight forward but there are some more complicated things.
##### Storage Backends
A `Storage Backend` is a remote storage location where files are stored. To add a new backend click on `Storage Data` and then on `Storage Backends`. There click the plus button.
A `Storage Backend` is a remote storage location where PDF files are read from. To add a new backend click on `Storage Data` and then on `Storage Backends`. There click the plus button.
Enter a name (just a display name for you to identify it) and an API access Token for the account you want to use.
Dropboxes API tokens can be found on the [Dropboxes API explorer](https://dropbox.github.io/dropbox-api-v2-explorer/#auth_token/from_oauth1)
@@ -44,29 +71,6 @@ When clicking submit, every recipe containing the word will be updated (tags are
> Currently the only option is word contains, maybe some more SQL like operators will be added later.
## Installation
### Docker-Compose
1. Clone this repository to your desired install location
2. Choose one of the included `docker-compose.yml` files [here](https://github.com/vabene1111/recipes/tree/develop/docs/docker).
3. Copy it to the root directory (where this readme is)
4. Start the container (`docker-compose up -d`)
5. This time and **on each update** run `update.sh` to apply migrations and collect static files
6. Create a default user by executing into the container with `docker-compose exec web_recipes sh` and run `python3 manage.py createsuperuser`.
### Manual
Copy `.env.template` to `.env` and fill in the missing values accordingly.
Make sure all variables are available to whatever serves your application.
Otherwise simply follow the instructions for any django based deployment
(for example [this one](http://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html)).
## Updating
0. Before updating it is recommended to **backup your database**
1. Stop the container using `docker-compose down`.
2. Pull the project files and start the container again using `docker-compose up -d --build`.
3. Run `update.sh`
## Contributing
Pull Requests and ideas are welcome, feel free to contribute in any way.
For any questions on how to work with django please refer to their excellent [documentation](https://www.djangoproject.com/start/).

9
boot.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/bin/sh
source venv/bin/activate
echo "Updating database"
python manage.py migrate
python manage.py collectstatic --noinput
echo "Done"
exec gunicorn -b :8080 --access-logfile - --error-logfile - recipes.wsgi

File diff suppressed because one or more lines are too long

View File

@@ -8,6 +8,25 @@
{% endblock %}
{% block content %}
<style>
.mealplan-cell .mealplan-add-button{
text-align: center;
display: block;
}
@media (hover: hover) {
.mealplan-cell .mealplan-add-button{
visibility: hidden;
float: right;
display: inline;
}
.mealplan-cell:hover .mealplan-add-button{
visibility: initial;
}
}
</style>
<h3>
{% trans 'Meal-Plan' %} <a href="{% url 'new_meal_plan' %}"><i class="fas fa-plus-circle"></i></a>
@@ -53,7 +72,8 @@
</tr>
<tr>
{% for day_key, days_value in plan_value.days.items %}
<td>
<td class="mealplan-cell">
<a class="mealplan-add-button" href="{% url 'new_meal_plan' %}?date={{ day_key|date:'Y-m-d' }}&meal={{ plan_key }}"><i class="fas fa-plus"></i></a>
{% for mp in days_value %}
<a href="{% url 'edit_meal_plan' mp.pk %}"><i class="fas fa-edit"></i></a>
<a href="{% url 'view_recipe' mp.recipe.id %}">{{ mp.recipe.name }}</a><br/>

View File

@@ -4,7 +4,7 @@
{% load l10n %}
{% load custom_tags %}
{% block title %}{% trans 'View' %}{% endblock %}
{% block title %}{{ recipe.name }}{% endblock %}
{% block extra_head %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pretty-checkbox@3.0/dist/pretty-checkbox.min.css"
@@ -60,6 +60,8 @@
{% endif %}
<a class="btn btn-info" href="{% url 'new_meal_plan' %}?recipe={{ recipe.pk }}"><i
class="fas fa-calendar"></i></a>
<a class="btn btn-light" onclick="window.print()"><i
class="fas fa-print"></i></a>
</div>
</div>
@@ -98,7 +100,7 @@
<div class="row">
{% if ingredients %}
<div class="col-lg-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2">
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2">
<div class="card">
<div class="card-body">
<div class="row">
@@ -148,12 +150,16 @@
</td>
<td style="vertical-align: middle!important;">
{% if i.note %}
<button class="btn btn-light btn-sm" type="button" data-container="body"
<button class="btn btn-light btn-sm d-print-none" type="button"
data-container="body"
data-toggle="popover"
data-placement="right" data-html="true" data-trigger="focus"
data-content="{{ i.note }}">
<i class="fas fa-info"></i>
</button>
<div class="d-none d-print-block">
<i class="far fa-comment-alt"></i> {{ i.note }}
</div>
{% endif %}
</td>
</tr>
@@ -173,7 +179,7 @@
{% endif %}
{% if recipe.image %}
<div class="col-md-6 order-md-2 col-sm-12 order-sm-1 col-12 order-1 " style="text-align: center">
<div class="col-12 order-1 col-sm-12 order-sm-1 col-md-6 order-md-2" style="text-align: center">
<img class="img img-fluid rounded" src="{{ recipe.image.url }}" style="max-height: 30vh;"
alt="{% trans 'Recipe Image' %}">
<br/>
@@ -294,7 +300,8 @@
<br/>
<br/>
<h5><i class="far fa-comments"></i> {% trans 'Comments' %}</h5>
<h5 {% if not comments %}class="d-print-none" {% endif %}><i class="far fa-comments"></i> {% trans 'Comments' %}
</h5>
{% for c in comments %}
<div class="card">
<div class="card-body">

View File

@@ -29,4 +29,4 @@ def delete_url(model, pk):
def markdown(value):
tags = markdown_tags + ['pre', 'table', 'td', 'tr', 'th', 'tbody', 'style', 'thead']
parsed_md = md.markdown(value, extensions=['markdown.extensions.fenced_code', 'tables', MarkdownFormatExtension()])
return bleach.clean(parsed_md, tags, print_attrs, markdown_attrs)
return bleach.clean(parsed_md, tags, markdown_attrs)

View File

@@ -1,4 +1,5 @@
import re
from datetime import datetime
from django.contrib import messages
from django.contrib.auth.decorators import login_required
@@ -120,6 +121,12 @@ class MealPlanCreate(LoginRequiredMixin, CreateView):
form_class = MealPlanForm
success_url = reverse_lazy('view_plan')
def get_initial(self):
return dict(
meal=self.request.GET['meal'] if 'meal' in self.request.GET else None,
date=datetime.strptime(self.request.GET['date'], '%Y-%m-%d') if 'date' in self.request.GET else None
)
def form_valid(self, form):
obj = form.save(commit=False)
obj.user = self.request.user

View File

@@ -137,7 +137,8 @@ def shopping_list(request):
if Recipe.objects.filter(pk=int(r)).exists():
recipes.append(int(r))
form = ShoppingForm(initial={'recipe': recipes})
markdown_format = False
form = ShoppingForm(initial={'recipe': recipes, 'markdown_format': False})
ingredients = []

View File

@@ -2,3 +2,10 @@ This is a docker compose example when using [jwilder's nginx reverse proxy](http
in combination with [jrcs's letsencrypt companion](https://hub.docker.com/r/jrcs/letsencrypt-nginx-proxy-companion/).
Please refer to the appropriate documentation on how to setup the reverse proxy and networks.
Remember to add the appropriate environment variables to `.env` file:
```
VIRTUAL_HOST=
LETSENCRYPT_HOST=
LETSENCRYPT_EMAIL=
```

View File

@@ -2,42 +2,42 @@ version: "3"
services:
db_recipes:
restart: always
image: "postgres:11-alpine"
image: postgres:11-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
- ./postgresql:/var/lib/postgresql/data
env_file:
- ./.env
- ./.env
networks:
- default
- default
web_recipes:
build: .
image: vabene1111/recipes
restart: always
env_file:
- ./.env
command: "gunicorn --bind 0.0.0.0:8080 recipes.wsgi"
- ./.env
volumes:
- .:/Recipes
- ./staticfiles:/opt/recipes/staticfiles
- ./mediafiles:/opt/recipes/mediafiles
depends_on:
- db_recipes
- db_recipes
networks:
- default
- default
nginx_recipes:
image: "nginx"
restart: always
env_file:
- ./.env
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./staticfiles:/static
- ./mediafiles:/media
networks:
- default
- nginx-proxy
nginx_recipes:
image: nginx:mainline-alpine
restart: always
env_file:
- ./.env
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./staticfiles:/static
- ./mediafiles:/media
networks:
- default
- nginx-proxy
networks:
default:
nginx-proxy:
external:
name: nginx-proxy
name: nginx-proxy

View File

@@ -0,0 +1,5 @@
This is the most basic configuration to run this image with docker compose.
> **NOTE**: There is no proxy included in this configuration and gunicorn is directly exposed as the webserver which is
> not recommended by according to the [gunicorn devs](https://serverfault.com/questions/331256/why-do-i-need-nginx-and-something-like-gunicorn).
> It is higly recommended to configure an additional proxy (nginx, ...) in front of this.

View File

@@ -2,39 +2,28 @@ version: "3"
services:
db_recipes:
restart: always
image: "postgres:11-alpine"
image: postgres:11-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
- ./postgresql:/var/lib/postgresql/data
env_file:
- ./.env
- ./.env
networks:
- default
- default
web_recipes:
build: .
image: vabene1111/recipes
restart: always
env_file:
- ./.env
command: "gunicorn --bind 0.0.0.0:8080 recipes.wsgi"
- ./.env
volumes:
- .:/Recipes
depends_on:
- db_recipes
networks:
- default
nginx_recipes:
image: "nginx"
restart: always
env_file:
- ./.env
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./staticfiles:/static
- ./mediafiles:/media
- ./staticfiles:/opt/recipes/staticfiles
- ./mediafiles:/opt/recipes/mediafiles
ports:
- 80:80
networks:
- default
- 80:8080
depends_on:
- web_recipes
- db_recipes
networks:
- default
networks:
default:

View File

@@ -2,39 +2,34 @@ version: "3"
services:
db_recipes:
restart: always
image: "postgres:11-alpine"
image: postgres:11-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
- ./postgresql:/var/lib/postgresql/data
env_file:
- ./.env
- ./.env
networks:
- default
web_recipes:
build: .
image: vabene1111/recipes
restart: always
env_file:
- ./.env
command: "gunicorn --bind 0.0.0.0:8080 recipes.wsgi"
- ./.env
volumes:
- .:/Recipes
- ./staticfiles:/opt/recipes/staticfiles
- ./mediafiles:/opt/recipes/mediafiles
depends_on:
- db_recipes
nginx_recipes:
image: "nginx"
restart: always
env_file:
- ./.env
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./staticfiles:/static
- ./mediafiles:/media
labels:
- db_recipes
labels: # This lables are only examples!
- "traefik.enable=true"
- "traefik.http.routers.recipes.rule=Host(`recipes.mydomain.com`, `recipes.myotherdomain.com`)"
- "traefik.http.routers.recipes.entrypoints=web_secure"
- "traefik.http.routers.recipes.tls.certresolver=le_resolver"
networks:
- default
- traefik
networks:
default:
external:
name: traefik
default:
traefik: # This is you external traefic network
external: true

View File

@@ -58,6 +58,7 @@ INSTALLED_APPS = [
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@@ -133,8 +134,8 @@ USE_L10N = True
USE_TZ = True
LANGUAGES = [
('de', _('German')),
('en', _('English')),
('de', _('German')),
('en', _('English')),
]
# Static files (CSS, JavaScript, Images)
@@ -145,3 +146,6 @@ STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "mediafiles")
# Serve static files with gzip
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

View File

@@ -17,4 +17,5 @@ lxml
webdavclient3
python-dotenv
psycopg2-binary
whitenoise
gunicorn

5
setup.sh Normal file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
source venv/bin/activate
echo "Creating Superuser."
python manage.py createsuperuser
echo "Done"

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env bash
docker-compose run web_recipes python3 manage.py migrate
docker-compose run web_recipes python3 manage.py collectstatic --noinput