mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-25 19:29:30 -05:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f08815482 | ||
|
|
6c0a81a4c5 | ||
|
|
9179bde8f9 | ||
|
|
0684c47e2a | ||
|
|
be52238413 | ||
|
|
a0332c432d | ||
|
|
35b038a33c | ||
|
|
9ac3d79b95 | ||
|
|
6f24b7d34e | ||
|
|
e84d15a7ed | ||
|
|
8f268b3b75 | ||
|
|
f9cb44c66b | ||
|
|
d09c6dbfed | ||
|
|
67d7cd1d23 | ||
|
|
bf3337b5e1 | ||
|
|
0b0d214085 | ||
|
|
60dc008b05 | ||
|
|
475b6e3728 | ||
|
|
0e70cd83e2 | ||
|
|
27297d170a | ||
|
|
bc8f8b8138 | ||
|
|
87ba53fde9 | ||
|
|
098dda28a4 | ||
|
|
8ffe6abb5c | ||
|
|
5706776002 | ||
|
|
fbe528e935 | ||
|
|
0df86b940f | ||
|
|
81c3707090 | ||
|
|
9fd691b9f0 | ||
|
|
821136787d | ||
|
|
6aedba09f3 | ||
|
|
7b17a1acfa | ||
|
|
9dd538519f | ||
|
|
b8821f1f72 | ||
|
|
3420dcd07d | ||
|
|
445c01bddc | ||
|
|
dd5996084d | ||
|
|
dfb1d80ca0 | ||
|
|
744fbc7a46 | ||
|
|
cd11cc58cf | ||
|
|
569e385915 | ||
|
|
abf552cd18 | ||
|
|
c6959488dc | ||
|
|
85e3155b50 | ||
|
|
f6aa50bbfc | ||
|
|
5ad27c015e | ||
|
|
4a68a99907 | ||
|
|
123dc1a74d | ||
|
|
2e23fcfd5d | ||
|
|
edbc21df19 |
@@ -1,10 +1,21 @@
|
||||
DEBUG=1
|
||||
# only set this to true when testing/debugging
|
||||
DEBUG=0
|
||||
|
||||
# hosts the application can run under e.g. recipes.mydomain.com,cooking.mydomain.com,...
|
||||
ALLOWED_HOSTS=*
|
||||
|
||||
# random secret key, use for example base64 /dev/urandom | head -c50 to generate one
|
||||
SECRET_KEY=
|
||||
|
||||
# add only a database password if you want to run with the default postgres, otherwise change settings accordingly
|
||||
DB_ENGINE=django.db.backends.postgresql_psycopg2
|
||||
POSTGRES_HOST=db_recipes
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USER=djangodb
|
||||
POSTGRES_PASSWORD=
|
||||
POSTGRES_DB=djangodb
|
||||
POSTGRES_DB=djangodb
|
||||
|
||||
# Serve mediafiles directly using gunicorn. Basically everyone recommends not doing this. Please use any of the examples
|
||||
# provided that include an additional nxginx container to handle media file serving.
|
||||
# If you know what you are doing turn this back on (1) to serve media files using djangos serve() method.
|
||||
GUNICORN_MEDIA=0
|
||||
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
5
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
8
.idea/dictionaries/vabene1111_PC.xml
generated
Normal file
8
.idea/dictionaries/vabene1111_PC.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="vabene1111-PC">
|
||||
<words>
|
||||
<w>gunicorn</w>
|
||||
<w>traefik</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
14
.idea/inspectionProfiles/Project_Default.xml
generated
14
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,14 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredPackages">
|
||||
<value>
|
||||
<list size="1">
|
||||
<item index="0" class="java.lang.String" itemvalue="psycopg2" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
1
.idea/inspectionProfiles/profiles_settings.xml
generated
1
.idea/inspectionProfiles/profiles_settings.xml
generated
@@ -1,6 +1,5 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="Default" />
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
|
||||
6
.idea/jsLibraryMappings.xml
generated
6
.idea/jsLibraryMappings.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<file url="file://$PROJECT_DIR$" libraries="{jquery-3.4.1, pdf, pdf_viewer, pretty-checkbox}" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -32,9 +32,11 @@ The docker image (`vabene1111/recipes`) simply exposes the application on port `
|
||||
2. Choose one of the included configurations [here](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`.
|
||||
4. Open the page to create the first user. Alternatively use `docker-compose exec web_recipes createsuperuser`
|
||||
|
||||
### Manual
|
||||
**Python >= 3.8** is required to run this!
|
||||
|
||||
Copy `.env.template` to `.env` and fill in the missing values accordingly.
|
||||
Make sure all variables are available to whatever serves your application.
|
||||
|
||||
@@ -57,5 +59,10 @@ You can find a basic kubernetes setup [here](docs/k8s/). Please see the README i
|
||||
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/).
|
||||
|
||||
### Translating
|
||||
There is a [transifex project](https://www.transifex.com/django-recipes/django-cookbook/) project to enable community driven translations. If you want to contribute a new language or help maintain an already existing one feel free to create a transifex account (using the link above) and request to join the project.
|
||||
|
||||
It is also possible to provide the translations directly by creating a new language using `manage.py makemessages -l <language_code> -i venv`. Once finished simply open a PR with the changed files.
|
||||
|
||||
## License
|
||||
This project is licensed under the MIT license. Even though it is not required to publish derivatives, I highly encourage pushing changes upstream and letting people profit from any work done on this project.
|
||||
|
||||
2
boot.sh
2
boot.sh
@@ -6,4 +6,6 @@ python manage.py migrate
|
||||
python manage.py collectstatic --noinput
|
||||
echo "Done"
|
||||
|
||||
chmod -R 755 /opt/recipes/mediafiles
|
||||
|
||||
exec gunicorn -b :8080 --access-logfile - --error-logfile - recipes.wsgi
|
||||
@@ -31,11 +31,17 @@ class UserPreferenceForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = UserPreference
|
||||
fields = ('default_unit', 'theme', 'nav_color', 'default_page', 'search_style')
|
||||
fields = ('default_unit', 'theme', 'nav_color', 'default_page', 'show_recent', 'search_style', 'plan_share')
|
||||
|
||||
help_texts = {
|
||||
'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!'),
|
||||
'default_unit': _('Default Unit to be used when inserting a new ingredient into a recipe.')
|
||||
'default_unit': _('Default Unit to be used when inserting a new ingredient into a recipe.'),
|
||||
'plan_share': _('Default user to share newly created meal plan entries with.'),
|
||||
'show_recent': _('Show recently viewed recipes on search page.'),
|
||||
}
|
||||
|
||||
widgets = {
|
||||
'plan_share': MultiSelectWidget
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +93,7 @@ class InternalRecipeForm(forms.ModelForm):
|
||||
}
|
||||
widgets = {'keywords': MultiSelectWidget}
|
||||
help_texts = {
|
||||
'instructions': _('You can use markdown to format the instructions. See the <a href="/docs/markdown/">docs here</a>')
|
||||
'instructions': _('You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>')
|
||||
}
|
||||
|
||||
|
||||
@@ -257,6 +263,17 @@ class MealPlanForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = MealPlan
|
||||
fields = ('recipe', 'title', 'meal', 'note', 'date')
|
||||
fields = ('recipe', 'title', 'meal', 'note', 'date', 'shared')
|
||||
|
||||
widgets = {'recipe': SelectWidget, 'date': DateWidget}
|
||||
help_texts = {
|
||||
'shared': _('You can list default users to share recipes with in the settings.'),
|
||||
'note': _('You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>')
|
||||
}
|
||||
|
||||
widgets = {'recipe': SelectWidget, 'date': DateWidget, 'shared': MultiSelectWidget}
|
||||
|
||||
|
||||
class SuperUserForm(forms.Form):
|
||||
name = forms.CharField()
|
||||
password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}))
|
||||
password_confirm = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}))
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
cookbook/locale/en/LC_MESSAGES/django.mo
Normal file
BIN
cookbook/locale/en/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
1105
cookbook/locale/en/LC_MESSAGES/django.po
Normal file
1105
cookbook/locale/en/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
25
cookbook/migrations/0043_auto_20200507_2302.py
Normal file
25
cookbook/migrations/0043_auto_20200507_2302.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 3.0.5 on 2020-05-07 21:02
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('cookbook', '0042_cooklog'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mealplan',
|
||||
name='shared',
|
||||
field=models.ManyToManyField(blank=True, related_name='plan_share', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='userpreference',
|
||||
name='plan_share',
|
||||
field=models.ManyToManyField(blank=True, related_name='plan_share_default', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
25
cookbook/migrations/0044_viewlog.py
Normal file
25
cookbook/migrations/0044_viewlog.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 3.0.5 on 2020-05-11 10:21
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('cookbook', '0043_auto_20200507_2302'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ViewLog',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
|
||||
],
|
||||
),
|
||||
]
|
||||
18
cookbook/migrations/0045_userpreference_show_recent.py
Normal file
18
cookbook/migrations/0045_userpreference_show_recent.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.0.5 on 2020-06-02 08:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0044_viewlog'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userpreference',
|
||||
name='show_recent',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
@@ -61,6 +61,8 @@ class UserPreference(models.Model):
|
||||
default_unit = models.CharField(max_length=32, default='g')
|
||||
default_page = models.CharField(choices=PAGES, max_length=64, default=SEARCH)
|
||||
search_style = models.CharField(choices=SEARCH_STYLE, max_length=64, default=LARGE)
|
||||
show_recent = models.BooleanField(default=True)
|
||||
plan_share = models.ManyToManyField(User, blank=True, related_name='plan_share_default')
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user)
|
||||
@@ -140,10 +142,6 @@ class Recipe(models.Model):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def all_tags(self):
|
||||
return ' '.join([(str(x)) for x in self.keywords.all()])
|
||||
|
||||
|
||||
class Unit(models.Model):
|
||||
name = models.CharField(unique=True, max_length=128)
|
||||
@@ -223,6 +221,7 @@ class MealPlan(models.Model):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, blank=True, null=True)
|
||||
title = models.CharField(max_length=64, blank=True, default='')
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
shared = models.ManyToManyField(User, blank=True, related_name='plan_share')
|
||||
meal = models.CharField(choices=MEAL_TYPES, max_length=128, default=BREAKFAST)
|
||||
note = models.TextField(blank=True)
|
||||
date = models.DateField()
|
||||
@@ -232,6 +231,10 @@ class MealPlan(models.Model):
|
||||
return self.title
|
||||
return str(self.recipe)
|
||||
|
||||
def get_meal_name(self):
|
||||
meals = dict(self.MEAL_TYPES)
|
||||
return meals.get(self.meal)
|
||||
|
||||
|
||||
class CookLog(models.Model):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
@@ -242,3 +245,12 @@ class CookLog(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return self.recipe.name
|
||||
|
||||
|
||||
class ViewLog(models.Model):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.recipe.name
|
||||
|
||||
12
cookbook/static/css/pretty-checkbox.min.css
vendored
Normal file
12
cookbook/static/css/pretty-checkbox.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
721
cookbook/static/css/select2-bootstrap.css
Normal file
721
cookbook/static/css/select2-bootstrap.css
Normal file
@@ -0,0 +1,721 @@
|
||||
/*!
|
||||
* Select2 Bootstrap Theme v0.1.0-beta.10 (https://select2.github.io/select2-bootstrap-theme)
|
||||
* Copyright 2015-2017 Florian Kissling and contributors (https://github.com/select2/select2-bootstrap-theme/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/select2/select2-bootstrap-theme/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
.select2-container--bootstrap {
|
||||
display: block;
|
||||
/*------------------------------------* #COMMON STYLES
|
||||
\*------------------------------------*/
|
||||
/**
|
||||
* Search field in the Select2 dropdown.
|
||||
*/
|
||||
/**
|
||||
* No outline for all search fields - in the dropdown
|
||||
* and inline in multi Select2s.
|
||||
*/
|
||||
/**
|
||||
* Adjust Select2's choices hover and selected styles to match
|
||||
* Bootstrap 3's default dropdown styles.
|
||||
*
|
||||
* @see http://getbootstrap.com/components/#dropdowns
|
||||
*/
|
||||
/**
|
||||
* Clear the selection.
|
||||
*/
|
||||
/**
|
||||
* Address disabled Select2 styles.
|
||||
*
|
||||
* @see https://select2.github.io/examples.html#disabled
|
||||
* @see http://getbootstrap.com/css/#forms-control-disabled
|
||||
*/
|
||||
/*------------------------------------* #DROPDOWN
|
||||
\*------------------------------------*/
|
||||
/**
|
||||
* Dropdown border color and box-shadow.
|
||||
*/
|
||||
/**
|
||||
* Limit the dropdown height.
|
||||
*/
|
||||
/*------------------------------------* #SINGLE SELECT2
|
||||
\*------------------------------------*/
|
||||
/*------------------------------------* #MULTIPLE SELECT2
|
||||
\*------------------------------------*/
|
||||
/**
|
||||
* Address Bootstrap control sizing classes
|
||||
*
|
||||
* 1. Reset Bootstrap defaults.
|
||||
* 2. Adjust the dropdown arrow button icon position.
|
||||
*
|
||||
* @see http://getbootstrap.com/css/#forms-control-sizes
|
||||
*/
|
||||
/* 1 */
|
||||
/*------------------------------------* #RTL SUPPORT
|
||||
\*------------------------------------*/
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection {
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
color: #555555;
|
||||
font-size: 14px;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection.form-control {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-search--dropdown .select2-search__field {
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
color: #555555;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-search__field {
|
||||
outline: 0;
|
||||
/* Firefox 18- */
|
||||
/**
|
||||
* Firefox 19+
|
||||
*
|
||||
* @see http://stackoverflow.com/questions/24236240/color-for-styled-placeholder-text-is-muted-in-firefox
|
||||
*/
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-search__field::-webkit-input-placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-search__field:-moz-placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-search__field::-moz-placeholder {
|
||||
color: #999;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-search__field:-ms-input-placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option {
|
||||
padding: 6px 12px;
|
||||
/**
|
||||
* Disabled results.
|
||||
*
|
||||
* @see https://select2.github.io/examples.html#disabled-results
|
||||
*/
|
||||
/**
|
||||
* Hover state.
|
||||
*/
|
||||
/**
|
||||
* Selected state.
|
||||
*/
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option[role=group] {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option[aria-disabled=true] {
|
||||
color: #777777;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option[aria-selected=true] {
|
||||
background-color: #f5f5f5;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option--highlighted[aria-selected] {
|
||||
background-color: #337ab7;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option .select2-results__option {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__group {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -12px;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -24px;
|
||||
padding-left: 36px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -36px;
|
||||
padding-left: 48px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -48px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||
margin-left: -60px;
|
||||
padding-left: 72px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results__group {
|
||||
color: #777777;
|
||||
display: block;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
line-height: 1.42857143;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap.select2-container--focus .select2-selection, .select2-container--bootstrap.select2-container--open .select2-selection {
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
|
||||
-webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||
-o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||
-webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s;
|
||||
transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s;
|
||||
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s;
|
||||
border-color: #66afe9;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap.select2-container--open {
|
||||
/**
|
||||
* Make the dropdown arrow point up while the dropdown is visible.
|
||||
*/
|
||||
/**
|
||||
* Handle border radii of the container when the dropdown is showing.
|
||||
*/
|
||||
}
|
||||
|
||||
.select2-container--bootstrap.select2-container--open .select2-selection .select2-selection__arrow b {
|
||||
border-color: transparent transparent #999 transparent;
|
||||
border-width: 0 4px 4px 4px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap.select2-container--open.select2-container--below .select2-selection {
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap.select2-container--open.select2-container--above .select2-selection {
|
||||
border-top-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top-color: transparent;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection__clear {
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection__clear:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap.select2-container--disabled .select2-selection {
|
||||
border-color: #ccc;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap.select2-container--disabled .select2-selection,
|
||||
.select2-container--bootstrap.select2-container--disabled .select2-search__field {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap.select2-container--disabled .select2-selection,
|
||||
.select2-container--bootstrap.select2-container--disabled .select2-selection--multiple .select2-selection__choice {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap.select2-container--disabled .select2-selection__clear,
|
||||
.select2-container--bootstrap.select2-container--disabled .select2-selection--multiple .select2-selection__choice__remove {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-dropdown {
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
border-color: #66afe9;
|
||||
overflow-x: hidden;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-dropdown--above {
|
||||
-webkit-box-shadow: 0px -6px 12px rgba(0, 0, 0, 0.175);
|
||||
box-shadow: 0px -6px 12px rgba(0, 0, 0, 0.175);
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-results > .select2-results__options {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--single {
|
||||
height: 34px;
|
||||
line-height: 1.42857143;
|
||||
padding: 6px 24px 6px 12px;
|
||||
/**
|
||||
* Adjust the single Select2's dropdown arrow button appearance.
|
||||
*/
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--single .select2-selection__arrow {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 12px;
|
||||
top: 0;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--single .select2-selection__arrow b {
|
||||
border-color: #999 transparent transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 4px 4px 0 4px;
|
||||
height: 0;
|
||||
left: 0;
|
||||
margin-left: -4px;
|
||||
margin-top: -2px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--single .select2-selection__rendered {
|
||||
color: #555555;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--single .select2-selection__placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple {
|
||||
min-height: 34px;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
/**
|
||||
* Make Multi Select2's choices match Bootstrap 3's default button styles.
|
||||
*/
|
||||
/**
|
||||
* Minus 2px borders.
|
||||
*/
|
||||
/**
|
||||
* Clear the selection.
|
||||
*/
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple .select2-selection__rendered {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
line-height: 1.42857143;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple .select2-selection__placeholder {
|
||||
color: #999;
|
||||
float: left;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice {
|
||||
color: #555555;
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
cursor: default;
|
||||
float: left;
|
||||
margin: 5px 0 0 6px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field {
|
||||
background: transparent;
|
||||
padding: 0 12px;
|
||||
height: 32px;
|
||||
line-height: 1.42857143;
|
||||
margin-top: 0;
|
||||
min-width: 5em;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove {
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple .select2-selection__clear {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--single.input-sm,
|
||||
.input-group-sm .select2-container--bootstrap .select2-selection--single,
|
||||
.form-group-sm .select2-container--bootstrap .select2-selection--single {
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
height: 30px;
|
||||
line-height: 1.5;
|
||||
padding: 5px 22px 5px 10px;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--single.input-sm .select2-selection__arrow b,
|
||||
.input-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b,
|
||||
.form-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b {
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple.input-sm,
|
||||
.input-group-sm .select2-container--bootstrap .select2-selection--multiple,
|
||||
.form-group-sm .select2-container--bootstrap .select2-selection--multiple {
|
||||
min-height: 30px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-selection__choice,
|
||||
.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice,
|
||||
.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
margin: 4px 0 0 5px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-search--inline .select2-search__field,
|
||||
.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field,
|
||||
.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field {
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
height: 28px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-selection__clear,
|
||||
.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear,
|
||||
.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--single.input-lg,
|
||||
.input-group-lg .select2-container--bootstrap .select2-selection--single,
|
||||
.form-group-lg .select2-container--bootstrap .select2-selection--single {
|
||||
border-radius: 6px;
|
||||
font-size: 18px;
|
||||
height: 46px;
|
||||
line-height: 1.3333333;
|
||||
padding: 10px 31px 10px 16px;
|
||||
/* 1 */
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--single.input-lg .select2-selection__arrow,
|
||||
.input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow,
|
||||
.form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--single.input-lg .select2-selection__arrow b,
|
||||
.input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b,
|
||||
.form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b {
|
||||
border-width: 5px 5px 0 5px;
|
||||
margin-left: -5px;
|
||||
margin-left: -10px;
|
||||
margin-top: -2.5px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple.input-lg,
|
||||
.input-group-lg .select2-container--bootstrap .select2-selection--multiple,
|
||||
.form-group-lg .select2-container--bootstrap .select2-selection--multiple {
|
||||
min-height: 46px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-selection__choice,
|
||||
.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice,
|
||||
.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice {
|
||||
font-size: 18px;
|
||||
line-height: 1.3333333;
|
||||
border-radius: 4px;
|
||||
margin: 9px 0 0 8px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-search--inline .select2-search__field,
|
||||
.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field,
|
||||
.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field {
|
||||
padding: 0 16px;
|
||||
font-size: 18px;
|
||||
height: 44px;
|
||||
line-height: 1.3333333;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-selection__clear,
|
||||
.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear,
|
||||
.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection.input-lg.select2-container--open .select2-selection--single {
|
||||
/**
|
||||
* Make the dropdown arrow point up while the dropdown is visible.
|
||||
*/
|
||||
}
|
||||
|
||||
.select2-container--bootstrap .select2-selection.input-lg.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
||||
border-color: transparent transparent #999 transparent;
|
||||
border-width: 0 5px 5px 5px;
|
||||
}
|
||||
|
||||
.input-group-lg .select2-container--bootstrap .select2-selection.select2-container--open .select2-selection--single {
|
||||
/**
|
||||
* Make the dropdown arrow point up while the dropdown is visible.
|
||||
*/
|
||||
}
|
||||
|
||||
.input-group-lg .select2-container--bootstrap .select2-selection.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
||||
border-color: transparent transparent #999 transparent;
|
||||
border-width: 0 5px 5px 5px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap[dir="rtl"] {
|
||||
/**
|
||||
* Single Select2
|
||||
*
|
||||
* 1. Makes sure that .select2-selection__placeholder is positioned
|
||||
* correctly.
|
||||
*/
|
||||
/**
|
||||
* Multiple Select2
|
||||
*/
|
||||
}
|
||||
|
||||
.select2-container--bootstrap[dir="rtl"] .select2-selection--single {
|
||||
padding-left: 24px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__rendered {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
text-align: right;
|
||||
/* 1 */
|
||||
}
|
||||
|
||||
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__clear {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__arrow {
|
||||
left: 12px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__arrow b {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice,
|
||||
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,
|
||||
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-search--inline {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
||||
margin-left: 0;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
|
||||
margin-left: 2px;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/*------------------------------------* #ADDITIONAL GOODIES
|
||||
\*------------------------------------*/
|
||||
/**
|
||||
* Address Bootstrap's validation states
|
||||
*
|
||||
* If a Select2 widget parent has one of Bootstrap's validation state modifier
|
||||
* classes, adjust Select2's border colors and focus states accordingly.
|
||||
* You may apply said classes to the Select2 dropdown (body > .select2-container)
|
||||
* via JavaScript match Bootstraps' to make its styles match.
|
||||
*
|
||||
* @see http://getbootstrap.com/css/#forms-control-validation
|
||||
*/
|
||||
.has-warning .select2-dropdown,
|
||||
.has-warning .select2-selection {
|
||||
border-color: #8a6d3b;
|
||||
}
|
||||
|
||||
.has-warning .select2-container--focus .select2-selection,
|
||||
.has-warning .select2-container--open .select2-selection {
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
|
||||
border-color: #66512c;
|
||||
}
|
||||
|
||||
.has-warning.select2-drop-active {
|
||||
border-color: #66512c;
|
||||
}
|
||||
|
||||
.has-warning.select2-drop-active.select2-drop.select2-drop-above {
|
||||
border-top-color: #66512c;
|
||||
}
|
||||
|
||||
.has-error .select2-dropdown,
|
||||
.has-error .select2-selection {
|
||||
border-color: #a94442;
|
||||
}
|
||||
|
||||
.has-error .select2-container--focus .select2-selection,
|
||||
.has-error .select2-container--open .select2-selection {
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
|
||||
border-color: #843534;
|
||||
}
|
||||
|
||||
.has-error.select2-drop-active {
|
||||
border-color: #843534;
|
||||
}
|
||||
|
||||
.has-error.select2-drop-active.select2-drop.select2-drop-above {
|
||||
border-top-color: #843534;
|
||||
}
|
||||
|
||||
.has-success .select2-dropdown,
|
||||
.has-success .select2-selection {
|
||||
border-color: #3c763d;
|
||||
}
|
||||
|
||||
.has-success .select2-container--focus .select2-selection,
|
||||
.has-success .select2-container--open .select2-selection {
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
|
||||
border-color: #2b542c;
|
||||
}
|
||||
|
||||
.has-success.select2-drop-active {
|
||||
border-color: #2b542c;
|
||||
}
|
||||
|
||||
.has-success.select2-drop-active.select2-drop.select2-drop-above {
|
||||
border-top-color: #2b542c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select2 widgets in Bootstrap Input Groups
|
||||
*
|
||||
* @see http://getbootstrap.com/components/#input-groups
|
||||
* @see https://github.com/twbs/bootstrap/blob/master/less/input-groups.less
|
||||
*/
|
||||
/**
|
||||
* Reset rounded corners
|
||||
*/
|
||||
.input-group > .select2-hidden-accessible:first-child + .select2-container--bootstrap > .selection > .select2-selection,
|
||||
.input-group > .select2-hidden-accessible:first-child + .select2-container--bootstrap > .selection > .select2-selection.form-control {
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.input-group > .select2-hidden-accessible:not(:first-child) + .select2-container--bootstrap:not(:last-child) > .selection > .select2-selection,
|
||||
.input-group > .select2-hidden-accessible:not(:first-child) + .select2-container--bootstrap:not(:last-child) > .selection > .select2-selection.form-control {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.input-group > .select2-hidden-accessible:not(:first-child):not(:last-child) + .select2-container--bootstrap:last-child > .selection > .select2-selection,
|
||||
.input-group > .select2-hidden-accessible:not(:first-child):not(:last-child) + .select2-container--bootstrap:last-child > .selection > .select2-selection.form-control {
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
|
||||
.input-group > .select2-container--bootstrap {
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
/**
|
||||
* Adjust z-index like Bootstrap does to show the focus-box-shadow
|
||||
* above appended buttons in .input-group and .form-group.
|
||||
*/
|
||||
/**
|
||||
* Adjust alignment of Bootstrap buttons in Bootstrap Input Groups to address
|
||||
* Multi Select2's height which - depending on how many elements have been selected -
|
||||
* may grow taller than its initial size.
|
||||
*
|
||||
* @see http://getbootstrap.com/components/#input-groups
|
||||
*/
|
||||
}
|
||||
|
||||
.input-group > .select2-container--bootstrap > .selection > .select2-selection.form-control {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.input-group > .select2-container--bootstrap.select2-container--open, .input-group > .select2-container--bootstrap.select2-container--focus {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.input-group > .select2-container--bootstrap,
|
||||
.input-group > .select2-container--bootstrap .input-group-btn,
|
||||
.input-group > .select2-container--bootstrap .input-group-btn .btn {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary fix for https://github.com/select2/select2-bootstrap-theme/issues/9
|
||||
*
|
||||
* Provides `!important` for certain properties of the class applied to the
|
||||
* original `<select>` element to hide it.
|
||||
*
|
||||
* @see https://github.com/select2/select2/pull/3301
|
||||
* @see https://github.com/fk/select2/commit/31830c7b32cb3d8e1b12d5b434dee40a6e753ada
|
||||
*/
|
||||
.form-control.select2-hidden-accessible {
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display override for inline forms
|
||||
*/
|
||||
@media (min-width: 768px) {
|
||||
.form-inline .select2-container--bootstrap {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
1
cookbook/static/css/select2.min.css
vendored
Normal file
1
cookbook/static/css/select2.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
21
cookbook/static/custom/css/markdown_blockquote.css
Normal file
21
cookbook/static/custom/css/markdown_blockquote.css
Normal file
@@ -0,0 +1,21 @@
|
||||
/* css classes needed to render markdown blockquotes */
|
||||
blockquote {
|
||||
background: #f9f9f9;
|
||||
border-left: 4px solid #ccc;
|
||||
margin: 1.5em 10px;
|
||||
padding: .5em 10px;
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before {
|
||||
color: #ccc;
|
||||
content: open-quote;
|
||||
font-size: 4em;
|
||||
line-height: .1em;
|
||||
margin-right: .25em;
|
||||
vertical-align: -.4em;
|
||||
}
|
||||
|
||||
blockquote p {
|
||||
display: inline;
|
||||
}
|
||||
7
cookbook/static/js/bootstrap.min.js
vendored
Normal file
7
cookbook/static/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cookbook/static/js/bootstrap.min.js.map
Normal file
1
cookbook/static/js/bootstrap.min.js.map
Normal file
File diff suppressed because one or more lines are too long
2
cookbook/static/js/jquery-3.5.1.min.js
vendored
Normal file
2
cookbook/static/js/jquery-3.5.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cookbook/static/js/pdf.min.js
vendored
Normal file
1
cookbook/static/js/pdf.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7677
cookbook/static/js/pdf_viewer.js
Normal file
7677
cookbook/static/js/pdf_viewer.js
Normal file
File diff suppressed because it is too large
Load Diff
5
cookbook/static/js/popper.min.js
vendored
Normal file
5
cookbook/static/js/popper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
cookbook/static/js/popper.min.js.map
Normal file
1
cookbook/static/js/popper.min.js.map
Normal file
File diff suppressed because one or more lines are too long
2
cookbook/static/js/select2.min.js
vendored
Normal file
2
cookbook/static/js/select2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -106,3 +106,21 @@ class RecipeImportTable(tables.Table):
|
||||
model = RecipeImport
|
||||
template_name = 'generic/table_template.html'
|
||||
fields = ('id', 'name', 'file_path')
|
||||
|
||||
|
||||
class ViewLogTable(tables.Table):
|
||||
recipe = tables.LinkColumn('view_recipe', args=[A('recipe_id')])
|
||||
|
||||
class Meta:
|
||||
model = ViewLog
|
||||
template_name = 'generic/table_template.html'
|
||||
fields = ('recipe', 'created_at')
|
||||
|
||||
|
||||
class CookLogTable(tables.Table):
|
||||
recipe = tables.LinkColumn('view_recipe', args=[A('recipe_id')])
|
||||
|
||||
class Meta:
|
||||
model = CookLog
|
||||
template_name = 'generic/table_template.html'
|
||||
fields = ('recipe', 'rating', 'serving', 'created_at')
|
||||
|
||||
@@ -20,29 +20,19 @@
|
||||
|
||||
<!-- Bootstrap 4 -->
|
||||
<link id="id_main_css" href="{% theme_url request %}" rel="stylesheet">
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.js"
|
||||
integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="{% static 'js/jquery-3.5.1.min.js' %}"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
|
||||
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
|
||||
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="{% static 'js/popper.min.js' %}"></script>
|
||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||
|
||||
<!-- Select2 for use with django autocomplete light -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/css/select2.min.css" rel="stylesheet"/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.min.js"></script>
|
||||
<link href="{% static 'css/select2.min.css' %}" rel="stylesheet"/>
|
||||
<script src="{% static 'js/select2.min.js' %}"></script>
|
||||
|
||||
<!-- Bootstrap theme for select2 -->
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/select2-bootstrap-theme/0.1.0-beta.10/select2-bootstrap.css"
|
||||
integrity="sha256-zFnNbsU+u3l0K+MaY92RvJI6AdAVAxK3/QrBApHvlH8=" crossorigin="anonymous"/>
|
||||
<link rel="stylesheet" href="{% static 'css/select2-bootstrap.css' %}"/>
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="{% static 'themes/select2-bootstrap-theme.css' %}"
|
||||
crossorigin="anonymous"/>
|
||||
<link rel="stylesheet" href="{% static 'themes/select2-bootstrap-theme.css' %}"/>
|
||||
|
||||
<script type="text/javascript">
|
||||
$.fn.select2.defaults.set("theme", "bootstrap");
|
||||
@@ -70,7 +60,7 @@
|
||||
class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'view_books,view_plan,view_shopping,list_ingredient' %}active{% endif %}">
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'view_books,view_plan,view_shopping,list_ingredient,view_plan_entry' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-mortar-pestle"></i> {% trans 'Utensils' %}
|
||||
@@ -128,7 +118,7 @@
|
||||
|
||||
<ul class="navbar-nav ml-auto">
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'view_settings' %}active{% endif %}">
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'view_settings,view_history' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"><i
|
||||
class="fas fa-user-alt"></i> {{ user.get_user_name }}
|
||||
@@ -137,11 +127,18 @@
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'view_settings' %}"><i
|
||||
class="fas fa-user-cog fa-fw"></i> {% trans 'Settings' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'view_history' %}"><i
|
||||
class="fas fa-history"></i> {% trans 'History' %}</a>
|
||||
{% if user.is_superuser %}
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}"><i
|
||||
class="fas fa-user-shield fa-fw"></i> {% trans 'Admin' %}</a>
|
||||
{% endif %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'docs_markdown' %}"><i
|
||||
class="fab fa-markdown fa-fw"></i> {% trans 'Markdown Help' %}</a>
|
||||
<a class="dropdown-item" href="https://github.com/vabene1111/recipes"><i
|
||||
class="fab fa-github fa-fw"></i> {% trans 'GitHub' %}</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'logout' %}"><i
|
||||
class="fas fa-sign-out-alt fa-fw"></i> {% trans 'Logout' %}</a>
|
||||
</div>
|
||||
|
||||
37
cookbook/templates/history.html
Normal file
37
cookbook/templates/history.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
{% load django_tables2 %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "History" %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{% trans 'History' %}</h3>
|
||||
<br/>
|
||||
|
||||
<ul class="nav nav-tabs" id="id_tab_nav" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="home-tab" data-toggle="tab" href="#view_log" role="tab" aria-controls="view_log"
|
||||
aria-selected="true">{% trans 'View Log' %}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="profile-tab" data-toggle="tab" href="#cook_log" role="tab" aria-controls="cook_log"
|
||||
aria-selected="false">{% trans 'Cook Log' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="id_tab_content">
|
||||
<div class="tab-pane fade show active" id="view_log" role="tabpanel" aria-labelledby="view-log">
|
||||
{% render_table view_log %}
|
||||
</div>
|
||||
<div class="tab-pane fade" id="cook_log" role="tabpanel" aria-labelledby="profile-tab">
|
||||
{% render_table cook_log %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -77,6 +77,12 @@
|
||||
{% endif %}
|
||||
<br/>
|
||||
|
||||
{% if last_viewed %}
|
||||
<h4>{% trans 'Last viewed' %}</h4>
|
||||
{% render_table last_viewed %}
|
||||
<h4>{% trans 'Recipes' %}</h4>
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_authenticated and recipes %}
|
||||
{% render_table recipes %}
|
||||
{% else %}
|
||||
|
||||
@@ -5,32 +5,7 @@
|
||||
{% block title %}{% trans "Markdown Info" %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<style>
|
||||
.code-block {
|
||||
background: #DDDDDD;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
background: #f9f9f9;
|
||||
border-left: 4px solid #ccc;
|
||||
margin: 1.5em 10px;
|
||||
padding: .5em 10px;
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before {
|
||||
color: #ccc;
|
||||
content: open-quote;
|
||||
font-size: 4em;
|
||||
line-height: .1em;
|
||||
margin-right: .25em;
|
||||
vertical-align: -.4em;
|
||||
}
|
||||
|
||||
blockquote p {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="{% static 'custom/css/markdown_blockquote.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -77,13 +77,13 @@
|
||||
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 %}
|
||||
{% if mp.recipe %}
|
||||
<a href="{% url 'edit_meal_plan' mp.pk %}"><i class="fas fa-edit"></i></a>
|
||||
<a href="{% url 'view_recipe' mp.recipe.pk %}">{{ mp.recipe }}</a><br/>
|
||||
{% else %}
|
||||
<a href="{% url 'edit_meal_plan' mp.pk %}">{{ mp.title }}</a><br/>
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url 'view_plan_entry' mp.pk %}">
|
||||
{% if mp.recipe %}
|
||||
{{ mp.recipe }}
|
||||
{% else %}
|
||||
{{ mp.title }}
|
||||
{% endif %}
|
||||
</a><br/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
|
||||
85
cookbook/templates/meal_plan_entry.html
Normal file
85
cookbook/templates/meal_plan_entry.html
Normal file
@@ -0,0 +1,85 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load custom_tags %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Meal Plan View' %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" href="{% static 'custom/css/markdown_blockquote.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-12">
|
||||
<h3>{{ plan.get_meal_name }} {{ plan.date }} <a href="{% url 'edit_meal_plan' plan.pk %}"
|
||||
class="d-print-none"><i class="fas fa-pencil-alt"></i></a>
|
||||
</h3>
|
||||
<small class="text-muted">{% trans 'Created by' %} {{ plan.created_by.get_user_name }}</small>
|
||||
{% if plan.shared.all %}
|
||||
<br/><small class="text-muted">{% trans 'Shared with' %}
|
||||
{% for x in plan.shared.all %}{{ x.get_user_name }}{% if not forloop.last %}, {% endif %} {% endfor %}</small>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
{% if plan.title %}
|
||||
<div class="row">
|
||||
<div class="col col-12">
|
||||
<h4>{{ plan.title }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if plan.recipe %}
|
||||
<div class="row">
|
||||
<div class="col col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% recipe_rating plan.recipe request.user as rating %}
|
||||
<h5 class="card-title"><a
|
||||
href="{% url 'view_recipe' plan.recipe.pk %}">{{ plan.recipe }}</a> {{ rating|safe }}
|
||||
</h5>
|
||||
{% recipe_last plan.recipe request.user as last_cooked %}
|
||||
{% if last_cooked %}
|
||||
{% trans 'Last cooked' %} {{ last_cooked|date }}
|
||||
{% else %}
|
||||
{% trans 'Never cooked before.' %}
|
||||
{% endif %}
|
||||
{% if plan.recipe.keywords %}
|
||||
<br/>
|
||||
<br/>
|
||||
{% for x in plan.recipe.keywords.all %}
|
||||
<span class="badge badge-pill badge-light">{{ x }}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if plan.note %}
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col col-12">
|
||||
{{ plan.note | markdown | safe }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if same_day_plan %}
|
||||
<br/>
|
||||
<h4>{% trans 'Other meals on this day' %}</h4>
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for x in same_day_plan %}
|
||||
<li class="list-group-item"><a href="{% url 'view_plan_entry' x.pk %}">{{ x.get_label }}
|
||||
({{ x.get_meal_name }})</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
@@ -7,11 +8,10 @@
|
||||
{% 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"
|
||||
integrity="sha384-ICB8i/maQ/5+tGLDUEcswB7Ch+OO9Oj8Z4Ov/Gs0gxqfTgLLkD3F43MhcEJ2x6/D" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="{% static 'css/pretty-checkbox.min.css' %}">
|
||||
|
||||
<link rel="stylesheet" href="{% static 'custom/css/markdown_blockquote.css' %}">
|
||||
|
||||
<!-- prevent weired character stuff escaping the pdf box -->
|
||||
<style>
|
||||
/* fixes print layout being disturbed by print button tooltip */
|
||||
@media print {
|
||||
@@ -20,6 +20,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* prevent weired character stuff escaping the pdf box */
|
||||
.textLayer > span {
|
||||
color: transparent;
|
||||
position: absolute;
|
||||
@@ -27,28 +28,6 @@
|
||||
cursor: text;
|
||||
transform-origin: 0% 0%;
|
||||
}
|
||||
|
||||
/* css classes needed to render markdown blockquotes */
|
||||
blockquote {
|
||||
background: #f9f9f9;
|
||||
border-left: 4px solid #ccc;
|
||||
margin: 1.5em 10px;
|
||||
padding: .5em 10px;
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before {
|
||||
color: #ccc;
|
||||
content: open-quote;
|
||||
font-size: 4em;
|
||||
line-height: .1em;
|
||||
margin-right: .25em;
|
||||
vertical-align: -.4em;
|
||||
}
|
||||
|
||||
blockquote p {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
@@ -282,13 +261,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.3.200/pdf.min.js"
|
||||
integrity="sha256-J4Z8Fhj2MITUakMQatkqOVdtqodUlwHtQ/ey6fSsudE="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="{% static 'js/pdf.min.js' %}"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.3.200/pdf_viewer.js"
|
||||
integrity="sha256-JW7ackRikw8/UM/hHV6vKaZBYc+t2ZQ77sd3LWR8vh8="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="{% static 'js/pdf_viewer.js' %}"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var url = "{% url 'api_get_recipe_file' recipe_id=12345 %}".replace(/12345/, {{ recipe.id }});
|
||||
|
||||
@@ -14,16 +14,18 @@
|
||||
<div class="card" style="margin-top: 2px;">
|
||||
<div class="row no-gutters">
|
||||
<div class="col-md-4">
|
||||
{% if row.cells.image|length > 1 %}
|
||||
<img src=" {{ row.cells.image }}" alt="{% trans 'Recipe Image' %}"
|
||||
class="card-img" style="object-fit: cover;height: 130px">
|
||||
{% else %}
|
||||
<img src="{% static 'recipe_no_image.svg' %}"
|
||||
alt="{% trans 'Recipe Image' %}"
|
||||
class="card-img d-none d-lg-block"
|
||||
style="object-fit: inherit; height: 130px">
|
||||
<a href="{% url 'view_recipe' row.cells.id %}">
|
||||
{% if row.cells.image|length > 1 %}
|
||||
<img src=" {{ row.cells.image }}" alt="{% trans 'Recipe Image' %}"
|
||||
class="card-img" style="object-fit: cover;height: 130px">
|
||||
{% else %}
|
||||
<img src="{% static 'recipe_no_image.svg' %}"
|
||||
alt="{% trans 'Recipe Image' %}"
|
||||
class="card-img d-none d-lg-block"
|
||||
style="object-fit: inherit; height: 130px">
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
@@ -31,8 +33,10 @@
|
||||
{% recipe_rating row.record request.user as rating %}
|
||||
{{ rating|safe }}
|
||||
</h5>
|
||||
<p class="card-text{% if not row.record.all_tags %} d-none d-lg-block{% endif %}">
|
||||
{{ row.cells.all_tags }}
|
||||
<p class="card-text{% if not row.record.keywords %} d-none d-lg-block{% endif %}">
|
||||
{% for x in row.record.keywords.all %}
|
||||
<span class="badge badge-pill badge-light">{{ x }}</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p class="card-text"><small class="text-muted">
|
||||
{% if row.cells.working_time != 0 %}
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
|
||||
{% block title %}{% trans 'Settings' %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{{ preference_form.media }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>
|
||||
|
||||
23
cookbook/templates/setup.html
Normal file
23
cookbook/templates/setup.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Cookbook Setup" %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{% trans 'Setup' %}</h1>
|
||||
<p>{% blocktrans %}To start using this application you must first create a superuser.{% endblocktrans %}</p>
|
||||
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Create Superuser' %}</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<!--// @formatter:off-->
|
||||
<textarea id="id_list" class="form-control" rows="{{ ingredients|length|add:1 }}">{% for i in ingredients %}{% if markdown_format %}- [ ]{% endif %} {{ i.amount.normalize }} {{ i.unit }} {{ i.ingredient.name }} {% endfor %}</textarea>
|
||||
<textarea id="id_list" class="form-control" rows="{{ ingredients|length|add:1 }}">{% for i in ingredients %}{% if markdown_format %}- [ ] {% endif %}{{ i.amount.normalize }} {{ i.unit }} {{ i.ingredient.name }} {% endfor %}</textarea>
|
||||
<!--// @formatter:on-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load theming_tags %}
|
||||
{% load django_tables2 %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}
|
||||
{% endblock %}</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<!-- Bootstrap 4 -->
|
||||
<link id="id_main_css" href="{% static 'themes/bootstrap.min.css' %}" rel="stylesheet">
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.js"
|
||||
integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<style>
|
||||
@media (max-width: 1025px) {
|
||||
.container {
|
||||
width: 95% !important;
|
||||
margin-left: 20px !important;
|
||||
margin-right: 20px !important;
|
||||
max-width: 1200px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-{% nav_color request %}" id="id_main_nav">
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText"
|
||||
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item {% if request.resolver_match.url_name in 'view_search,edit_recipe,edit_internal_recipe,edit_external_recipe,view_recipe' %}active{% endif %}">
|
||||
<a class="nav-link" href="{% url 'view_search' %}"><i
|
||||
class="fas fa-book"></i> {% trans 'Cookbook' %}<span
|
||||
class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'view_books,view_plan,view_shopping,list_ingredient' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-mortar-pestle"></i> {% trans 'Utensils' %}
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'view_books' %}"><i
|
||||
class="fas fa-bookmark fa-fw"></i> {% trans 'Books' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'view_plan' %}"><i
|
||||
class="fas fa-calendar fa-fw"></i> {% trans 'Meal-Plan' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'view_shopping' %}"><i
|
||||
class="fas fa-shopping-cart fa-fw"></i> {% trans 'Shopping' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'list_ingredient' %}"><i
|
||||
class="fas fa-leaf fa-fw"></i> {% trans 'Ingredients' %}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'list_keyword,data_batch_edit' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-tags"></i> {% trans 'Tags' %}
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'list_keyword' %}"><i
|
||||
class="fas fa-tags fa-fw"></i> {% trans 'Keyword' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'data_batch_edit' %}"><i
|
||||
class="fas fa-edit fa-fw"></i> {% trans 'Batch Edit' %}</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'list_storage,data_sync,list_recipe_import,list_sync_log,data_stats,edit_ingredient' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"><i class="fas fa-database"></i> {% trans 'Storage Data' %}
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'list_storage' %}"><i
|
||||
class="fas fa-database fa-fw"></i> {% trans 'Storage Backends' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'data_sync' %}"><i
|
||||
class="fas fa-sync-alt fa-fw"></i> {% trans 'Configure Sync' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'list_recipe_import' %}"><i
|
||||
class="far fa-file-alt fa-fw"></i> {% trans 'Discovered Recipes' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'list_sync_log' %}"><i
|
||||
class="fas fa-history fa-fw"></i> {% trans 'Discovery Log' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'data_stats' %}"><i
|
||||
class="fas fa-chart-line fa-fw"></i> {% trans 'Statistics' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'edit_ingredient' %}"><i
|
||||
class="fas fa-balance-scale fa-fw"></i> {% trans 'Units & Ingredients' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'view_import' %}"><i
|
||||
class="fas fa-file-import"></i> {% trans 'Import Recipe' %}</a>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="navbar-nav ml-auto">
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'view_settings' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"><i
|
||||
class="fas fa-user-alt"></i> {{ user.get_user_name }}
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'view_settings' %}"><i
|
||||
class="fas fa-user-cog fa-fw"></i> {% trans 'Settings' %}</a>
|
||||
{% if user.is_superuser %}
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}"><i
|
||||
class="fas fa-user-shield fa-fw"></i> {% trans 'Admin' %}</a>
|
||||
{% endif %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'logout' %}"><i
|
||||
class="fas fa-sign-out-alt fa-fw"></i> {% trans 'Logout' %}</a>
|
||||
</div>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'login' %}">{% trans 'Login' %} <i class="fas fa-sign-in-alt"></i></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container" style="background: #ffff00">
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<form action="" method="get" id="search_form">
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
<div class="row">
|
||||
<div class="col md-12">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="{% trans 'Search recipe ...' %}"
|
||||
id="{{ filter.form.name.id_for_label }}" name="{{ filter.form.name.name }}"
|
||||
aria-describedby="button-addon4">
|
||||
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="submit"><i class="fas fa-search"></i></button>
|
||||
<button type="button"
|
||||
class="btn btn-light dropdown-toggle dropdown-toggle-split dropdown-toggle-no-arrow"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<button class="dropdown-item" type="button"
|
||||
onclick="location.href='{% url 'new_recipe' %}'"><i
|
||||
class="fas fa-plus-circle fa-fw"></i> {% trans 'New Recipe' %}</button>
|
||||
<button data-toggle="collapse" href="#collapse_adv_search"
|
||||
role="button" class="dropdown-item"
|
||||
aria-expanded="false" type="button"
|
||||
aria-controls="collapse_adv_search"><i
|
||||
class="fas fa-search-plus fa-fw"></i> {% trans 'Advanced Search' %}
|
||||
</button>
|
||||
<button class="dropdown-item" type="button"
|
||||
onclick="window.location = window.location.pathname;"><i
|
||||
class="fas fa-sync fa-fw"></i> {% trans 'Reset Search' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="collapse col-md-12" id="collapse_adv_search">
|
||||
<div style="margin-top: 1vh">
|
||||
{{ filter.form.keywords | as_crispy_field }}
|
||||
</div>
|
||||
<div>
|
||||
{{ filter.form.ingredients | as_crispy_field }}
|
||||
</div>
|
||||
<div>
|
||||
{{ filter.form.internal | as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="table-container">
|
||||
|
||||
<table>
|
||||
|
||||
<div class="card">
|
||||
<div class="row no-gutters">
|
||||
<div class="col-md-4">
|
||||
|
||||
<img src="{% static 'recipe_no_image.svg' %}" class="card-img"
|
||||
alt="{% trans 'Recipe Image' %}" style="height: 100%;">
|
||||
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Test</h5>
|
||||
<p class="card-text">This is a wider card with supporting text below as a
|
||||
natural
|
||||
lead-in to additional content. This content is a little bit longer.</p>
|
||||
<p class="card-text"><small class="text-muted">Last updated 3 mins
|
||||
ago</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,11 +8,14 @@ from cookbook.helper import dal
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.index, name='index'),
|
||||
path('setup/', views.setup, name='view_setup'),
|
||||
path('search/', views.search, name='view_search'),
|
||||
path('books/', views.books, name='view_books'),
|
||||
path('plan/', views.meal_plan, name='view_plan'),
|
||||
path('plan/entry/<int:pk>', views.meal_plan_entry, name='view_plan_entry'),
|
||||
path('shopping/', views.shopping_list, name='view_shopping'),
|
||||
path('settings/', views.settings, name='view_settings'),
|
||||
path('settings/', views.user_settings, name='view_settings'),
|
||||
path('history/', views.history, name='view_history'),
|
||||
|
||||
path('import/', import_export.import_recipe, name='view_import'),
|
||||
path('export/', import_export.export_recipe, name='view_export'),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import uuid
|
||||
from io import BytesIO
|
||||
|
||||
import simplejson
|
||||
@@ -71,7 +72,7 @@ def internal_recipe_update(request, pk):
|
||||
|
||||
im_io = BytesIO()
|
||||
img.save(im_io, 'PNG', quality=70)
|
||||
recipe.image = File(im_io, name=(str(recipe.pk) + '.png'))
|
||||
recipe.image = File(im_io, name=f'{uuid.uuid4()}_{recipe.pk}.png')
|
||||
elif 'image' in form.changed_data and form.cleaned_data['image'] is False:
|
||||
recipe.image = None
|
||||
|
||||
@@ -272,7 +273,7 @@ class MealPlanUpdate(OwnerRequiredMixin, UpdateView):
|
||||
form_class = MealPlanForm
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('view_plan')
|
||||
return reverse('view_plan_entry', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(MealPlanUpdate, self).get_context_data(**kwargs)
|
||||
|
||||
@@ -128,7 +128,8 @@ class MealPlanCreate(GroupRequiredMixin, CreateView):
|
||||
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
|
||||
date=datetime.strptime(self.request.GET['date'], '%Y-%m-%d') if 'date' in self.request.GET else None,
|
||||
shared=self.request.user.userpreference.plan_share.all() if self.request.user.userpreference.plan_share else None
|
||||
)
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
@@ -2,23 +2,29 @@ import copy
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib.auth import update_session_auth_hash, authenticate
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django_tables2 import RequestConfig
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from cookbook.filters import RecipeFilter
|
||||
from cookbook.forms import *
|
||||
from cookbook.helper.permission_helper import group_required
|
||||
from cookbook.tables import RecipeTable, RecipeTableSmall
|
||||
from cookbook.tables import RecipeTable, RecipeTableSmall, CookLogTable, ViewLogTable
|
||||
|
||||
|
||||
def index(request):
|
||||
if not request.user.is_authenticated:
|
||||
if User.objects.count() < 1 and 'django.contrib.auth.backends.RemoteUserBackend' not in settings.AUTHENTICATION_BACKENDS:
|
||||
return HttpResponseRedirect(reverse_lazy('view_setup'))
|
||||
return HttpResponseRedirect(reverse_lazy('view_search'))
|
||||
try:
|
||||
page_map = {
|
||||
@@ -42,7 +48,21 @@ def search(request):
|
||||
table = RecipeTableSmall(f.qs)
|
||||
RequestConfig(request, paginate={'per_page': 25}).configure(table)
|
||||
|
||||
return render(request, 'index.html', {'recipes': table, 'filter': f})
|
||||
if request.GET == {} and request.user.userpreference.show_recent:
|
||||
qs = Recipe.objects.filter(viewlog__created_by=request.user).order_by('-viewlog__created_at').all()
|
||||
|
||||
recent_list = []
|
||||
for r in qs:
|
||||
if r not in recent_list:
|
||||
recent_list.append(r)
|
||||
if len(recent_list) >= 5:
|
||||
break
|
||||
|
||||
last_viewed = RecipeTable(recent_list)
|
||||
else:
|
||||
last_viewed = None
|
||||
|
||||
return render(request, 'index.html', {'recipes': table, 'filter': f, 'last_viewed': last_viewed})
|
||||
else:
|
||||
return render(request, 'index.html')
|
||||
|
||||
@@ -78,6 +98,10 @@ def recipe_view(request, pk):
|
||||
comment_form = CommentForm()
|
||||
bookmark_form = RecipeBookEntryForm()
|
||||
|
||||
if request.user.is_authenticated:
|
||||
if not ViewLog.objects.filter(recipe=recipe).filter(created_by=request.user).filter(created_at__gt=(timezone.now() - timezone.timedelta(minutes=5))).exists():
|
||||
ViewLog.objects.create(recipe=recipe, created_by=request.user)
|
||||
|
||||
return render(request, 'recipe_view.html',
|
||||
{'recipe': recipe, 'ingredients': ingredients, 'comments': comments, 'comment_form': comment_form,
|
||||
'bookmark_form': bookmark_form})
|
||||
@@ -130,13 +154,26 @@ def meal_plan(request):
|
||||
plan[t[0]] = {'type_name': t[1], 'days': copy.deepcopy(days_dict)}
|
||||
|
||||
for d in days:
|
||||
plan_day = MealPlan.objects.filter(date=d).all()
|
||||
plan_day = MealPlan.objects.filter(date=d).filter(Q(created_by=request.user) | Q(shared=request.user)).distinct().all()
|
||||
for p in plan_day:
|
||||
plan[p.meal]['days'][d].append(p)
|
||||
|
||||
return render(request, 'meal_plan.html', {'js_week': js_week, 'plan': plan, 'days': days, 'surrounding_weeks': surrounding_weeks})
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def meal_plan_entry(request, pk):
|
||||
plan = MealPlan.objects.get(pk=pk)
|
||||
|
||||
if plan.created_by != request.user and plan.shared != request.user:
|
||||
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
|
||||
return HttpResponseRedirect(reverse_lazy('index'))
|
||||
|
||||
same_day_plan = MealPlan.objects.filter(date=plan.date).exclude(pk=plan.pk).filter(Q(created_by=request.user) | Q(shared=request.user)).order_by('meal').all()
|
||||
|
||||
return render(request, 'meal_plan_entry.html', {'plan': plan, 'same_day_plan': same_day_plan})
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def shopping_list(request):
|
||||
markdown_format = True
|
||||
@@ -163,7 +200,7 @@ def shopping_list(request):
|
||||
ingredients = []
|
||||
|
||||
for r in recipes:
|
||||
for ri in RecipeIngredient.objects.filter(recipe=r).all():
|
||||
for ri in RecipeIngredient.objects.filter(recipe=r).exclude(unit__name__contains='Special:').all():
|
||||
index = None
|
||||
for x, ig in enumerate(ingredients):
|
||||
if ri.ingredient == ig.ingredient and ri.unit == ig.unit:
|
||||
@@ -178,7 +215,7 @@ def shopping_list(request):
|
||||
|
||||
|
||||
@group_required('guest')
|
||||
def settings(request):
|
||||
def user_settings(request):
|
||||
up = request.user.userpreference
|
||||
|
||||
user_name_form = UserNameForm(instance=request.user)
|
||||
@@ -194,7 +231,9 @@ def settings(request):
|
||||
up.nav_color = form.cleaned_data['nav_color']
|
||||
up.default_unit = form.cleaned_data['default_unit']
|
||||
up.default_page = form.cleaned_data['default_page']
|
||||
up.show_recent = form.cleaned_data['show_recent']
|
||||
up.search_style = form.cleaned_data['search_style']
|
||||
up.plan_share.set(form.cleaned_data['plan_share'])
|
||||
up.save()
|
||||
|
||||
if 'user_name_form' in request.POST:
|
||||
@@ -218,5 +257,42 @@ def settings(request):
|
||||
return render(request, 'settings.html', {'preference_form': preference_form, 'user_name_form': user_name_form, 'password_form': password_form})
|
||||
|
||||
|
||||
@group_required('guest')
|
||||
def history(request):
|
||||
view_log = ViewLogTable(ViewLog.objects.filter(created_by=request.user).order_by('-created_at').all())
|
||||
cook_log = CookLogTable(CookLog.objects.filter(created_by=request.user).order_by('-created_at').all())
|
||||
return render(request, 'history.html', {'view_log': view_log, 'cook_log': cook_log})
|
||||
|
||||
|
||||
def setup(request):
|
||||
if User.objects.count() > 0 or 'django.contrib.auth.backends.RemoteUserBackend' in settings.AUTHENTICATION_BACKENDS:
|
||||
messages.add_message(request, messages.ERROR, _('The setup page can only be used to create the first user! If you have forgotten your superuser credentials please consult the django documentation on how to reset passwords.'))
|
||||
return HttpResponseRedirect(reverse('login'))
|
||||
|
||||
if request.method == 'POST':
|
||||
form = SuperUserForm(request.POST)
|
||||
if form.is_valid():
|
||||
if form.cleaned_data['password'] != form.cleaned_data['password_confirm']:
|
||||
form.add_error('password', _('Passwords dont match!'))
|
||||
else:
|
||||
user = User(
|
||||
username=form.cleaned_data['name'],
|
||||
is_superuser=True
|
||||
)
|
||||
try:
|
||||
validate_password(form.cleaned_data['password'], user=user)
|
||||
user.set_password(form.cleaned_data['password'])
|
||||
user.save()
|
||||
messages.add_message(request, messages.SUCCESS, _('User has been created, please login!'))
|
||||
return HttpResponseRedirect(reverse('login'))
|
||||
except ValidationError as e:
|
||||
for m in e:
|
||||
form.add_error('password', m)
|
||||
else:
|
||||
form = SuperUserForm()
|
||||
|
||||
return render(request, 'setup.html', {'form': form})
|
||||
|
||||
|
||||
def markdown_info(request):
|
||||
return render(request, 'markdown_info.html', {})
|
||||
|
||||
@@ -4,10 +4,6 @@ server {
|
||||
|
||||
client_max_body_size 16M;
|
||||
|
||||
# serve static files
|
||||
location /static/ {
|
||||
alias /static/;
|
||||
}
|
||||
# serve media files
|
||||
location /media/ {
|
||||
alias /media/;
|
||||
|
||||
64
docs/docker/traefik-nginx/README.md
Normal file
64
docs/docker/traefik-nginx/README.md
Normal file
@@ -0,0 +1,64 @@
|
||||
This is the recommended setup to run django recipes with traefik.
|
||||
|
||||
----
|
||||
|
||||
Please refer to the traefik documentation on how to setup a docker service in traefik. Since treafik can be a little
|
||||
confusing at times, the following are examples of my traefik configuration.
|
||||
|
||||
|
||||
You need to create a network called `traefik` using `docker network create traefik`.
|
||||
## docker-compose.yml
|
||||
|
||||
```
|
||||
version: "3.3"
|
||||
|
||||
services:
|
||||
|
||||
traefik:
|
||||
image: "traefik:v2.1"
|
||||
container_name: "traefik"
|
||||
ports:
|
||||
- "443:443"
|
||||
- "80:80"
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- "./letsencrypt:/letsencrypt"
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
- "./config:/etc/traefik/"
|
||||
|
||||
|
||||
networks:
|
||||
default:
|
||||
external:
|
||||
name: traefik
|
||||
```
|
||||
|
||||
## traefik.toml
|
||||
Place this in a directory called `config` as this is mounted into the traefik container (see docer compose).
|
||||
**Change the email address accordingly**.
|
||||
```
|
||||
[api]
|
||||
insecure=true
|
||||
|
||||
[providers.docker]
|
||||
endpoint = "unix:///var/run/docker.sock"
|
||||
exposedByDefault = false
|
||||
network = "traefik"
|
||||
|
||||
#[log]
|
||||
# level = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.web_secure]
|
||||
address = ":443"
|
||||
|
||||
[certificatesResolvers.le_resolver.acme]
|
||||
|
||||
email = "you_email@mail.com"
|
||||
storage = "/letsencrypt/acme.json"
|
||||
|
||||
tlsChallenge=true
|
||||
```
|
||||
46
docs/docker/traefik-nginx/docker-compose.yml
Normal file
46
docs/docker/traefik-nginx/docker-compose.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
version: "3"
|
||||
services:
|
||||
db_recipes:
|
||||
restart: always
|
||||
image: postgres:11-alpine
|
||||
volumes:
|
||||
- ./postgresql:/var/lib/postgresql/data
|
||||
env_file:
|
||||
- ./.env
|
||||
networks:
|
||||
- default
|
||||
|
||||
web_recipes:
|
||||
image: vabene1111/recipes
|
||||
restart: always
|
||||
env_file:
|
||||
- ./.env
|
||||
volumes:
|
||||
- ./staticfiles:/opt/recipes/staticfiles
|
||||
- ./mediafiles:/opt/recipes/mediafiles
|
||||
depends_on:
|
||||
- db_recipes
|
||||
networks:
|
||||
- default
|
||||
|
||||
nginx_recipes:
|
||||
image: nginx:mainline-alpine
|
||||
restart: always
|
||||
env_file:
|
||||
- ./.env
|
||||
volumes:
|
||||
- ./nginx/conf.d:/etc/nginx/conf.d
|
||||
- ./mediafiles:/media
|
||||
labels: # traefik example labels
|
||||
- "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:
|
||||
traefik: # This is you external traefik network
|
||||
external: true
|
||||
16
docs/docker/traefik-nginx/nginx/conf.d/Recipes.conf
Normal file
16
docs/docker/traefik-nginx/nginx/conf.d/Recipes.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
client_max_body_size 16M;
|
||||
|
||||
# serve media files
|
||||
location /media/ {
|
||||
alias /media/;
|
||||
}
|
||||
# pass requests for dynamic content to gunicorn
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://web_recipes:8080;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
# Important Information
|
||||
Although this application allows running without any webserver in front of gunicorn it is heavily recommended by almost
|
||||
everyone **not** to do this. It is hard to find exact explanations and appears not to be a security but only
|
||||
a performance risk but that is just my personal interpretation.
|
||||
|
||||
**If you dont know what you are doing please choose the traefik-nginx config**
|
||||
|
||||
----
|
||||
|
||||
Please refer to the traefik documentation on how to setup a docker service in traefik. Since treafik can be a little
|
||||
confusing at times, the following are examples of my traefik configuration.
|
||||
|
||||
|
||||
Binary file not shown.
BIN
recipes/locale/en/LC_MESSAGES/django.mo
Normal file
BIN
recipes/locale/en/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
26
recipes/locale/en/LC_MESSAGES/django.po
Normal file
26
recipes/locale/en/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,26 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-06-02 21:20+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
#: .\recipes\settings.py:138
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: .\recipes\settings.py:139
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
@@ -21,6 +21,8 @@ SECRET_KEY = os.getenv('SECRET_KEY') if os.getenv('SECRET_KEY') else '728f4t5438
|
||||
|
||||
DEBUG = bool(int(os.getenv('DEBUG', True)))
|
||||
|
||||
GUNICORN_MEDIA = bool(int(os.getenv('GUNICORN_MEDIA', True)))
|
||||
|
||||
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS').split(',') if os.getenv('ALLOWED_HOSTS') else ['*']
|
||||
|
||||
LOGIN_REDIRECT_URL = "index"
|
||||
|
||||
@@ -14,9 +14,11 @@ Including another URLconf
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
from django.conf.urls.static import static
|
||||
from django.urls import include, path
|
||||
from django.contrib import admin
|
||||
from django.views.static import serve
|
||||
|
||||
urlpatterns = [
|
||||
path('', include('cookbook.urls')),
|
||||
@@ -25,5 +27,5 @@ urlpatterns = [
|
||||
path('i18n/', include('django.conf.urls.i18n')),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
if settings.GUNICORN_MEDIA or settings.DEBUG:
|
||||
urlpatterns += url(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
|
||||
|
||||
Reference in New Issue
Block a user