mirror of
https://github.com/wger-project/wger.git
synced 2026-02-18 00:17:51 +01:00
Merge branch 'master' into groups
# Conflicts: # requirements.txt
This commit is contained in:
3
.github/contributing.md
vendored
3
.github/contributing.md
vendored
@@ -15,7 +15,8 @@ in a pull request.
|
||||
## Questions
|
||||
Are you just using the software and have a question or improvement?
|
||||
|
||||
* Ask it on the [gitter channel](https://gitter.im/wger-project/wger)
|
||||
* Ask it on the [gitter channel](https://gitter.im/wger-project/wger),
|
||||
* the [discord server](https://discord.gg/rPWFv6W)
|
||||
* or just [open an issue](https://github.com/wger-project/wger/issues)
|
||||
|
||||
## Issues
|
||||
|
||||
3
.github/linters/.eslintrc.yml
vendored
3
.github/linters/.eslintrc.yml
vendored
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"jquery": true
|
||||
"jquery": true,
|
||||
"es6": true
|
||||
},
|
||||
"globals": {
|
||||
"d3": true,
|
||||
|
||||
10
.github/linters/.python-lint
vendored
10
.github/linters/.python-lint
vendored
@@ -55,7 +55,7 @@ confidence=
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once). You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# disable everything first and then re-enable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use "--disable=all --enable=classes
|
||||
@@ -143,8 +143,8 @@ disable=import-error,
|
||||
comprehension-escape
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# either give multiple identifiers separated by comma (,) or put this option
|
||||
# multiple times (only on the command line, not in the configuration file where
|
||||
# it should appear only once). See also the "--disable" option for examples.
|
||||
enable=c-extension-no-member
|
||||
|
||||
@@ -180,7 +180,7 @@ score=yes
|
||||
max-nested-blocks=5
|
||||
|
||||
# Complete name of functions that never returns. When checking for
|
||||
# inconsistent-return-statements if a never returning function is called then
|
||||
# inconsistent-return-statements, if a never returning function is called then
|
||||
# it will be considered as an explicit return statement and no message will be
|
||||
# printed.
|
||||
never-returning-functions=sys.exit
|
||||
@@ -504,7 +504,7 @@ max-attributes=7
|
||||
# Maximum number of boolean expressions in an if statement (see R0916).
|
||||
max-bool-expr=5
|
||||
|
||||
# Maximum number of branch for function / method body.
|
||||
# Maximum number of branches for function / method body.
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of locals for function / method body.
|
||||
|
||||
66
.github/workflows/docker.yml
vendored
66
.github/workflows/docker.yml
vendored
@@ -6,38 +6,44 @@ on:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
path-context:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build base image
|
||||
uses: docker/build-push-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
repository: wger/base
|
||||
dockerfile: extras/docker/base/Dockerfile
|
||||
tags: latest,2.0-dev
|
||||
tag_with_ref: true
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Build dev image
|
||||
uses: docker/build-push-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
repository: wger/devel
|
||||
dockerfile: extras/docker/development/Dockerfile
|
||||
tags: latest,2.0-dev
|
||||
tag_with_ref: true
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Build apache image
|
||||
uses: docker/build-push-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
repository: wger/apache
|
||||
dockerfile: extras/docker/apache/Dockerfile
|
||||
tags: latest,2.0-dev
|
||||
tag_with_ref: true
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: extras/docker/base/Dockerfile
|
||||
push: true
|
||||
tags: wger/base:latest,wger/base:2.0-dev
|
||||
|
||||
- name: Build apache image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: extras/docker/apache/Dockerfile
|
||||
push: true
|
||||
tags: wger/apache:latest,wger/apache:2.0-dev
|
||||
|
||||
- name: Build dev image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: extras/docker/development/Dockerfile
|
||||
push: true
|
||||
tags: wger/devel:latest,wger/devel:2.0-dev
|
||||
|
||||
3
.github/workflows/linter.yml
vendored
3
.github/workflows/linter.yml
vendored
@@ -46,8 +46,9 @@ jobs:
|
||||
# Run Linter against code base #
|
||||
################################
|
||||
- name: Lint Code Base
|
||||
uses: docker://github/super-linter:v3.5.1
|
||||
uses: docker://github/super-linter:v3.9.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VALIDATE_PYTHON_FLAKE8: true
|
||||
VALIDATE_MD: true
|
||||
VALIDATE_JAVASCRIPT_ES: true
|
||||
|
||||
2
.github/workflows/pypi-publish.yml
vendored
2
.github/workflows/pypi-publish.yml
vendored
@@ -1,4 +1,4 @@
|
||||
# This workflows will upload a Python Package using Twine when a release is created
|
||||
# This workflow will upload a Python Package using Twine when a release is created
|
||||
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
||||
|
||||
name: Upload Package to PyPI
|
||||
|
||||
@@ -27,7 +27,7 @@ env:
|
||||
# Install the application
|
||||
install:
|
||||
# Install requirements
|
||||
- pip install -r requirements_devel.txt
|
||||
- pip install -r requirements_dev.txt
|
||||
- python setup.py develop
|
||||
- cd wger
|
||||
- if [[ "$DB" = "postgresql" ]]; then pip install psycopg2; fi
|
||||
|
||||
@@ -25,6 +25,11 @@ Developers
|
||||
* Malcolm Jones: https://github.com/DevloperMal
|
||||
* Boniface Mwenda: https://github.com/andela-bmwenda
|
||||
* Scott Peshak: https://github.com/speshak
|
||||
* Musanje Louis Michael: https://github.com/louiCoder
|
||||
* Kevin Antonio Rateni Iatauro: https://github.com/WalkingPizza
|
||||
* Sven - https://github.com/Svn-Sp
|
||||
* Christopher OConnell - https://github.com/oconnelc
|
||||
* Biplov - https://github.com/beingbiplov
|
||||
|
||||
Translators
|
||||
-----------
|
||||
|
||||
@@ -4,7 +4,7 @@ include AUTHORS.txt
|
||||
include AGPL.txt
|
||||
include CC-BY-SA.txt
|
||||
include requirements.txt
|
||||
include requirements_devel.txt
|
||||
include requirements_dev.txt
|
||||
|
||||
# Application folder as well as extras
|
||||
recursive-include extras *.*
|
||||
|
||||
146
README.md
Normal file
146
README.md
Normal file
@@ -0,0 +1,146 @@
|
||||
<img src="https://raw.githubusercontent.com/wger-project/wger/master/wger/core/static/images/logos/logo.png" width="100" height="100" />
|
||||
|
||||
# wger
|
||||
|
||||
wger (ˈvɛɡɐ) Workout Manager is a free, open source web application that help
|
||||
you manage your personal workouts, weight and diet plans and can also be used
|
||||
as a simple gym management utility. It offers a REST API as well, for easy
|
||||
integration with other projects and tools.
|
||||
|
||||
For a live system, refer to the project's site: <https://wger.de/>
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
These are the basic steps to install and run the application locally on a Linux
|
||||
system. There are more detailed instructions, other deployment options as well
|
||||
as an administration guide available at <https://wger.readthedocs.io> or locally
|
||||
in your code repository in the docs folder.
|
||||
|
||||
Please consult the commands' help for further information and available
|
||||
parameters.
|
||||
|
||||
|
||||
### Production
|
||||
|
||||
If you want to host your own instance, take a look at the provided docker
|
||||
compose file. This config will persist your database and uploaded images:
|
||||
|
||||
<https://github.com/wger-project/docker>
|
||||
|
||||
### Demo
|
||||
|
||||
If you just want to try it out:
|
||||
|
||||
```shell script
|
||||
docker run -ti --name wger.apache --publish 8000:80 wger/apache
|
||||
```
|
||||
|
||||
Then just open <http://localhost:8000> and log in as **admin**, password **admin**
|
||||
|
||||
Please note that this image will overwrite your data when you pull a new version,
|
||||
it is only intended as an easy to setup demo
|
||||
|
||||
### Development version
|
||||
|
||||
#### Docker
|
||||
|
||||
To develop, check the README in wger/extras/docker on how to use
|
||||
the wger/devel docker image or the docker-compose file for development
|
||||
|
||||
#### Local installation (git)
|
||||
|
||||
**Note:** You can safely install from master, it is almost always in a usable
|
||||
and stable state.
|
||||
|
||||
|
||||
Install the necessary packages
|
||||
|
||||
```shell script
|
||||
sudo apt-get install python3-dev nodejs npm git
|
||||
sudo npm install -g yarn sass
|
||||
```
|
||||
|
||||
Make a virtualenv where we will install the python packages
|
||||
|
||||
```shell script
|
||||
python3 -m venv venv-wger
|
||||
source venv-wger/bin/activate
|
||||
```
|
||||
|
||||
Start the application. This will download the required JS and CSS libraries
|
||||
and create a SQlite database and populate it with data on the first run. If
|
||||
you want to use another database, edit the settings.py file before calling
|
||||
bootstrap. You will need to create the database and user yourself.
|
||||
|
||||
```shell script
|
||||
git clone https://github.com/wger-project/wger.git
|
||||
cd wger
|
||||
pip install -r requirements.txt
|
||||
python setup.py develop
|
||||
wger create-settings
|
||||
wger bootstrap
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
Log in as: **admin**, password **admin**
|
||||
|
||||
After the first run you just start django's development server::
|
||||
|
||||
```shell script
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
|
||||
### Command line options
|
||||
|
||||
You can get a list of all available commands by calling ``wger`` without any
|
||||
arguments:
|
||||
|
||||
* `bootstrap` Performs all steps necessary to bootstrap the application
|
||||
* `config-location` Returns the default location for the settings file
|
||||
and the data folder
|
||||
* `create-or-reset-admin` Creates an admin user or resets the password
|
||||
for an existing one
|
||||
* `create-settings` Creates a local settings file
|
||||
* `load-fixtures` Loads all fixtures
|
||||
* `migrate-db` Run all database migrations
|
||||
* `start` Start the application using django's built in webserver
|
||||
|
||||
To get help on a specific command: ``wger <command> --help``.
|
||||
|
||||
|
||||
## Contact
|
||||
|
||||
Feel free to contact us if you found this useful or if there was something that
|
||||
didn't behave as you expected. We can't fix what we don't know about, so please
|
||||
report liberally. If you're not sure if something is a bug or not, feel free to
|
||||
file a bug anyway.
|
||||
|
||||
* **discord:** <https://discord.gg/rPWFv6W>
|
||||
* **gitter:** <https://gitter.im/wger-project/wger>
|
||||
* **issue tracker:** <https://github.com/wger-project/wger/issues>
|
||||
* **twitter:** <https://twitter.com/wger_project>
|
||||
|
||||
|
||||
## Sources
|
||||
|
||||
All the code and the content is available on github:
|
||||
|
||||
<https://github.com/wger-project/wger>
|
||||
|
||||
|
||||
## License
|
||||
|
||||
The application is licensed under the Affero GNU General Public License 3 or
|
||||
later (AGPL 3+).
|
||||
|
||||
The initial exercise and ingredient data is licensed additionally under one of
|
||||
the Creative Commons licenses, see the individual exercises for more details.
|
||||
|
||||
The documentation is released under a CC-BY-SA: either version 4 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Some images were taken from Wikipedia, see the SOURCES file in their respective
|
||||
folders for more details.
|
||||
166
README.rst
166
README.rst
@@ -1,166 +0,0 @@
|
||||
wger
|
||||
====
|
||||
|
||||
wger (ˈvɛɡɐ) Workout Manager is a free, open source web application that help
|
||||
you manage your personal workouts, weight and diet plans and can also be used
|
||||
as a simple gym management utility. It offers a REST API as well, for easy
|
||||
integration with other projects and tools.
|
||||
|
||||
For a live system, refer to the project's site: https://wger.de/
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
These are the basic steps to install and run the application locally on a Linux
|
||||
system. There are more detailed instructions, other deployment options as well
|
||||
as an administration guide available at https://wger.readthedocs.io or locally
|
||||
in your code repository in the docs folder.
|
||||
|
||||
Please consult the commands' help for further information and available
|
||||
parameters.
|
||||
|
||||
|
||||
Docker
|
||||
------
|
||||
|
||||
Useful to just try it out. Check the documentation on how to use the wger/devel
|
||||
docker image or the docker-compose file for development::
|
||||
|
||||
docker run -ti --name wger.apache --publish 8000:80 wger/apache
|
||||
|
||||
Then just open http://localhost:8000 and log in as: **admin**, password **admin**
|
||||
|
||||
|
||||
Development version (from git)
|
||||
------------------------------
|
||||
|
||||
**Note:** You can safely install from master, it is almost always in a usable
|
||||
and stable state.
|
||||
|
||||
|
||||
1) Install the necessary packages
|
||||
|
||||
::
|
||||
|
||||
$ sudo apt-get install python3-dev nodejs npm git
|
||||
$ sudo npm install -g yarn sass
|
||||
|
||||
|
||||
Then install the python packages from pypi in the virtualenv::
|
||||
|
||||
$ python3 -m venv venv-wger
|
||||
$ source venv-wger/bin/activate
|
||||
|
||||
|
||||
2) Start the application. This will download the required JS and CSS libraries
|
||||
and create a SQlite database and populate it with data on the first run.
|
||||
|
||||
::
|
||||
|
||||
$ git clone https://github.com/wger-project/wger.git
|
||||
$ cd wger
|
||||
$ pip install -r requirements.txt
|
||||
$ python setup.py develop
|
||||
$ wger create-settings --settings-path $(pwd)/settings.py --database-path $(pwd)/database.sqlite
|
||||
$ wger bootstrap --settings-path $(pwd)/settings.py --no-start-server
|
||||
$ python manage.py runserver
|
||||
|
||||
3) Log in as: **admin**, password **admin**
|
||||
|
||||
After the first run you just start django's development server::
|
||||
|
||||
$ python manage.py runserver
|
||||
|
||||
|
||||
Stable version (from PyPI)
|
||||
--------------------------
|
||||
|
||||
1) Install the necessary packages and their dependencies in a virtualenv
|
||||
|
||||
::
|
||||
|
||||
$ sudo apt-get install python3-dev nodejs npm git
|
||||
$ sudo npm install -g yarn
|
||||
$ python3 -m venv venv-wger
|
||||
$ source venv-wger/bin/activate
|
||||
$ pip install wger
|
||||
|
||||
|
||||
2) Start the application. This will download the required JS and CSS libraries
|
||||
and create a SQlite database and populate it with data on the first run.
|
||||
Then, log in as: **admin**, password **admin**
|
||||
|
||||
::
|
||||
|
||||
$ wger bootstrap
|
||||
|
||||
|
||||
3) To start the installation again, just call wger start
|
||||
|
||||
::
|
||||
|
||||
$ wger start
|
||||
|
||||
|
||||
Command line options
|
||||
--------------------
|
||||
You can get a list of all available commands by calling ``wger`` without any
|
||||
arguments::
|
||||
|
||||
Available tasks:
|
||||
|
||||
bootstrap Performs all steps necessary to bootstrap the application
|
||||
config-location Returns the default location for the settings file and the data folder
|
||||
create-or-reset-admin Creates an admin user or resets the password for an existing one
|
||||
create-settings Creates a local settings file
|
||||
load-fixtures Loads all fixtures
|
||||
migrate-db Run all database migrations
|
||||
start Start the application using django's built in webserver
|
||||
|
||||
You can also get help on a specific command with ``wger --help <command>``.
|
||||
|
||||
Contact
|
||||
=======
|
||||
|
||||
Feel free to contact us if you found this useful or if there was something that
|
||||
didn't behave as you expected. We can't fix what we don't know about, so please
|
||||
report liberally. If you're not sure if something is a bug or not, feel free to
|
||||
file a bug anyway.
|
||||
|
||||
* **gitter:** https://gitter.im/wger-project/wger
|
||||
* **issue tracker:** https://github.com/wger-project/wger/issues
|
||||
* **twitter:** https://twitter.com/wger_project
|
||||
|
||||
|
||||
Sources
|
||||
=======
|
||||
|
||||
All the code and the content is freely available:
|
||||
|
||||
* **Main repository:** https://github.com/wger-project/wger
|
||||
|
||||
|
||||
Donations
|
||||
=========
|
||||
wger is free software and will always remain that way. However, if you want to
|
||||
help and support the project you are more than welcome to donate an amount of
|
||||
your choice.
|
||||
|
||||
.. image:: https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif
|
||||
:target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UPMWQJY85JC5N
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
The application is licensed under the Affero GNU General Public License 3 or
|
||||
later (AGPL 3+).
|
||||
|
||||
The initial exercise and ingredient data is licensed additionally under one of
|
||||
the Creative Commons licenses, see the individual exercises for more details.
|
||||
|
||||
The documentation is released under a CC-BY-SA: either version 4 of the License,
|
||||
or (at your option) any later version.
|
||||
|
||||
Some images were taken from Wikipedia, see the SOURCES file in their respective
|
||||
folders for more details.
|
||||
@@ -9,7 +9,7 @@ Upgrade steps from 1.9:
|
||||
* Update python libraries ``pip install -r requirements.txt``
|
||||
* Install ``yarn`` and ``sass`` (e.g. ``sudo npm install -g yarn sass``)
|
||||
* Update CSS and JS libraries ``yarn install``
|
||||
* Compile the CSS ``sass wger/core/static/scss/main.scss:wger/core/static/yarn/bootstrap-compiled.css``
|
||||
* Compile the CSS ``yarn build:css:sass``
|
||||
* Run migrations ``python manage.py migrate``
|
||||
* Update static files (only production): ``python manage.py collectstatic``
|
||||
* Subcommands for ``wger`` now use dashes in their names (i.e. create-settings instead of create_settings)
|
||||
@@ -17,13 +17,16 @@ Upgrade steps from 1.9:
|
||||
|
||||
🚀 Features:
|
||||
|
||||
* Add nutrition diary to log the daily calories actually taken `#284`_
|
||||
* Add nutrition diary to log the daily calories actually taken `#284`_ `#501`_ (thanks `@WalkingPizza`_)
|
||||
`#506`_ (thanks `@oconnelc`_)
|
||||
* Improved user experience, on desktop and mobile `#337`_
|
||||
* Show BMI on weight graph `#462`_ (thanks `@Svn-Sp`_)
|
||||
* Allow user to edit and delete body weight entries `#478`_ (thanks `@beingbiplov`_)
|
||||
|
||||
|
||||
🐛 Bug Fixes:
|
||||
|
||||
* `#499`_, `#505`_, `#504`_
|
||||
* `#499`_, `#505`_, `#504`_, `#511`_, `#516`_, `#522`_
|
||||
|
||||
|
||||
🧰 Maintenance:
|
||||
@@ -32,14 +35,29 @@ Upgrade steps from 1.9:
|
||||
* Updated many libraries to last version (bootstrap, font awesome, etc.)
|
||||
* Use yarn to download CSS/JS libraries
|
||||
* Improvements to documentation (e.g. `#494`_)
|
||||
* Improved cache handling `#246`_ (thanks `@louiCoder`_)
|
||||
|
||||
.. _@Svn-Sp: https://github.com/Svn-Sp
|
||||
.. _@louiCoder: https://github.com/louiCoder
|
||||
.. _@WalkingPizza: https://github.com/WalkingPizza
|
||||
.. _@oconnelc: https://github.com/oconnelc
|
||||
.. _@beingbiplov: https://github.com/beingbiplov
|
||||
|
||||
.. _#246: https://github.com/wger-project/wger/issues/246
|
||||
.. _#284: https://github.com/wger-project/wger/issues/284
|
||||
.. _#337: https://github.com/wger-project/wger/issues/337
|
||||
.. _#340: https://github.com/wger-project/wger/issues/340
|
||||
.. _#462: https://github.com/wger-project/wger/issues/462
|
||||
.. _#478: https://github.com/wger-project/wger/issues/478
|
||||
.. _#494: https://github.com/wger-project/wger/issues/494
|
||||
.. _#499: https://github.com/wger-project/wger/issues/499
|
||||
.. _#501: https://github.com/wger-project/wger/issues/501
|
||||
.. _#504: https://github.com/wger-project/wger/issues/504
|
||||
.. _#505: https://github.com/wger-project/wger/issues/505
|
||||
.. _#506: https://github.com/wger-project/wger/issues/506
|
||||
.. _#511: https://github.com/wger-project/wger/issues/511
|
||||
.. _#516: https://github.com/wger-project/wger/issues/516
|
||||
.. _#522: https://github.com/wger-project/wger/issues/522
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ running application (to e.g. delete guest users, send emails, etc.).
|
||||
Administration Commands
|
||||
-----------------------
|
||||
|
||||
The application provides several administration and bootstraping commands that
|
||||
The application provides several administration and bootstrapping commands that
|
||||
can be passed to the ``wger`` command::
|
||||
|
||||
wger <command>
|
||||
@@ -25,7 +25,7 @@ arguments::
|
||||
create-settings Creates a local settings file
|
||||
load-fixtures Loads all fixtures
|
||||
migrate-db Run all database migrations
|
||||
start Start the application using django's built in webserver
|
||||
start Start the application using django's built-in webserver
|
||||
|
||||
You can also get help on a specific command with ``wger --help <command>``.
|
||||
|
||||
|
||||
24
docs/conf.py
24
docs/conf.py
@@ -47,8 +47,8 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'wger Workout Manager'
|
||||
copyright = u'2020, Roland Geider'
|
||||
project = 'wger Workout Manager'
|
||||
copyright = '2020, Roland Geider'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
@@ -105,18 +105,18 @@ pygments_style = 'sphinx'
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
@@ -124,7 +124,7 @@ html_theme = 'default'
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
@@ -200,8 +200,8 @@ latex_elements = {
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'wgerWorkoutManager.tex', u'wger Workout Manager Documentation',
|
||||
u'Roland Geider', 'manual'),
|
||||
('index', 'wgerWorkoutManager.tex', 'wger Workout Manager Documentation',
|
||||
'Roland Geider', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
@@ -230,8 +230,8 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'wgerworkoutmanager', u'wger Workout Manager Documentation',
|
||||
[u'Roland Geider'], 1)
|
||||
('index', 'wgerworkoutmanager', 'wger Workout Manager Documentation',
|
||||
['Roland Geider'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
@@ -244,8 +244,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'wgerWorkoutManager', u'wger Workout Manager Documentation',
|
||||
u'Roland Geider', 'wgerWorkoutManager', 'One line description of project.',
|
||||
('index', 'wgerWorkoutManager', 'wger Workout Manager Documentation',
|
||||
'Roland Geider', 'wgerWorkoutManager', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
@@ -21,13 +21,12 @@ Get the code
|
||||
|
||||
$ git clone https://github.com/wger-project/wger.git src
|
||||
$ cd src
|
||||
$ WGER_PATH=$(pwd)
|
||||
|
||||
Install Requirements
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
::
|
||||
|
||||
$ pip install -r requirements_devel.txt
|
||||
$ pip install -r requirements_dev.txt
|
||||
$ npm install -g yarn sass
|
||||
$ python setup.py develop
|
||||
|
||||
@@ -38,12 +37,8 @@ This will download the required JS and CSS libraries and create a SQlite
|
||||
database and populate it with data on the first run::
|
||||
|
||||
|
||||
$ wger create-settings \
|
||||
--settings-path $WGER_PATH/settings.py \
|
||||
--database-path $WGER_PATH/database.sqlite
|
||||
$ wger bootstrap \
|
||||
--settings-path $WGER_PATH/settings.py \
|
||||
--no-start-server
|
||||
$ wger create-settings
|
||||
$ wger bootstrap
|
||||
|
||||
You can of course also use other databases such as postgres or mariaDB. Create
|
||||
a database and user and edit the DATABASES settings before calling bootstrap.
|
||||
|
||||
@@ -18,7 +18,7 @@ There are 3 groups used for the different administrative roles:
|
||||
|
||||
* **general manager:** Can manage (add, edit, delete) the different gyms for the
|
||||
installation as well as add gym managers, trainers and member, but is not
|
||||
allowed to see the members workout data.
|
||||
allowed to see the members' workout data.
|
||||
* **gym manager:** Can manage users for a single gym (editing, deactivating,
|
||||
adding contracts, etc.).
|
||||
* **trainer:** Can manage the workouts and other data for the members of a
|
||||
@@ -34,7 +34,7 @@ be changed later. The user's gym appears in the top right menu.
|
||||
|
||||
Member management
|
||||
-----------------
|
||||
You can new members to a gym by clicking the *Add member* button at the top of
|
||||
You can add members to a gym by clicking the *Add member* button at the top of
|
||||
the member overview. After filling in the form, a password will be generated
|
||||
for the user. You should save this and give it to the user, as it is not possible
|
||||
to retrieve it later. Alternatively you can just instruct the new members to
|
||||
|
||||
@@ -58,11 +58,10 @@ Feel free to contact us if you found this useful or if there was something that
|
||||
didn't behave as you expected (in this case you can also open a ticket on the
|
||||
issue tracker).
|
||||
|
||||
* **discord:** https://discord.gg/rPWFv6W
|
||||
* **gitter:** https://gitter.im/wger-project/wger
|
||||
* **issue tracker:** https://github.com/wger-project/wger/issues
|
||||
* **twitter:** https://twitter.com/wger_project
|
||||
* **mailing list:** https://groups.google.com/group/wger / wger@googlegroups.com,
|
||||
no registration needed
|
||||
|
||||
|
||||
Sources
|
||||
|
||||
@@ -88,7 +88,8 @@ If using sqlite, create a folder for it (must be writable by the apache user)::
|
||||
|
||||
mkdir db
|
||||
touch db/database.sqlite
|
||||
chmod -R o+w db
|
||||
chown :www-data -R /home/wger/db
|
||||
chmod g+w /home/wger/db /home/wger/db/database.sqlite
|
||||
|
||||
Application
|
||||
-----------
|
||||
@@ -116,9 +117,7 @@ Get the application::
|
||||
npm install -g yarn sass
|
||||
python setup.py develop
|
||||
pip install psycopg2 # Only if using postgres
|
||||
wger create-settings \
|
||||
--settings-path /home/wger/src/settings.py \
|
||||
--database-path /home/wger/db/database.sqlite
|
||||
wger create-settings --database-path /home/wger/db/database.sqlite
|
||||
|
||||
If you are using postgres, you need to edit the settings file and set the
|
||||
correct values for the database (use ``django.db.backends.postgresql_psycopg2``
|
||||
@@ -128,7 +127,7 @@ for the engine). Also set ``MEDIA_ROOT`` to ``/home/wger/media`` and
|
||||
Run the installation script, this will download some CSS and JS libraries and
|
||||
load all initial data::
|
||||
|
||||
wger bootstrap --settings-path /home/wger/src/settings.py --no-start-server
|
||||
wger bootstrap
|
||||
|
||||
|
||||
Collect all static resources::
|
||||
|
||||
@@ -32,7 +32,7 @@ Updating SASS files
|
||||
```````````````````
|
||||
After updating the SASS files, you need to compile them to regular CSS::
|
||||
|
||||
sass wger/core/static/scss/main.scss:wger/core/static/yarn/bootstrap-compiled.css
|
||||
yarn build:css:sass
|
||||
|
||||
|
||||
Clearing the cache
|
||||
@@ -80,7 +80,8 @@ Or for options for, e.g. user generation::
|
||||
|
||||
To get you started, you might want to invoke the script in the following way. This
|
||||
will create 10 gyms and 300 users, randomly assigning them to a different gym. Each
|
||||
user will have 20 workouts and each exercise in each workout 30 log entries::
|
||||
user will have 20 workouts and each exercise in each workout 30 log entries as well
|
||||
as 10 nutrition diary entries per day::
|
||||
|
||||
python generator.py gyms 10
|
||||
python generator.py users 300
|
||||
@@ -89,6 +90,7 @@ user will have 20 workouts and each exercise in each workout 30 log entries::
|
||||
python generator.py sessions random
|
||||
python generator.py weight 100
|
||||
python generator.py nutrition 20
|
||||
python generator.py nutrition-diary 10
|
||||
|
||||
.. note::
|
||||
All generated users have their username as password.
|
||||
|
||||
@@ -9,7 +9,7 @@ package manager) if you want to create plots. It requires a running web test
|
||||
server (you can edit which one in the .conf file)
|
||||
|
||||
WARNING: You should *not* run this script against a server that is not under
|
||||
your responsablity as it can result a DOS in bench mode.
|
||||
your responsibility as it can result a DOS in bench mode.
|
||||
|
||||
For more information on configuration, options, etc. refer to the funkload
|
||||
documentation page: http://funkload.nuxeo.org
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# A wger installation under apache with WSGI
|
||||
#
|
||||
# Note: you MUST build this image from the projec's root!
|
||||
# Note: you MUST build this image from the project's root!
|
||||
# docker build -f extras/docker/apache/Dockerfile --tag wger/apache .
|
||||
#
|
||||
# Please consult the documentation for usage
|
||||
@@ -31,7 +31,7 @@ RUN apt-get update \
|
||||
|
||||
# Set up the application
|
||||
COPY requirements* ./
|
||||
RUN pip3 wheel --no-cache-dir --wheel-dir /usr/src/app/wheels -r requirements_devel.txt
|
||||
RUN pip3 wheel --no-cache-dir --wheel-dir /usr/src/app/wheels -r requirements_dev.txt
|
||||
|
||||
|
||||
########
|
||||
@@ -85,12 +85,8 @@ RUN . /home/wger/venv/bin/activate \
|
||||
&& pip install wheel \
|
||||
&& pip install --no-cache /wheels/* \
|
||||
&& python setup.py develop \
|
||||
&& wger create-settings \
|
||||
--settings-path /home/wger/src/settings.py \
|
||||
--database-path /home/wger/db/database.sqlite \
|
||||
&& wger bootstrap \
|
||||
--settings-path /home/wger/src/settings.py \
|
||||
--no-start-server
|
||||
&& wger create-settings --database-path /home/wger/db/database.sqlite \
|
||||
&& wger bootstrap
|
||||
|
||||
|
||||
# Change permissions of some files and folders so the apache process
|
||||
@@ -104,6 +100,7 @@ RUN mkdir -p ~/static/CACHE ~/media \
|
||||
USER root
|
||||
RUN apt-get remove build-essential -y \
|
||||
&& apt autoremove -y \
|
||||
&& chown www-data:www-data -R /home/wger/db
|
||||
&& chown :www-data -R /home/wger/db \
|
||||
&& chmod g+w /home/wger/db /home/wger/db/database.sqlite
|
||||
|
||||
ENTRYPOINT ["/home/wger/entrypoint.sh"]
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
Demo image for wger
|
||||
===================
|
||||
Thank you for downloading wger Workout Manager. wger (ˈvɛɡɐ) is a free, open
|
||||
source web application that manages your exercises and personal workouts, weight
|
||||
and diet plans. It can also be used as a simple gym management utility, providing
|
||||
different administrative roles (trainer, manager, etc.). It offers a REST API
|
||||
as well, for easy integration with other projects and tools.
|
||||
wger (ˈvɛɡɐ) Workout Manager is a free, open source web application that help
|
||||
you manage your personal workouts, weight and diet plans and can also be used
|
||||
as a simple gym management utility. It offers a REST API as well, for easy
|
||||
integration with other projects and tools.
|
||||
|
||||
It is written with python/django and uses jQuery and some D3js for charts.
|
||||
|
||||
Please note that this image will overwrite your data when you pull a new version,
|
||||
it is only intended as an easy to setup demo. If you want to host your own
|
||||
instance, take a look at the provided docker compose file. That config will
|
||||
persist your database and uploaded images:
|
||||
|
||||
<https://github.com/wger-project/docker>
|
||||
|
||||
Installation
|
||||
------------
|
||||
@@ -46,10 +51,10 @@ didn't behave as you expected. We can't fix what we don't know about, so please
|
||||
report liberally. If you're not sure if something is a bug or not, feel free to
|
||||
file a bug anyway.
|
||||
|
||||
* discord: <https://discord.gg/rPWFv6W>
|
||||
* gitter: <https://gitter.im/wger-project/wger>
|
||||
* issue tracker: <https://github.com/wger-project/wger/issues>
|
||||
* twitter: <https://twitter.com/wger_project>
|
||||
* mailing list: <https://groups.google.com/group/wger> / wger@googlegroups.com, no registration needed
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
@@ -25,7 +25,7 @@ RUN apt-get update \
|
||||
sqlite3 \
|
||||
netcat-openbsd \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& npm install -g yarn sass \
|
||||
&& npm install -g yarn@1.22.5 sass\
|
||||
&& locale-gen en_US.UTF-8
|
||||
|
||||
# Environmental variables
|
||||
|
||||
@@ -14,10 +14,10 @@ didn't behave as you expected. We can't fix what we don't know about, so please
|
||||
report liberally. If you're not sure if something is a bug or not, feel free to
|
||||
file a bug anyway.
|
||||
|
||||
* discord: <https://discord.gg/rPWFv6W>
|
||||
* gitter: <https://gitter.im/wger-project/wger>
|
||||
* issue tracker: <https://github.com/wger-project/wger/issues>
|
||||
* twitter: <https://twitter.com/wger_project>
|
||||
* mailing list: <https://groups.google.com/group/wger> / wger@googlegroups.com, no registration needed
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
@@ -66,10 +66,10 @@ didn't behave as you expected. We can't fix what we don't know about, so please
|
||||
report liberally. If you're not sure if something is a bug or not, feel free to
|
||||
file a bug anyway.
|
||||
|
||||
* discord: <https://discord.gg/rPWFv6W>
|
||||
* gitter: <https://gitter.im/wger-project/wger>
|
||||
* issue tracker: <https://github.com/wger-project/wger/issues>
|
||||
* twitter: <https://twitter.com/wger_project>
|
||||
* mailing list: <https://groups.google.com/group/wger> / wger@googlegroups.com, no registration needed
|
||||
|
||||
## Sources
|
||||
|
||||
|
||||
@@ -14,3 +14,7 @@ DJANGO_CACHE_TIMEOUT=100
|
||||
DJANGO_CACHE_CLIENT_CLASS=django_redis.client.DefaultClient
|
||||
|
||||
DJANGO_MEDIA_ROOT=/home/wger/media
|
||||
|
||||
# Others
|
||||
DJANGO_DEBUG=True
|
||||
WGER_USE_GUNICORN=False
|
||||
|
||||
@@ -7,6 +7,7 @@ services:
|
||||
- type: bind
|
||||
source: ../../../
|
||||
target: /home/wger/src/
|
||||
- media:/home/wger/media
|
||||
ports:
|
||||
- 8000:8000
|
||||
env_file:
|
||||
@@ -34,3 +35,4 @@ services:
|
||||
|
||||
volumes:
|
||||
wger-postgres-data:
|
||||
media:
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
# Docker image for wger development:
|
||||
#
|
||||
# This image uses a virtual environment, which is not necessary in a docker
|
||||
# image and is more or less intented to check that the installation instructions
|
||||
# for a local develoment are up-to-date
|
||||
# image and is more or less intended to check that the installation instructions
|
||||
# for a local development are up-to-date
|
||||
#
|
||||
# Please consult the documentation for usage
|
||||
#
|
||||
# Note: you MUST build this image from the projec's root!
|
||||
# Note: you MUST build this image from the project's root!
|
||||
# docker build -f extras/docker/development/Dockerfile --tag wger/devel .
|
||||
#
|
||||
# Run the container:
|
||||
@@ -43,7 +43,7 @@ RUN apt-get update \
|
||||
# Set up the application
|
||||
COPY . .
|
||||
RUN pip3 install wheel \
|
||||
&& pip3 wheel --no-cache-dir --wheel-dir /usr/src/app/wheels -r requirements_devel.txt
|
||||
&& pip3 wheel --no-cache-dir --wheel-dir /usr/src/app/wheels -r requirements_dev.txt
|
||||
|
||||
|
||||
########
|
||||
@@ -66,12 +66,8 @@ RUN . /home/wger/venv/bin/activate \
|
||||
&& pip install --upgrade pip \
|
||||
&& pip install --no-cache /wheels/* \
|
||||
&& python setup.py develop \
|
||||
&& wger create-settings \
|
||||
--settings-path /home/wger/src/settings.py \
|
||||
--database-path /home/wger/db/database.sqlite \
|
||||
&& wger bootstrap \
|
||||
--settings-path /home/wger/src/settings.py \
|
||||
--no-start-server
|
||||
&& wger create-settings --database-path /home/wger/db/database.sqlite \
|
||||
&& wger bootstrap
|
||||
|
||||
# Download the exercise images
|
||||
RUN mkdir ~/media \
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Please consult the README for usage
|
||||
#
|
||||
# Note: you MUST build this image from the projec's root!
|
||||
# Note: you MUST build this image from the project's root!
|
||||
# docker build -f extras/docker/development/Dockerfile --tag wger/devel .
|
||||
#
|
||||
# Run the container:
|
||||
@@ -25,7 +25,7 @@ RUN apt-get update \
|
||||
|
||||
# Build the necessary python wheels
|
||||
COPY requirements* ./
|
||||
RUN pip3 wheel --no-cache-dir --wheel-dir /wheels -r requirements_devel.txt
|
||||
RUN pip3 wheel --no-cache-dir --wheel-dir /wheels -r requirements_prod.txt
|
||||
|
||||
|
||||
|
||||
@@ -47,14 +47,13 @@ COPY ${DOCKER_DIR}/settings.py /tmp/
|
||||
COPY ${DOCKER_DIR}/entrypoint.sh /home/wger/entrypoint.sh
|
||||
RUN chmod +x /home/wger/entrypoint.sh
|
||||
RUN pip3 install --no-cache /wheels/* \
|
||||
&& pip3 install psycopg2-binary \
|
||||
&& pip3 install django-redis \
|
||||
&& python3 setup.py develop
|
||||
|
||||
RUN chown -R wger:wger .
|
||||
|
||||
USER wger
|
||||
RUN mkdir ~/media \
|
||||
&& mkdir ~/static \
|
||||
&& mkdir ~/db/
|
||||
|
||||
CMD ["/home/wger/entrypoint.sh"]
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
# Development image for wger
|
||||
wger (ˈvɛɡɐ) Workout Manager is a free, open source web application that help
|
||||
you manage your personal workouts, weight and diet plans and can also be used
|
||||
as a simple gym management utility. It offers a REST API as well, for easy
|
||||
integration with other projects and tools.
|
||||
|
||||
|
||||
Please note that this image is intended for development, if you want to
|
||||
host your own instance, take a look at the provided docker compose file:
|
||||
|
||||
<https://github.com/wger-project/docker>
|
||||
|
||||
Thank you for downloading wger Workout Manager. wger (ˈvɛɡɐ) is a free, open
|
||||
source web application that manages your exercises and personal workouts, weight
|
||||
and diet plans. It can also be used as a simple gym management utility, providing
|
||||
different administrative roles (trainer, manager, etc.). It offers a REST API
|
||||
as well, for easy integration with other projects and tools.
|
||||
|
||||
|
||||
## Usage
|
||||
@@ -65,10 +70,10 @@ didn't behave as you expected. We can't fix what we don't know about, so please
|
||||
report liberally. If you're not sure if something is a bug or not, feel free to
|
||||
file a bug anyway.
|
||||
|
||||
* discord: <https://discord.gg/rPWFv6W>
|
||||
* gitter: <https://gitter.im/wger-project/wger>
|
||||
* issue tracker: <https://github.com/wger-project/wger/issues>
|
||||
* twitter: <https://twitter.com/wger_project>
|
||||
* mailing list: <https://groups.google.com/group/wger> / wger@googlegroups.com, no registration needed
|
||||
|
||||
## Sources
|
||||
|
||||
|
||||
@@ -16,16 +16,31 @@ if [[ "$DJANGO_DB_PORT" == "5432" ]]; then
|
||||
echo "PostgreSQL started :)"
|
||||
fi
|
||||
|
||||
# Bootstrap the application
|
||||
wger bootstrap \
|
||||
--settings-path /home/wger/src/settings.py \
|
||||
--no-start-server
|
||||
|
||||
if [[ "$WGER_DOWNLOAD_IMGS" == "TRUE" ]];
|
||||
# The python wger package needs to be installed in development mode.
|
||||
# If the created folder does not exist (e.g. because this image was mounted
|
||||
# after a first checkout), repeat the process.
|
||||
if [ ! -d "/home/wger/src/wger.egg-info" ];
|
||||
then
|
||||
wger download-exercise-images
|
||||
chmod -R g+w ~wger/media
|
||||
python3 setup.py develop --user
|
||||
fi
|
||||
|
||||
# Run the development server
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
# Bootstrap the application
|
||||
# * Load the fixtures with exercises, ingredients, etc
|
||||
# * Create an admin user
|
||||
# * Download JS and CSS files
|
||||
# * Compile custom bootstrap theme
|
||||
wger bootstrap
|
||||
|
||||
# Collect static files
|
||||
if [[ "$DJANGO_DEBUG" == "False" ]];
|
||||
then
|
||||
python3 manage.py collectstatic --no-input
|
||||
fi
|
||||
|
||||
# Run the server
|
||||
if [[ "$WGER_USE_GUNICORN" == "True" ]];
|
||||
then
|
||||
gunicorn wger.wsgi:application --reload --bind 0.0.0.0:8000
|
||||
else
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
fi
|
||||
|
||||
@@ -6,8 +6,7 @@ from wger.settings_global import *
|
||||
|
||||
|
||||
# Use 'DEBUG = True' to get more details for server errors
|
||||
DEBUG = True
|
||||
TEMPLATES[0]['OPTIONS']['debug'] = True
|
||||
DEBUG = os.environ.get("DJANGO_DEBUG", True)
|
||||
|
||||
ADMINS = (
|
||||
('Your name', 'your_email@example.com'),
|
||||
@@ -51,9 +50,11 @@ SITE_URL = 'http://localhost:8000'
|
||||
|
||||
# Path to uploaded files
|
||||
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||
MEDIA_ROOT = '/home/wger/media'
|
||||
MEDIA_ROOT = os.environ.get("DJANGO_MEDIA_ROOT", '/home/wger/media')
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
STATIC_ROOT = os.environ.get("DJANGO_STATIC_ROOT", '/home/wger/static')
|
||||
|
||||
# Allow all hosts to access the application. Change if used in production.
|
||||
ALLOWED_HOSTS = '*'
|
||||
|
||||
|
||||
@@ -15,53 +15,59 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
import uuid
|
||||
import random
|
||||
import django
|
||||
import datetime
|
||||
# Standard Library
|
||||
import argparse
|
||||
import csv
|
||||
import datetime
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
# Django
|
||||
import django
|
||||
from django.db import IntegrityError
|
||||
from django.utils import timezone
|
||||
from django.utils.text import slugify
|
||||
|
||||
|
||||
sys.path.insert(0, os.path.join('..', '..'))
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings')
|
||||
django.setup()
|
||||
|
||||
# Django
|
||||
# Must happen after calling django.setup()
|
||||
from django.contrib.auth.models import User
|
||||
from wger.core.models import DaysOfWeek
|
||||
|
||||
# wger
|
||||
from wger.core.models import (
|
||||
DaysOfWeek,
|
||||
Language
|
||||
)
|
||||
from wger.exercises.models import Exercise
|
||||
from wger.gym.models import (
|
||||
GymUserConfig,
|
||||
Gym
|
||||
Gym,
|
||||
GymUserConfig
|
||||
)
|
||||
from wger.manager.models import (
|
||||
Workout,
|
||||
Day,
|
||||
Set,
|
||||
Setting,
|
||||
Schedule,
|
||||
ScheduleStep,
|
||||
Set,
|
||||
Setting,
|
||||
Workout,
|
||||
WorkoutLog,
|
||||
WorkoutSession
|
||||
)
|
||||
from wger.weight.models import WeightEntry
|
||||
|
||||
from wger.core.models import Language
|
||||
|
||||
# Nutrition import //_c
|
||||
from wger.nutrition.models import (
|
||||
Ingredient,
|
||||
IngredientWeightUnit,
|
||||
WeightUnit,
|
||||
NutritionPlan,
|
||||
LogItem,
|
||||
Meal,
|
||||
MealItem
|
||||
MealItem,
|
||||
NutritionPlan
|
||||
)
|
||||
from wger.weight.models import WeightEntry
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Data generator. Please consult the documentation')
|
||||
subparsers = parser.add_subparsers(help='The kind of entries you want to generate')
|
||||
@@ -132,12 +138,27 @@ weight_parser.add_argument('--base-weight',
|
||||
# Nutrition options
|
||||
nutrition_parser = subparsers.add_parser('nutrition', help='Creates a meal plan')
|
||||
nutrition_parser.add_argument('number_nutrition_plans',
|
||||
action='store',
|
||||
help='Number of meal plans to create',
|
||||
type=int)
|
||||
action='store',
|
||||
help='Number of meal plans to create',
|
||||
type=int)
|
||||
nutrition_parser.add_argument('--add-to-user',
|
||||
action='store',
|
||||
help='Add to the specified user-ID, not all existing users')
|
||||
action='store',
|
||||
help='Add to the specified user-ID, not all existing users')
|
||||
|
||||
# Nutrition diary options
|
||||
nutrition_parser = subparsers.add_parser('nutrition-diary', help='Creates a meal plan')
|
||||
nutrition_parser.add_argument('number_nutrition_logs',
|
||||
action='store',
|
||||
help='Number of nutrition diary logs to create',
|
||||
type=int)
|
||||
nutrition_parser.add_argument('--number-diary-dates',
|
||||
action='store',
|
||||
help='Number of dates in which to create logs (default: 30)',
|
||||
default=30,
|
||||
type=int)
|
||||
nutrition_parser.add_argument('--add-to-user',
|
||||
action='store',
|
||||
help='Add to the specified user-ID, not all existing users')
|
||||
|
||||
args = parser.parse_args()
|
||||
# print(args)
|
||||
@@ -248,7 +269,6 @@ if hasattr(args, 'number_gyms'):
|
||||
# Bulk-create all the gyms
|
||||
Gym.objects.bulk_create(gym_list)
|
||||
|
||||
|
||||
#
|
||||
# Workout generator
|
||||
#
|
||||
@@ -364,7 +384,6 @@ if hasattr(args, 'number_logs'):
|
||||
# Bulk-create the logs
|
||||
WorkoutLog.objects.bulk_create(weight_log)
|
||||
|
||||
|
||||
#
|
||||
# Session generator
|
||||
#
|
||||
@@ -382,8 +401,8 @@ if hasattr(args, 'impression_sessions'):
|
||||
|
||||
workout = WorkoutLog.objects.filter(user=user, date=date).first().workout
|
||||
start = datetime.time(hour=random.randint(8, 20), minute=random.randint(0, 59))
|
||||
end = datetime.datetime.combine(datetime.date.today(), start) \
|
||||
+ datetime.timedelta(minutes=random.randint(40, 120))
|
||||
end = datetime.datetime.combine(datetime.date.today(), start) \
|
||||
+ datetime.timedelta(minutes=random.randint(40, 120))
|
||||
end = datetime.time(hour=end.hour, minute=end.minute)
|
||||
|
||||
session = WorkoutSession()
|
||||
@@ -449,7 +468,7 @@ if hasattr(args, 'number_nutrition_plans'):
|
||||
userlist = [i for i in User.objects.all()]
|
||||
|
||||
# Load all ingredients to a list
|
||||
ingredientList = [i for i in Ingredient.objects.order_by('?').all()[:100]]
|
||||
ingredient_list = [i for i in Ingredient.objects.order_by('?').all()[:100]]
|
||||
|
||||
# Total meals per plan
|
||||
total_meals = 4
|
||||
@@ -461,8 +480,9 @@ if hasattr(args, 'number_nutrition_plans'):
|
||||
for i in range(0, args.number_nutrition_plans):
|
||||
uid = str(uuid.uuid4()).split('-')
|
||||
start_date = datetime.date.today() - datetime.timedelta(days=random.randint(0, 100))
|
||||
nutrition_plan = NutritionPlan(language=Language.objects.all()[1], description='Dummy nutrition plan - {0}'.format(uid[1]),
|
||||
creation_date=start_date)
|
||||
nutrition_plan = NutritionPlan(language=Language.objects.all()[1],
|
||||
description='Dummy nutrition plan - {0}'.format(uid[1]),
|
||||
creation_date=start_date)
|
||||
nutrition_plan.user = user
|
||||
|
||||
nutrition_plan.save()
|
||||
@@ -472,8 +492,40 @@ if hasattr(args, 'number_nutrition_plans'):
|
||||
for j in range(0, total_meals):
|
||||
meal = Meal(plan=nutrition_plan, order=order)
|
||||
meal.save()
|
||||
for k in range(0, random.randint(1,5)):
|
||||
ingredient = random.choice(ingredientList)
|
||||
meal_item = MealItem(meal=meal, ingredient=ingredient, weight_unit=None, order=order, amount=random.randint(10, 250))
|
||||
for k in range(0, random.randint(1, 5)):
|
||||
ingredient = random.choice(ingredient_list)
|
||||
meal_item = MealItem(meal=meal, ingredient=ingredient, weight_unit=None,
|
||||
order=order, amount=random.randint(10, 250))
|
||||
meal_item.save()
|
||||
order = order + 1
|
||||
|
||||
# Nutrition logs Generator
|
||||
if hasattr(args, 'number_nutrition_logs'):
|
||||
print("** Generating {0} nutrition plan(s) per user".format(args.number_nutrition_logs))
|
||||
|
||||
if args.add_to_user:
|
||||
userlist = [User.objects.get(pk=args.add_to_user)]
|
||||
else:
|
||||
userlist = [i for i in User.objects.all()]
|
||||
|
||||
# Load all ingredients to a list
|
||||
ingredient_list = [i for i in Ingredient.objects.order_by('?').all()[:100]]
|
||||
|
||||
for user in userlist:
|
||||
plan_list = NutritionPlan.objects.order_by('?').filter(user=user)
|
||||
print(' - generating for {0}'.format(user.username))
|
||||
|
||||
# Add diary entries
|
||||
for plan in NutritionPlan.objects.filter(user=user):
|
||||
for i in range(0, args.number_diary_dates):
|
||||
date = timezone.now() - datetime.timedelta(days=random.randint(0, 100),
|
||||
hours=random.randint(0, 12),
|
||||
minutes=random.randint(0, 59))
|
||||
for j in range(0, args.number_nutrition_logs):
|
||||
ingredient = random.choice(ingredient_list)
|
||||
log = LogItem(plan=plan,
|
||||
datetime=date,
|
||||
ingredient=ingredient,
|
||||
weight_unit=None,
|
||||
amount=random.randint(10, 300))
|
||||
log.save()
|
||||
|
||||
@@ -8,7 +8,7 @@ from django.core.management import execute_from_command_line
|
||||
|
||||
# wger
|
||||
from wger.tasks import (
|
||||
get_user_config_path,
|
||||
get_path,
|
||||
setup_django_environment
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ if __name__ == "__main__":
|
||||
|
||||
# If user passed the settings flag ignore the default wger settings
|
||||
if not any('--settings' in s for s in sys.argv):
|
||||
setup_django_environment(get_user_config_path('wger', 'settings.py'))
|
||||
setup_django_environment(get_path('settings.py'))
|
||||
|
||||
# Alternative to above
|
||||
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
|
||||
|
||||
@@ -19,14 +19,17 @@
|
||||
"yarn": "^1.22.5",
|
||||
"Sortable": "RubaXa/Sortable#1.10.2",
|
||||
"bootstrap": "twbs/bootstrap#4.x",
|
||||
"components-font-awesome": "components/font-awesome#5.14.0",
|
||||
"components-font-awesome": "components/font-awesome#5.15.1",
|
||||
"d3": "mbostock-bower/d3-bower#>=5",
|
||||
"datatables": "DataTables/DataTables#1.10.x",
|
||||
"devbridge-autocomplete": "^1.4.11",
|
||||
"jquery": "^3.5.0",
|
||||
"metrics-graphics": "mozilla/metrics-graphics#2.15.x",
|
||||
"shariff": "^3.2.1",
|
||||
"tinymce": "^5.4.2"
|
||||
"tinymce": "^5.5.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build:css:sass": "sass wger/core/static/scss/main.scss:wger/core/static/yarn/bootstrap-compiled.css"
|
||||
},
|
||||
"engines": {
|
||||
"yarn": ">= 1.0.0"
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
#
|
||||
# Requirements for wger for production
|
||||
# Common requirements for wger
|
||||
#
|
||||
|
||||
# Building/installing
|
||||
wheel
|
||||
|
||||
# Application
|
||||
bleach~=3.1
|
||||
django-activity-stream
|
||||
bleach~=3.2
|
||||
django-activity-stream~=0.9
|
||||
django-bootstrap-breadcrumbs~=0.9
|
||||
django-formtools~=2.2
|
||||
django-recaptcha==2.0.6
|
||||
@@ -16,13 +13,13 @@ django-crispy-forms~=1.9
|
||||
django_compressor~=2.4
|
||||
django_extensions~=3.0
|
||||
django-sortedm2m~=3.0
|
||||
django-storages~=1.9
|
||||
django-storages~=1.10
|
||||
easy-thumbnails~=2.7
|
||||
icalendar==4.0.6
|
||||
icalendar==4.0.7
|
||||
invoke~=1.4
|
||||
pillow~=7.2
|
||||
pillow~=8.0
|
||||
python-mimeparse
|
||||
reportlab==3.5.48
|
||||
reportlab==3.5.53
|
||||
matplotlib>=3.1
|
||||
requests
|
||||
setuptools>=18.5
|
||||
@@ -31,14 +28,11 @@ sphinx
|
||||
# AWS
|
||||
#boto3
|
||||
|
||||
# Production
|
||||
#psycopg2
|
||||
#python-memcached
|
||||
|
||||
# REST API
|
||||
django-cors-headers>=3.0
|
||||
django-filter==2.3.0
|
||||
djangorestframework~=3.11
|
||||
django-filter==2.4.0
|
||||
djangorestframework~=3.12
|
||||
|
||||
|
||||
# Explicitly set versions as a workaround for CI/Devel.
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#
|
||||
# Requirements for wger during development
|
||||
# Requirements for wger during development only
|
||||
#
|
||||
|
||||
# Production packages
|
||||
# Regular packages
|
||||
-r requirements.txt
|
||||
|
||||
# Building/installing
|
||||
wheel
|
||||
|
||||
# Development packages
|
||||
coverage
|
||||
django-debug-toolbar
|
||||
11
requirements_prod.txt
Normal file
11
requirements_prod.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# Requirements for wger for production
|
||||
#
|
||||
|
||||
# Regular packages
|
||||
-r requirements.txt
|
||||
|
||||
gunicorn~=20.0
|
||||
psycopg2-binary
|
||||
django-redis
|
||||
|
||||
@@ -26,7 +26,7 @@ import_heading_django=Django
|
||||
# A comment to consistently place directly above imports from the standard library.
|
||||
import_heading_stdlib=Standard Library
|
||||
|
||||
# A comment to consistently place directly above thirdparty imports.
|
||||
# A comment to consistently place directly above third-party imports.
|
||||
import_heading_thirdparty=Third Party
|
||||
|
||||
# A comment to consistently place directly above wger imports.
|
||||
@@ -39,18 +39,18 @@ import_heading_localfolder=Local
|
||||
# Tab to indent by a single tab.
|
||||
indent=' '
|
||||
|
||||
# A list of imports that will be forced to display withing the first party
|
||||
# A list of imports that will be forced to display within the first party
|
||||
# category.
|
||||
known_first_party=wger
|
||||
known_django=django
|
||||
|
||||
# An integer that represents how you want imports to be displayed if their long
|
||||
# An integer that represents how you want imports to be displayed if they're long
|
||||
# enough to span multiple lines. A full definition of all possible modes can be
|
||||
# found in isort's README.
|
||||
multi_line_output=3
|
||||
force_grid_wrap=True
|
||||
|
||||
# If set to true - isort will create separate sections withing "from" imports
|
||||
# If set to true - isort will create separate sections within "from" imports
|
||||
# for CONSTANTS, Classes, and modules/functions.
|
||||
order_by_type=True
|
||||
|
||||
|
||||
6
setup.py
6
setup.py
@@ -17,7 +17,7 @@ from setuptools import (
|
||||
from wger import get_version
|
||||
|
||||
|
||||
with open('README.rst') as readme:
|
||||
with open('README.md') as readme:
|
||||
long_description = readme.read()
|
||||
|
||||
with open('requirements.txt') as requirements_production:
|
||||
@@ -27,7 +27,7 @@ setup(
|
||||
name='wger',
|
||||
description='FLOSS workout, fitness and weight manager/tracker written with Django',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/x-rst',
|
||||
long_description_content_type='text/markdown',
|
||||
version=get_version(),
|
||||
url='https://github.com/wger-project',
|
||||
author='Roland Geider',
|
||||
@@ -49,7 +49,7 @@ setup(
|
||||
'Programming Language :: Python :: 3.8',
|
||||
],
|
||||
|
||||
nstall_requires=install_requires,
|
||||
install_requires=install_requires,
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'wger = wger.__main__:main',
|
||||
|
||||
@@ -32,7 +32,7 @@ invoke_cmd = 'invoke '
|
||||
|
||||
|
||||
def main():
|
||||
# Change the working directory so that invoke can find the tasks fiel
|
||||
# Change the working directory so that invoke can find the tasks file
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
args = sys.argv[1:]
|
||||
|
||||
@@ -78,7 +78,7 @@ class LanguageConfig(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"Config for language {0}".format(self.language)
|
||||
return "Config for language {0}".format(self.language)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
@@ -134,7 +134,7 @@ class GymConfig(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"Default gym {0}".format(self.default_gym)
|
||||
return "Default gym {0}".format(self.default_gym)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Workout Manager. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Django
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
# Third Party
|
||||
from rest_framework import serializers
|
||||
|
||||
@@ -45,6 +48,16 @@ class UsernameSerializer(serializers.Serializer):
|
||||
username = serializers.CharField()
|
||||
|
||||
|
||||
class UserApiSerializer(serializers.ModelSerializer):
|
||||
""" Serializer to map to User model in relation to api user"""
|
||||
username = serializers.CharField(required=True)
|
||||
password = serializers.CharField(required=True, min_length=8)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['username', 'password']
|
||||
|
||||
|
||||
class LanguageSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Language serializer
|
||||
|
||||
@@ -15,12 +15,19 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Workout Manager. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Standard Library
|
||||
import logging
|
||||
|
||||
# Django
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
# Third Party
|
||||
from rest_framework import viewsets
|
||||
from rest_framework import (
|
||||
status,
|
||||
viewsets
|
||||
)
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
|
||||
# wger
|
||||
@@ -29,6 +36,7 @@ from wger.core.api.serializers import (
|
||||
LanguageSerializer,
|
||||
LicenseSerializer,
|
||||
RepetitionUnitSerializer,
|
||||
UserApiSerializer,
|
||||
UsernameSerializer,
|
||||
UserprofileSerializer,
|
||||
WeightUnitSerializer
|
||||
@@ -41,12 +49,16 @@ from wger.core.models import (
|
||||
UserProfile,
|
||||
WeightUnit
|
||||
)
|
||||
from wger.utils.api_token import create_token
|
||||
from wger.utils.permissions import (
|
||||
UpdateOnlyPermission,
|
||||
WgerPermission
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UserProfileViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint for workout objects
|
||||
@@ -78,6 +90,43 @@ class UserProfileViewSet(viewsets.ModelViewSet):
|
||||
return Response(UsernameSerializer(user).data)
|
||||
|
||||
|
||||
class UserAPILoginView(viewsets.ViewSet):
|
||||
"""
|
||||
API endpoint for api user objects
|
||||
"""
|
||||
permission_classes = (AllowAny,)
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserApiSerializer
|
||||
throttle_scope = 'login'
|
||||
|
||||
def get(self, request):
|
||||
return Response({'message': "You must send a 'username' and 'password' via POST"})
|
||||
|
||||
def post(self, request):
|
||||
data = request.data
|
||||
serializer = self.serializer_class(data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
username = serializer.data["username"]
|
||||
password = serializer.data["password"]
|
||||
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
logger.info(f"Tried logging via API with unknown user: '{username}'")
|
||||
return Response({'detail': 'Username or password unknown'},
|
||||
status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
if user.check_password(password):
|
||||
token = create_token(user)
|
||||
return Response({'token': token.key},
|
||||
status=status.HTTP_200_OK)
|
||||
else:
|
||||
logger.info(f"User '{username}' tried logging via API with a wrong password")
|
||||
return Response({'detail': 'Username or password unknown'},
|
||||
status=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
|
||||
class LanguageViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
API endpoint for workout objects
|
||||
|
||||
@@ -112,7 +112,7 @@ class UserPreferencesForm(forms.ModelForm):
|
||||
'workout_reminder',
|
||||
'workout_duration',
|
||||
),
|
||||
AccordionGroup("{} ({})".format(_("Gym mode"), _("mobile version only")),
|
||||
AccordionGroup(f"{_('Gym mode')} ({_('mobile version only')})",
|
||||
"timer_active",
|
||||
"timer_pause"
|
||||
),
|
||||
|
||||
@@ -80,24 +80,24 @@ class Command(BaseCommand):
|
||||
|
||||
for user in User.objects.all():
|
||||
if int(options['verbosity']) >= 2:
|
||||
self.stdout.write("* Processing user {0}".format(user.username))
|
||||
self.stdout.write(f"* Processing user {user.username}")
|
||||
|
||||
for entry in WorkoutLog.objects.filter(user=user).dates('date', 'year'):
|
||||
|
||||
if int(options['verbosity']) >= 3:
|
||||
self.stdout.write(" Year {0}".format(entry.year))
|
||||
self.stdout.write(f" Year {entry.year}")
|
||||
for month in WorkoutLog.objects.filter(user=user,
|
||||
date__year=entry.year).dates('date',
|
||||
'month'):
|
||||
if int(options['verbosity']) >= 3:
|
||||
self.stdout.write(" Month {0}".format(entry.month))
|
||||
self.stdout.write(f" Month {entry.month}")
|
||||
reset_workout_log(user.id, entry.year, entry.month)
|
||||
for day in WorkoutLog.objects.filter(user=user,
|
||||
date__year=entry.year,
|
||||
date__month=month.month).dates('date',
|
||||
'day'):
|
||||
if int(options['verbosity']) >= 3:
|
||||
self.stdout.write(" Day {0}".format(day.day))
|
||||
self.stdout.write(f" Day {day.day}")
|
||||
reset_workout_log(user.id, entry.year, entry.month, day)
|
||||
|
||||
for language in Language.objects.all():
|
||||
|
||||
@@ -43,4 +43,4 @@ class Command(BaseCommand):
|
||||
counter += 1
|
||||
profile.user.delete()
|
||||
|
||||
self.stdout.write("Deleted {0} temporary users".format(counter))
|
||||
self.stdout.write(f"Deleted {counter} temporary users")
|
||||
|
||||
@@ -44,5 +44,5 @@ class Command(BaseCommand):
|
||||
|
||||
# Print the result
|
||||
for i in out:
|
||||
self.stdout.write('msgid "{0}"\n'
|
||||
'msgstr ""\n\n'.format(i))
|
||||
self.stdout.write(f'msgid "{i}"\n'
|
||||
'msgstr ""\n\n')
|
||||
|
||||
@@ -63,7 +63,7 @@ class Language(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"{0} ({1})".format(self.full_name, self.short_name)
|
||||
return f"{self.full_name} ({self.short_name})"
|
||||
|
||||
def get_absolute_url(self):
|
||||
"""
|
||||
@@ -382,7 +382,7 @@ by the US Department of Agriculture. It is extremely complete, with around
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"Profile for user {0}".format(self.user)
|
||||
return f"Profile for user {self.user}"
|
||||
|
||||
@property
|
||||
def use_metric(self):
|
||||
@@ -521,7 +521,7 @@ class UserCache(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"Cache for user {0}".format(self.user)
|
||||
return f"Cache for user {self.user}"
|
||||
|
||||
|
||||
class DaysOfWeek(models.Model):
|
||||
@@ -582,7 +582,7 @@ class License(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"{0} ({1})".format(self.full_name, self.short_name)
|
||||
return f"{self.full_name} ({self.short_name})"
|
||||
|
||||
#
|
||||
# Own methods
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "template_no_context.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{% trans "Forbidden!" %}{% endblock %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "template_no_context.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{% trans "Page not found" %}{% endblock %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "template_no_context.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% block title %}{% trans "An error occured" %}{% endblock %}
|
||||
{% block title %}{% trans "An error occurred" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>{% trans "Something happened that caused an error." %}</p>
|
||||
|
||||
@@ -101,9 +101,9 @@ day in your current workout:{% endblocktrans %} <strong>{{current_workout}}</str
|
||||
{% endif %}
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
– {{ current_workout.creation_date }}
|
||||
<p>
|
||||
<p>
|
||||
– {{ current_workout.creation_date }}
|
||||
<p>
|
||||
{% endif %}
|
||||
|
||||
<table class="table table-hover table-sm">
|
||||
|
||||
@@ -98,9 +98,9 @@
|
||||
<div class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}
|
||||
{% for key, val in response_headers.items %}
|
||||
<b>{{ key }}:</b>
|
||||
<span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>
|
||||
<span class="lit">{{ val|break_long_headers|urlize }}</span>
|
||||
{% endfor %}
|
||||
</div>{{ content|urlize_quoted_links }}
|
||||
</div>{{ content|urlize }}
|
||||
</pre>
|
||||
{% endautoescape %}
|
||||
</div>
|
||||
|
||||
@@ -34,6 +34,7 @@ $(document).ready(function() {
|
||||
<span class="{% fa_class 'edit' %}"></span>
|
||||
{% trans 'Edit' %}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{% url 'manager:day:delete' day.obj.id %}" class="dropdown-item">
|
||||
<span class="{% fa_class 'trash' %}"></span>
|
||||
{% trans 'Delete' %}
|
||||
@@ -50,23 +51,31 @@ $(document).ready(function() {
|
||||
{% for set in day.set_list %}
|
||||
<tr data-id="{{ set.obj.id }}" id="set-{{ set.obj.id }}">
|
||||
<td style="width: 15%;border-right-width: 0px;">
|
||||
<p>#{{ forloop.counter }}</p>
|
||||
<span>#{{ forloop.counter }}</span>
|
||||
|
||||
{% if editable %}
|
||||
<div class="dropdown float-right">
|
||||
<button class="btn btn-link btn-sm btn-block dropdown-toggle " type="button" id="dropdownMenuSet{{set.obj.id}}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuSet{{set.obj.id}}">
|
||||
<a href="{% url 'manager:set:edit' set.obj.id %}" class="wger-modal-dialog dropdown-item">
|
||||
<span class="{% fa_class 'edit' %}"></span>
|
||||
{% trans 'Edit' %}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{% url 'manager:set:delete' set.obj.id %}" class="dropdown-item">
|
||||
<span class="{% fa_class 'trash' %}"></span>
|
||||
{% trans 'Delete' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if false and day.set_list|length > 1 %}
|
||||
<span class="editoptions">
|
||||
{% if day.set_list|length > 1 %}
|
||||
<span class="{% fa_class 'bars' %} dragndrop-handle"></span>
|
||||
<br>
|
||||
{% endif %}
|
||||
<a href="{% url 'manager:set:edit' set.obj.id %}"
|
||||
title="{% trans 'Edit' %}"
|
||||
class="wger-modal-dialog">
|
||||
<span class="{% fa_class 'edit' %}"></span></a>
|
||||
<br>
|
||||
<a href="{% url 'manager:set:delete' set.obj.id %}"
|
||||
title="{% trans 'Delete' %}">
|
||||
<span class="{% fa_class 'trash' %}"></span></a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="border-left-width: 0px;">
|
||||
|
||||
98
wger/core/templates/template_no_context.html
Normal file
98
wger/core/templates/template_no_context.html
Normal file
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
This file is part of wger Workout Manager
|
||||
|
||||
wger is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
wger is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Workout Manager. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
{% load i18n static wger_extras compress django_bootstrap_breadcrumbs %}
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="author" content="Roland Geider">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/workout-manager.css' %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'yarn/bootstrap-compiled.css' %}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap-custom.css' %}">
|
||||
|
||||
<link rel="icon" href="{% static 'images/favicon.png' %}" type="image/png">
|
||||
|
||||
|
||||
{% block header %}{% endblock %}
|
||||
|
||||
<title>{% trans "An error occurred" %}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{# #}
|
||||
{# Navigation #}
|
||||
{# #}
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-wger-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand extra-bold" href="{% url 'core:index' %}">
|
||||
<img src="{% static 'images/logos/logo-bg-white.png' %}" style="width: 38px;">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
|
||||
{# #}
|
||||
{# Main content #}
|
||||
{# #}
|
||||
<div id="main">
|
||||
<div class="container pt-4">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8">
|
||||
<h2 id="page-title" class="page-header">
|
||||
{% block title %}{% endblock %}
|
||||
</h2>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-8 order-2 order-sm-1 mt-3" id="main-content">
|
||||
<div id="content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<footer id="main-footer" class="bg-wger-light border-top">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<ul class="list-unstyled mb-0 pt-2">
|
||||
<li><a href="{% url 'core:about' %}" class="text-muted small">{% trans "About" %}</a></li>
|
||||
<li><a href="{% url 'software:tos' %}" class="text-muted small">{% trans "Terms of service" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="{% static 'yarn/shariff/dist/shariff.min.js' %}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -24,6 +24,7 @@ It will also irrevocably remove all the data associated with it (workouts, logs
|
||||
and can't be undone. {% endblocktrans %}{% endwith %}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2"></div>
|
||||
{% crispy form %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -131,9 +131,9 @@ def render_muscles(muscles=None, muscles_sec=None):
|
||||
except IndexError:
|
||||
front_back = "front" if out_sec[0].is_front else "back"
|
||||
|
||||
backgrounds = ["images/muscles/main/muscle-{}.svg".format(i.id) for i in out_main] \
|
||||
+ ["images/muscles/secondary/muscle-{}.svg".format(i.id) for i in out_sec] \
|
||||
+ ["images/muscles/muscular_system_{}.svg".format(front_back)]
|
||||
backgrounds = [f"images/muscles/main/muscle-{i.id}.svg" for i in out_main] \
|
||||
+ [f"images/muscles/secondary/muscle-{i.id}.svg" for i in out_sec] \
|
||||
+ [f"images/muscles/muscular_system_{front_back}.svg"]
|
||||
|
||||
return {"backgrounds": backgrounds,
|
||||
"empty": False}
|
||||
@@ -146,7 +146,7 @@ def language_select(context, language):
|
||||
"""
|
||||
|
||||
return {'language_name': language[1],
|
||||
'path': 'images/icons/flag-{0}.svg'.format(language[0]),
|
||||
'path': f'images/icons/flag-{language[0]}.svg',
|
||||
'i18n_path': context['i18n_path'][language[0]]}
|
||||
|
||||
|
||||
@@ -158,6 +158,22 @@ def get_item(dictionary, key):
|
||||
return dictionary.get(key)
|
||||
|
||||
|
||||
@register.filter
|
||||
def minus(a, b):
|
||||
"""
|
||||
Simple function that subtracts two values in a template
|
||||
"""
|
||||
return a - b
|
||||
|
||||
|
||||
@register.filter
|
||||
def is_positive(a):
|
||||
"""
|
||||
Simple function that checks whether one value is bigger than the other
|
||||
"""
|
||||
return a > 0
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def fa_class(class_name='', icon_type='fas', fixed_width=True):
|
||||
"""
|
||||
@@ -171,7 +187,7 @@ def fa_class(class_name='', icon_type='fas', fixed_width=True):
|
||||
if not class_name:
|
||||
return css
|
||||
|
||||
css += '{} fa-{}'.format(icon_type, class_name)
|
||||
css += f'{icon_type} fa-{class_name}'
|
||||
|
||||
if fixed_width:
|
||||
css += ' fa-fw'
|
||||
|
||||
@@ -79,14 +79,14 @@ class ApiBaseTestCase(APITestCase):
|
||||
"""
|
||||
Return the URL to use for testing
|
||||
"""
|
||||
return '/api/{0}/{1}/'.format(self.api_version, self.get_resource_name())
|
||||
return f'/api/{self.api_version}/{self.get_resource_name()}/'
|
||||
|
||||
@property
|
||||
def url_detail(self):
|
||||
"""
|
||||
Return the detail URL to use for testing
|
||||
"""
|
||||
return '{0}{1}/'.format(self.url, self.pk)
|
||||
return f'{self.url}{self.pk}/'
|
||||
|
||||
def get_credentials(self, username=None):
|
||||
"""
|
||||
@@ -399,7 +399,7 @@ class ApiPutTestCase(object):
|
||||
#
|
||||
# Currently resources that have a 'user' field 'succeed'
|
||||
if response.status_code == status.HTTP_201_CREATED:
|
||||
# print('201: {0}'.format(self.url_detail))
|
||||
# print(f'201: {self.url_detail}')
|
||||
obj = self.resource.objects.get(pk=response.data['id'])
|
||||
obj2 = self.resource.objects.get(pk=self.pk)
|
||||
self.assertNotEqual(obj.get_owner_object().user.username,
|
||||
@@ -408,7 +408,7 @@ class ApiPutTestCase(object):
|
||||
self.assertEqual(count_before + 1, count_after)
|
||||
|
||||
elif response.status_code == status.HTTP_403_FORBIDDEN:
|
||||
# print('403: {0}'.format(self.url_detail))
|
||||
# print(f'403: {self.url_detail}')
|
||||
self.assertEqual(count_before, count_after)
|
||||
else:
|
||||
# Anonymous user
|
||||
|
||||
@@ -75,13 +75,13 @@ def delete_testcase_add_methods(cls):
|
||||
def test_unauthorized(self):
|
||||
self.user_login(user)
|
||||
self.delete_object(fail=False)
|
||||
setattr(cls, 'test_unauthorized_{0}'.format(user), test_unauthorized)
|
||||
setattr(cls, f'test_unauthorized_{user}', test_unauthorized)
|
||||
|
||||
for user in get_user_list(cls.user_success):
|
||||
def test_authorized(self):
|
||||
self.user_login(user)
|
||||
self.delete_object(fail=False)
|
||||
setattr(cls, 'test_authorized_{0}'.format(user), test_authorized)
|
||||
setattr(cls, f'test_authorized_{user}', test_authorized)
|
||||
|
||||
|
||||
class BaseTestCase(object):
|
||||
@@ -173,7 +173,7 @@ class WgerTestCase(BaseTestCase, TestCase):
|
||||
"""
|
||||
Login the user, by default as 'admin'
|
||||
"""
|
||||
password = '{0}{0}'.format(user)
|
||||
password = f'{user}{user}'
|
||||
self.client.login(username=user, password=password)
|
||||
self.current_user = user
|
||||
self.current_password = password
|
||||
|
||||
@@ -28,7 +28,7 @@ class DaysOfWeekRepresentationTestCase(WgerTestCase):
|
||||
"""
|
||||
Test that the representation of an object is correct
|
||||
"""
|
||||
self.assertEqual("{0}".format(DaysOfWeek.objects.get(pk=1)), 'Monday')
|
||||
self.assertEqual(f"{DaysOfWeek.objects.get(pk=1)}", 'Monday')
|
||||
|
||||
|
||||
class DaysOfWeekApiTestCase(api_base_test.ApiBaseResourceTestCase):
|
||||
|
||||
@@ -84,10 +84,10 @@ class DeleteUserByAdminTestCase(WgerTestCase):
|
||||
self.assertEqual(User.objects.filter(username='test').count(), 1)
|
||||
if fail:
|
||||
self.assertIn(response.status_code, (302, 403),
|
||||
'Unexpected status code for user {0}'.format(self.current_user))
|
||||
f'Unexpected status code for user {self.current_user}')
|
||||
else:
|
||||
self.assertEqual(response.status_code, 200,
|
||||
'Unexpected status code for user {0}'.format(self.current_user))
|
||||
f'Unexpected status code for user {self.current_user}')
|
||||
|
||||
# Wrong admin password
|
||||
if not fail:
|
||||
|
||||
@@ -39,7 +39,7 @@ class LanguageRepresentationTestCase(WgerTestCase):
|
||||
"""
|
||||
Test that the representation of an object is correct
|
||||
"""
|
||||
self.assertEqual("{0}".format(Language.objects.get(pk=1)), 'Deutsch (de)')
|
||||
self.assertEqual(f"{Language.objects.get(pk=1)}", 'Deutsch (de)')
|
||||
|
||||
|
||||
class LanguageOverviewTest(WgerAccessTestCase):
|
||||
|
||||
@@ -34,7 +34,7 @@ class LicenseRepresentationTestCase(WgerTestCase):
|
||||
"""
|
||||
Test that the representation of an object is correct
|
||||
"""
|
||||
self.assertEqual("{0}".format(License.objects.get(pk=1)),
|
||||
self.assertEqual(f"{License.objects.get(pk=1)}",
|
||||
'A cool and free license - Germany (ACAFL - DE)')
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class RepresentationTestCase(WgerTestCase):
|
||||
"""
|
||||
Test that the representation of an object is correct
|
||||
"""
|
||||
self.assertEqual("{0}".format(RepetitionUnit.objects.get(pk=1)), 'Repetitions')
|
||||
self.assertEqual(f"{RepetitionUnit.objects.get(pk=1)}", 'Repetitions')
|
||||
|
||||
|
||||
class OverviewTest(WgerAccessTestCase):
|
||||
|
||||
@@ -29,5 +29,5 @@ class RobotsTxtTestCase(WgerTestCase):
|
||||
|
||||
response = self.client.get(reverse('robots'))
|
||||
for lang in Language.objects.all():
|
||||
self.assertTrue('wger.de/{0}/sitemap.xml'.format(lang.short_name)
|
||||
self.assertTrue(f'wger.de/{lang.short_name}/sitemap.xml'
|
||||
in str(response.content))
|
||||
|
||||
@@ -34,7 +34,7 @@ class RepresentationTestCase(WgerTestCase):
|
||||
"""
|
||||
Test that the representation of an object is correct
|
||||
"""
|
||||
self.assertEqual("{0}".format(WeightUnit.objects.get(pk=1)), 'kg')
|
||||
self.assertEqual(f"{WeightUnit.objects.get(pk=1)}", 'kg')
|
||||
|
||||
|
||||
class OverviewTest(WgerAccessTestCase):
|
||||
|
||||
@@ -90,7 +90,7 @@ class LanguageDeleteView(WgerDeleteMixin, LoginRequiredMixin, PermissionRequired
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(LanguageDeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}?').format(self.object.full_name)
|
||||
context['title'] = _('Delete {0}?').format(self.object.full_name)
|
||||
return context
|
||||
|
||||
|
||||
@@ -108,5 +108,5 @@ class LanguageEditView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredMixi
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(LanguageEditView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object.full_name)
|
||||
context['title'] = _('Edit {0}').format(self.object.full_name)
|
||||
return context
|
||||
|
||||
@@ -81,7 +81,7 @@ class LicenseUpdateView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredMix
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(LicenseUpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object)
|
||||
context['title'] = _('Edit {0}').format(self.object)
|
||||
return context
|
||||
|
||||
|
||||
@@ -100,5 +100,5 @@ class LicenseDeleteView(WgerDeleteMixin, LoginRequiredMixin, PermissionRequiredM
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(LicenseDeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}?').format(self.object)
|
||||
context['title'] = _('Delete {0}?').format(self.object)
|
||||
return context
|
||||
|
||||
@@ -32,7 +32,6 @@ from django.urls import (
|
||||
reverse_lazy
|
||||
)
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
@@ -94,7 +93,6 @@ def demo_entries(request):
|
||||
|
||||
|
||||
@login_required
|
||||
@vary_on_headers('User-Agent')
|
||||
def dashboard(request):
|
||||
"""
|
||||
Show the index page, in our case, the last workout and nutritional plan
|
||||
|
||||
@@ -82,7 +82,7 @@ class UpdateView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredMixin, Upd
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object)
|
||||
context['title'] = _('Edit {0}').format(self.object)
|
||||
return context
|
||||
|
||||
|
||||
@@ -111,5 +111,5 @@ class DeleteView(WgerDeleteMixin, LoginRequiredMixin, PermissionRequiredMixin, D
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(DeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}?').format(self.object)
|
||||
context['title'] = _('Delete {0}?').format(self.object)
|
||||
return context
|
||||
|
||||
@@ -95,6 +95,7 @@ from wger.manager.models import (
|
||||
WorkoutSession
|
||||
)
|
||||
from wger.nutrition.models import NutritionPlan
|
||||
from wger.utils.api_token import create_token
|
||||
from wger.utils.generic_views import (
|
||||
WgerFormMixin,
|
||||
WgerMultiplePermissionRequiredMixin
|
||||
@@ -159,6 +160,7 @@ def delete(request, user_pk=None):
|
||||
else:
|
||||
gym_pk = request.user.userprofile.gym_id
|
||||
return HttpResponseRedirect(reverse('gym:gym:user-list', kwargs={'pk': gym_pk}))
|
||||
form.helper.form_action = request.path
|
||||
context = {'form': form,
|
||||
'user_delete': user}
|
||||
|
||||
@@ -196,13 +198,11 @@ def trainer_login(request, user_pk):
|
||||
or user.has_perm('gym.manage_gyms')):
|
||||
own = True
|
||||
|
||||
# Note: it seems we have to manually set the authentication backend here
|
||||
# - https://docs.djangoproject.com/en/1.6/topics/auth/default/#auth-web-requests
|
||||
# - http://stackoverflow.com/questions/3807777/django-login-without-authenticating
|
||||
# Note: when logging without authenticating, it is necessary to set the
|
||||
# authentication backend
|
||||
if own:
|
||||
del(request.session['trainer.identity'])
|
||||
user.backend = 'django.contrib.auth.backends.ModelBackend'
|
||||
django_login(request, user)
|
||||
django_login(request, user, 'django.contrib.auth.backends.ModelBackend')
|
||||
|
||||
if not own:
|
||||
request.session['trainer.identity'] = orig_user_pk
|
||||
@@ -456,12 +456,10 @@ def api_key(request):
|
||||
try:
|
||||
token = Token.objects.get(user=request.user)
|
||||
except Token.DoesNotExist:
|
||||
token = False
|
||||
if request.GET.get('new_key'):
|
||||
if token:
|
||||
token.delete()
|
||||
token = None
|
||||
|
||||
token = Token.objects.create(user=request.user)
|
||||
if request.GET.get('new_key'):
|
||||
token = create_token(request.user, request.GET.get('new_key'))
|
||||
|
||||
# Redirect to get rid of the GET parameter
|
||||
return HttpResponseRedirect(reverse('core:user:api-key'))
|
||||
|
||||
@@ -82,7 +82,7 @@ class UpdateView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredMixin, Upd
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object)
|
||||
context['title'] = _('Edit {0}').format(self.object)
|
||||
return context
|
||||
|
||||
|
||||
@@ -111,5 +111,5 @@ class DeleteView(WgerDeleteMixin, LoginRequiredMixin, PermissionRequiredMixin, D
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(DeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}?').format(self.object)
|
||||
context['title'] = _('Delete {0}?').format(self.object)
|
||||
return context
|
||||
|
||||
@@ -59,7 +59,7 @@ class ExerciseViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint for exercise objects
|
||||
"""
|
||||
queryset = Exercise.objects.all()
|
||||
queryset = Exercise.objects.accepted()
|
||||
serializer_class = ExerciseSerializer
|
||||
permission_classes = (IsAuthenticatedOrReadOnly, CreateOnlyPermission)
|
||||
ordering_fields = '__all__'
|
||||
|
||||
@@ -43,6 +43,11 @@ from wger.exercises.models import (
|
||||
)
|
||||
|
||||
|
||||
EXERCISE_API = "{0}/api/v2/exercise/?limit=999&status=2"
|
||||
IMAGE_API = "{0}/api/v2/exerciseimage/?exercise={1}"
|
||||
THUMBNAIL_API = "{0}/api/v2/exerciseimage/{1}/thumbnails/"
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
Download exercise images from wger.de and updates the local database
|
||||
@@ -79,40 +84,35 @@ class Command(BaseCommand):
|
||||
except ValidationError:
|
||||
raise CommandError('Please enter a valid URL')
|
||||
|
||||
exercise_api = "{0}/api/v2/exercise/?limit=999&status=2"
|
||||
image_api = "{0}/api/v2/exerciseimage/?exercise={1}"
|
||||
thumbnail_api = "{0}/api/v2/exerciseimage/{1}/thumbnails/"
|
||||
|
||||
headers = {'User-agent': default_user_agent('wger/{} + requests'.format(get_version()))}
|
||||
|
||||
# Get all exercises
|
||||
result = requests.get(exercise_api.format(remote_url), headers=headers).json()
|
||||
result = requests.get(EXERCISE_API.format(remote_url), headers=headers).json()
|
||||
for exercise_json in result['results']:
|
||||
exercise_name = exercise_json['name']
|
||||
exercise_uuid = exercise_json['uuid']
|
||||
exercise_id = exercise_json['id']
|
||||
|
||||
self.stdout.write('')
|
||||
self.stdout.write(u"*** Processing {0} (ID: {1}, UUID: {2})".format(exercise_name,
|
||||
exercise_id,
|
||||
exercise_uuid))
|
||||
self.stdout.write("*** Processing {0} (ID: {1}, UUID: {2})".format(exercise_name,
|
||||
exercise_id,
|
||||
exercise_uuid))
|
||||
|
||||
try:
|
||||
exercise = Exercise.objects.get(uuid=exercise_uuid)
|
||||
|
||||
except Exercise.DoesNotExist:
|
||||
self.stdout.write(' Remote exercise not found in local DB, skipping...')
|
||||
|
||||
continue
|
||||
|
||||
# Get all images
|
||||
images = requests.get(image_api.format(remote_url, exercise_id), headers=headers).json()
|
||||
images = requests.get(IMAGE_API.format(remote_url, exercise_id), headers=headers).json()
|
||||
|
||||
if images['count']:
|
||||
|
||||
for image_json in images['results']:
|
||||
image_id = image_json['id']
|
||||
result = requests.get(thumbnail_api.format(remote_url, image_id),
|
||||
result = requests.get(THUMBNAIL_API.format(remote_url, image_id),
|
||||
headers=headers).json()
|
||||
|
||||
image_name = os.path.basename(result['original'])
|
||||
@@ -129,7 +129,7 @@ class Command(BaseCommand):
|
||||
|
||||
# Save the downloaded image, see link for details
|
||||
# http://stackoverflow.com/questions/1308386/programmatically-saving-image-to-
|
||||
retrieved_image = requests.get(result['original'], headers=headers)
|
||||
retrieved_image = requests.get(remote_url + result['original'], headers=headers)
|
||||
img_temp = NamedTemporaryFile(delete=True)
|
||||
img_temp.write(retrieved_image.content)
|
||||
img_temp.flush()
|
||||
|
||||
@@ -26,7 +26,7 @@ def capitalize_name(apps, schema_editor):
|
||||
def capitalize(input):
|
||||
out = []
|
||||
for word in input.split(' '):
|
||||
if len(word) > 2 and word[0] != u'ß':
|
||||
if len(word) > 2 and word[0] != 'ß':
|
||||
out.append(word[:1].upper() + word[1:])
|
||||
else:
|
||||
out.append(word)
|
||||
|
||||
@@ -328,7 +328,7 @@ class Exercise(AbstractSubmissionModel, AbstractLicenseModel, models.Model):
|
||||
self.license_author = request.user.username
|
||||
|
||||
subject = _('New user submitted exercise')
|
||||
message = _(u'The user {0} submitted a new exercise "{1}".').format(
|
||||
message = _('The user {0} submitted a new exercise "{1}".').format(
|
||||
request.user.username, self.name)
|
||||
mail.mail_admins(str(subject),
|
||||
str(message),
|
||||
@@ -448,7 +448,7 @@ class ExerciseImage(AbstractSubmissionModel, AbstractLicenseModel, models.Model)
|
||||
self.license_author = request.user.username
|
||||
|
||||
subject = _('New user submitted image')
|
||||
message = _(u'The user {0} submitted a new image "{1}" for exercise {2}.').format(
|
||||
message = _('The user {0} submitted a new image "{1}" for exercise {2}.').format(
|
||||
request.user.username,
|
||||
self.name,
|
||||
self.exercise)
|
||||
|
||||
@@ -171,15 +171,21 @@ $(document).ready(function() {
|
||||
{% for comment in comments %}
|
||||
<li>
|
||||
{{ comment }}
|
||||
|
||||
{% if perms.exercises.change_exercisecomment %}
|
||||
<span class="editoptions">
|
||||
<a href="{% url 'exercise:comment:delete' comment.id %}"
|
||||
title="{% trans 'Delete' %}">
|
||||
<span class="{% fa_class 'trash' %}"></span></a>
|
||||
<a href="{% url 'exercise:comment:edit' comment.id %}"
|
||||
title="{% trans 'Edit' %}"
|
||||
class="wger-modal-dialog">
|
||||
<span class="{% fa_class 'edit' %}"></span></a>
|
||||
<span class="dropdown">
|
||||
<button type="button" class="btn btn-link dropdown-toggle btn-sm" data-toggle="dropdown"></button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="{% url 'exercise:comment:edit' comment.id %}" class="dropdown-item wger-modal-dialog">
|
||||
<span class="{% fa_class 'edit' %}"></span>
|
||||
{% trans 'Edit' %}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{% url 'exercise:comment:delete' comment.id %}" class="dropdown-item">
|
||||
<span class="{% fa_class 'trash' %}"></span>
|
||||
{% trans 'Delete' %}
|
||||
</a>
|
||||
</div>
|
||||
</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
@@ -90,7 +90,7 @@ class ExerciseCategoryUpdateView(WgerFormMixin,
|
||||
# Send some additional data to the template
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ExerciseCategoryUpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object.name)
|
||||
context['title'] = _('Edit {0}').format(self.object.name)
|
||||
|
||||
return context
|
||||
|
||||
@@ -118,5 +118,5 @@ class ExerciseCategoryDeleteView(WgerDeleteMixin,
|
||||
# Send some additional data to the template
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ExerciseCategoryDeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}?').format(self.object.name)
|
||||
context['title'] = _('Delete {0}?').format(self.object.name)
|
||||
return context
|
||||
|
||||
@@ -237,7 +237,7 @@ class ExerciseUpdateView(ExercisesEditAddView,
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ExerciseUpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object.name)
|
||||
context['title'] = _('Edit {0}').format(self.object.name)
|
||||
|
||||
return context
|
||||
|
||||
@@ -283,7 +283,7 @@ class ExerciseCorrectView(ExercisesEditAddView, LoginRequiredMixin, UpdateView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ExerciseCorrectView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Correct {0}').format(self.object.name)
|
||||
context['title'] = _('Correct {0}').format(self.object.name)
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
@@ -334,7 +334,7 @@ class ExerciseDeleteView(WgerDeleteMixin,
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(ExerciseDeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}?').format(self.object.name)
|
||||
context['title'] = _('Delete {0}?').format(self.object.name)
|
||||
return context
|
||||
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ class MuscleUpdateView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredMixi
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(MuscleUpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object.name)
|
||||
context['title'] = _('Edit {0}').format(self.object.name)
|
||||
return context
|
||||
|
||||
|
||||
@@ -122,5 +122,5 @@ class MuscleDeleteView(WgerDeleteMixin, LoginRequiredMixin, PermissionRequiredMi
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(MuscleDeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}?').format(self.object.name)
|
||||
context['title'] = _('Delete {0}?').format(self.object.name)
|
||||
return context
|
||||
|
||||
@@ -19,6 +19,10 @@ from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
# Third Party
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Submit
|
||||
|
||||
# wger
|
||||
from wger.core.forms import UserPersonalInformationForm
|
||||
from wger.utils.widgets import BootstrapSelectMultiple
|
||||
@@ -62,6 +66,9 @@ class GymUserPermissionForm(forms.ModelForm):
|
||||
self.fields['role'] = forms.MultipleChoiceField(choices=field_choices,
|
||||
initial=User,
|
||||
widget=BootstrapSelectMultiple())
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_class = 'wger-form'
|
||||
self.helper.add_input(Submit('submit', _("Save"), css_class='btn-success btn-block'))
|
||||
|
||||
|
||||
class GymUserAddForm(GymUserPermissionForm, UserPersonalInformationForm):
|
||||
|
||||
@@ -70,19 +70,19 @@ class Gym(m.Model):
|
||||
null=True)
|
||||
"""Gym owner"""
|
||||
|
||||
zip_code = m.CharField(_(u'ZIP code'),
|
||||
zip_code = m.CharField(_('ZIP code'),
|
||||
max_length=10,
|
||||
blank=True,
|
||||
null=True)
|
||||
"""ZIP code"""
|
||||
|
||||
city = m.CharField(_(u'City'),
|
||||
city = m.CharField(_('City'),
|
||||
max_length=30,
|
||||
blank=True,
|
||||
null=True)
|
||||
"""City"""
|
||||
|
||||
street = m.CharField(_(u'Street'),
|
||||
street = m.CharField(_('Street'),
|
||||
max_length=30,
|
||||
blank=True,
|
||||
null=True)
|
||||
@@ -139,7 +139,7 @@ class GymConfig(m.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return ugettext(u'Configuration for {}'.format(self.gym.name))
|
||||
return ugettext('Configuration for {}'.format(self.gym.name))
|
||||
|
||||
def get_owner_object(self):
|
||||
"""
|
||||
@@ -401,7 +401,7 @@ class ContractType(m.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"{}".format(self.name)
|
||||
return "{}".format(self.name)
|
||||
|
||||
def get_owner_object(self):
|
||||
"""
|
||||
@@ -450,7 +450,7 @@ class ContractOption(m.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"{}".format(self.name)
|
||||
return "{}".format(self.name)
|
||||
|
||||
def get_owner_object(self):
|
||||
"""
|
||||
@@ -571,19 +571,19 @@ class Contract(m.Model):
|
||||
null=True)
|
||||
"""The member's email"""
|
||||
|
||||
zip_code = m.CharField(_(u'ZIP code'),
|
||||
zip_code = m.CharField(_('ZIP code'),
|
||||
max_length=10,
|
||||
blank=True,
|
||||
null=True)
|
||||
"""ZIP code"""
|
||||
|
||||
city = m.CharField(_(u'City'),
|
||||
city = m.CharField(_('City'),
|
||||
max_length=30,
|
||||
blank=True,
|
||||
null=True)
|
||||
"""City"""
|
||||
|
||||
street = m.CharField(_(u'Street'),
|
||||
street = m.CharField(_('Street'),
|
||||
max_length=30,
|
||||
blank=True,
|
||||
null=True)
|
||||
|
||||
@@ -158,7 +158,7 @@ class UpdateView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredMixin, Upd
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object)
|
||||
context['title'] = _('Edit {0}').format(self.object)
|
||||
return context
|
||||
|
||||
|
||||
@@ -194,5 +194,5 @@ class DeleteView(WgerDeleteMixin, LoginRequiredMixin, PermissionRequiredMixin, D
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(DeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}?').format(self.object)
|
||||
context['title'] = _('Delete {0}?').format(self.object)
|
||||
return context
|
||||
|
||||
@@ -65,5 +65,5 @@ class GymConfigUpdateView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredM
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(GymConfigUpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object)
|
||||
context['title'] = _('Edit {0}').format(self.object)
|
||||
return context
|
||||
|
||||
@@ -151,7 +151,7 @@ class UpdateView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredMixin, Upd
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object)
|
||||
context['title'] = _('Edit {0}').format(self.object)
|
||||
return context
|
||||
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class UpdateView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredMixin, Upd
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object)
|
||||
context['title'] = _('Edit {0}').format(self.object)
|
||||
return context
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ class DeleteView(WgerDeleteMixin, LoginRequiredMixin, PermissionRequiredMixin, D
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(DeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}').format(self.object)
|
||||
context['title'] = _('Delete {0}').format(self.object)
|
||||
return context
|
||||
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class UpdateView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredMixin, Upd
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object)
|
||||
context['title'] = _('Edit {0}').format(self.object)
|
||||
return context
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ class DeleteView(WgerDeleteMixin, LoginRequiredMixin, PermissionRequiredMixin, D
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(DeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}').format(self.object)
|
||||
context['title'] = _('Delete {0}').format(self.object)
|
||||
return context
|
||||
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ class UpdateView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredMixin, Upd
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object)
|
||||
context['title'] = _('Edit {0}').format(self.object)
|
||||
return context
|
||||
|
||||
|
||||
@@ -204,5 +204,5 @@ class DeleteView(WgerDeleteMixin, LoginRequiredMixin, PermissionRequiredMixin, D
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(DeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}?').format(self.object)
|
||||
context['title'] = _('Delete {0}?').format(self.object)
|
||||
return context
|
||||
|
||||
@@ -311,11 +311,13 @@ def gym_permissions_user_edit(request, user_pk):
|
||||
form = GymUserPermissionForm(initial={'role': initial_data},
|
||||
available_roles=form_group_permission)
|
||||
|
||||
context = {}
|
||||
context['title'] = member.get_full_name()
|
||||
context['form'] = form
|
||||
context['extend_template'] = 'base_empty.html' if request.is_ajax() else 'base.html'
|
||||
context['submit_text'] = 'Save'
|
||||
# Set form action to absolute path
|
||||
form.helper.form_action = request.path
|
||||
|
||||
context = {'title': member.get_full_name(),
|
||||
'form': form,
|
||||
'extend_template': 'base_empty.html' if request.is_ajax() else 'base.html',
|
||||
'submit_text': 'Save'}
|
||||
|
||||
return render(request, 'form.html', context)
|
||||
|
||||
@@ -446,7 +448,7 @@ class GymUpdateView(WgerFormMixin, LoginRequiredMixin, PermissionRequiredMixin,
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(GymUpdateView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Edit {0}').format(self.object)
|
||||
context['title'] = _('Edit {0}').format(self.object)
|
||||
return context
|
||||
|
||||
|
||||
@@ -471,5 +473,5 @@ class GymDeleteView(WgerDeleteMixin, LoginRequiredMixin, PermissionRequiredMixin
|
||||
Send some additional data to the template
|
||||
"""
|
||||
context = super(GymDeleteView, self).get_context_data(**kwargs)
|
||||
context['title'] = _(u'Delete {0}?').format(self.object)
|
||||
context['title'] = _('Delete {0}?').format(self.object)
|
||||
return context
|
||||
|
||||
@@ -24,7 +24,10 @@ from wger.core.api.serializers import (
|
||||
RepetitionUnitSerializer,
|
||||
WeightUnitSerializer
|
||||
)
|
||||
from wger.exercises.api.serializers import ExerciseSerializer
|
||||
from wger.exercises.api.serializers import (
|
||||
ExerciseSerializer,
|
||||
MuscleSerializer
|
||||
)
|
||||
from wger.manager.models import (
|
||||
Day,
|
||||
Schedule,
|
||||
@@ -115,6 +118,16 @@ class SettingSerializer(serializers.ModelSerializer):
|
||||
#
|
||||
# Custom helper serializers for the canonical form of a workout
|
||||
#
|
||||
class MusclesCanonicalFormSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for the muscles in the canonical form of a day/workout
|
||||
"""
|
||||
front = serializers.ListField(child=MuscleSerializer())
|
||||
back = serializers.ListField(child=MuscleSerializer())
|
||||
frontsecondary = serializers.ListField(child=MuscleSerializer())
|
||||
backsecondary = serializers.ListField(child=MuscleSerializer())
|
||||
|
||||
|
||||
class WorkoutCanonicalFormExerciseListSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for settings in the canonical form of a workout
|
||||
@@ -139,7 +152,7 @@ class WorkoutCanonicalFormExerciseSerializer(serializers.Serializer):
|
||||
exercise_list = WorkoutCanonicalFormExerciseListSerializer(many=True)
|
||||
has_settings = serializers.BooleanField()
|
||||
is_superset = serializers.BooleanField()
|
||||
muscles = serializers.ReadOnlyField()
|
||||
muscles = MusclesCanonicalFormSerializer()
|
||||
|
||||
|
||||
class DaysOfWeekCanonicalFormSerializer(serializers.Serializer):
|
||||
@@ -159,7 +172,7 @@ class DayCanonicalFormSerializer(serializers.Serializer):
|
||||
obj = DaySerializer()
|
||||
set_list = WorkoutCanonicalFormExerciseSerializer(many=True)
|
||||
days_of_week = DaysOfWeekCanonicalFormSerializer()
|
||||
muscles = serializers.ReadOnlyField()
|
||||
muscles = MusclesCanonicalFormSerializer()
|
||||
|
||||
|
||||
class WorkoutCanonicalFormSerializer(serializers.Serializer):
|
||||
@@ -167,5 +180,5 @@ class WorkoutCanonicalFormSerializer(serializers.Serializer):
|
||||
Serializer for the canonical form of a workout
|
||||
"""
|
||||
obj = WorkoutSerializer()
|
||||
muscles = serializers.ReadOnlyField()
|
||||
day_list = DayCanonicalFormSerializer(many=True)
|
||||
muscles = MusclesCanonicalFormSerializer()
|
||||
|
||||
@@ -209,7 +209,7 @@ class WorkoutScheduleDownloadForm(Form):
|
||||
Form for the workout schedule download
|
||||
"""
|
||||
pdf_type = ChoiceField(
|
||||
label=ugettext_lazy(u"Type"),
|
||||
label=ugettext_lazy("Type"),
|
||||
choices=(("log", ugettext_lazy("Log")),
|
||||
("table", ugettext_lazy("Table")))
|
||||
)
|
||||
|
||||
@@ -37,7 +37,11 @@ from reportlab.platypus import (
|
||||
|
||||
# wger
|
||||
from wger.utils.helpers import normalize_decimal
|
||||
from wger.utils.pdf import styleSheet
|
||||
from wger.utils.pdf import (
|
||||
header_colour,
|
||||
row_color,
|
||||
styleSheet
|
||||
)
|
||||
|
||||
|
||||
def render_workout_day(day, nr_of_weeks=7, images=False, comments=False, only_table=False):
|
||||
@@ -66,20 +70,13 @@ def render_workout_day(day, nr_of_weeks=7, images=False, comments=False, only_ta
|
||||
day_markers = []
|
||||
group_exercise_marker = {}
|
||||
|
||||
# Background colour for days
|
||||
# Reportlab doesn't use the HTML hexadecimal format, but has a range of
|
||||
# 0 till 1, so we have to convert here.
|
||||
header_colour = colors.Color(int('73', 16) / 255.0,
|
||||
int('8a', 16) / 255.0,
|
||||
int('5f', 16) / 255.0)
|
||||
|
||||
set_count = 1
|
||||
day_markers.append(len(data))
|
||||
|
||||
p = Paragraph(u'<para align="center">%(days)s: %(description)s</para>' %
|
||||
p = Paragraph('<para align="center">%(days)s: %(description)s</para>' %
|
||||
{'days': day['days_of_week']['text'],
|
||||
'description': day['obj'].description},
|
||||
styleSheet["Bold"])
|
||||
styleSheet["SubHeader"])
|
||||
|
||||
data.append([p])
|
||||
|
||||
@@ -100,7 +97,7 @@ def render_workout_day(day, nr_of_weeks=7, images=False, comments=False, only_ta
|
||||
# Process the settings
|
||||
if exercise['has_weight']:
|
||||
setting_out = []
|
||||
for i in exercise['setting_text'].split(u'–'):
|
||||
for i in exercise['setting_text'].split('–'):
|
||||
setting_out.append(Paragraph(i, styleSheet["Small"], bulletText=''))
|
||||
else:
|
||||
setting_out = Paragraph(exercise['setting_text'], styleSheet["Small"])
|
||||
@@ -138,7 +135,7 @@ def render_workout_day(day, nr_of_weeks=7, images=False, comments=False, only_ta
|
||||
bulletFontSize=3,
|
||||
start='square')]
|
||||
|
||||
data.append([set_count,
|
||||
data.append([f"#{set_count}",
|
||||
exercise_content,
|
||||
setting_out]
|
||||
+ [''] * nr_of_weeks)
|
||||
@@ -176,7 +173,7 @@ def render_workout_day(day, nr_of_weeks=7, images=False, comments=False, only_ta
|
||||
# list
|
||||
for i in range(exercise_start, len(data) + 1):
|
||||
if not i % 2:
|
||||
table_style.append(('BACKGROUND', (1, i - 1), (-1, i - 1), colors.lavender))
|
||||
table_style.append(('BACKGROUND', (0, i - 1), (-1, i - 1), row_color))
|
||||
|
||||
# Put everything together and manually set some of the widths
|
||||
t = Table(data, style=table_style)
|
||||
@@ -217,7 +214,7 @@ def reps_smart_text(settings, set_obj):
|
||||
if setting.repetition_unit_id != 2:
|
||||
reps = "{0} {1}".format(setting.reps, rep_unit).strip()
|
||||
else:
|
||||
reps = u'∞'
|
||||
reps = '∞'
|
||||
return reps
|
||||
|
||||
def get_weight_unit_reprentation(setting):
|
||||
@@ -262,8 +259,8 @@ def reps_smart_text(settings, set_obj):
|
||||
weight_unit = settings[0].weight_unit
|
||||
weight = normalize_weight(settings[0])
|
||||
|
||||
setting_text = u'{0} × {1}'.format(set_obj.sets, reps).strip()
|
||||
setting_list_text = u'{0} {1}'.format(reps, rep_unit).strip()
|
||||
setting_text = '{0} × {1}'.format(set_obj.sets, reps).strip()
|
||||
setting_list_text = '{0} {1}'.format(reps, rep_unit).strip()
|
||||
if weight:
|
||||
setting_text += ' ({0} {1})'.format(weight, weight_unit)
|
||||
setting_list_text += ' ({0} {1})'.format(weight, weight_unit)
|
||||
@@ -296,7 +293,7 @@ def reps_smart_text(settings, set_obj):
|
||||
tmp_repetition_unit.append(setting.repetition_unit)
|
||||
tmp_weight_unit.append(setting.weight_unit)
|
||||
|
||||
setting_text = u' – '.join(tmp_reps_text)
|
||||
setting_text = ' – '.join(tmp_reps_text)
|
||||
setting_list = tmp_reps_text
|
||||
repetition_units = tmp_repetition_unit
|
||||
weight_units = tmp_weight_unit
|
||||
|
||||
@@ -97,9 +97,9 @@ class Workout(models.Model):
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
if self.comment:
|
||||
return u"{0}".format(self.comment)
|
||||
return "{0}".format(self.comment)
|
||||
else:
|
||||
return u"{0} ({1})".format(_('Workout'), self.creation_date)
|
||||
return "{0} ({1})".format(_('Workout'), self.creation_date)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
@@ -464,16 +464,16 @@ class Day(models.Model):
|
||||
|
||||
# Muscles for this set
|
||||
for muscle in exercise.muscles.all():
|
||||
if muscle.is_front and muscle.id not in muscles_front:
|
||||
muscles_front.append(muscle.id)
|
||||
elif not muscle.is_front and muscle.id not in muscles_back:
|
||||
muscles_back.append(muscle.id)
|
||||
if muscle.is_front and muscle not in muscles_front:
|
||||
muscles_front.append(muscle)
|
||||
elif not muscle.is_front and muscle not in muscles_back:
|
||||
muscles_back.append(muscle)
|
||||
|
||||
for muscle in exercise.muscles_secondary.all():
|
||||
if muscle.is_front and muscle.id not in muscles_front:
|
||||
muscles_front_secondary.append(muscle.id)
|
||||
if muscle.is_front and muscle not in muscles_front:
|
||||
muscles_front_secondary.append(muscle)
|
||||
elif not muscle.is_front and muscle.id not in muscles_back:
|
||||
muscles_back_secondary.append(muscle.id)
|
||||
muscles_back_secondary.append(muscle)
|
||||
|
||||
for setting in Setting.objects.filter(set=set_obj,
|
||||
exercise=exercise).order_by('order', 'id'):
|
||||
@@ -546,7 +546,7 @@ class Day(models.Model):
|
||||
|
||||
return {'obj': self,
|
||||
'days_of_week': {
|
||||
'text': u', '.join([str(_(i.day_of_week))
|
||||
'text': ', '.join([str(_(i.day_of_week))
|
||||
for i in tmp_days_of_week]),
|
||||
'day_list': tmp_days_of_week},
|
||||
'muscles': {
|
||||
@@ -585,7 +585,7 @@ class Set(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"Set-ID {0}".format(self.id)
|
||||
return "Set-ID {0}".format(self.id)
|
||||
|
||||
def get_owner_object(self):
|
||||
"""
|
||||
@@ -666,7 +666,7 @@ class Setting(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"settings for exercise {0} in set {1}".format(self.exercise.id, self.set.id)
|
||||
return "settings for exercise {0} in set {1}".format(self.exercise.id, self.set.id)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
@@ -751,9 +751,9 @@ class WorkoutLog(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"Log entry: {0} - {1} kg on {2}".format(self.reps,
|
||||
self.weight,
|
||||
self.date)
|
||||
return "Log entry: {0} - {1} kg on {2}".format(self.reps,
|
||||
self.weight,
|
||||
self.date)
|
||||
|
||||
def get_owner_object(self):
|
||||
"""
|
||||
@@ -869,7 +869,7 @@ class WorkoutSession(models.Model):
|
||||
"""
|
||||
Return a more human-readable representation
|
||||
"""
|
||||
return u"{0} - {1}".format(self.workout, self.date)
|
||||
return "{0} - {1}".format(self.workout, self.date)
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
|
||||
@@ -56,18 +56,18 @@
|
||||
{{log.weight}} {% trans log.weight_unit.name %}
|
||||
|
||||
{% if is_owner %}
|
||||
<span class="editoptions dropdown">
|
||||
<span class="dropdown">
|
||||
<div class="float-right">
|
||||
<button type="button" class="btn btn-light dropdown-toggle btn-sm" data-toggle="dropdown">
|
||||
<span class="{% fa_class 'cog' %}"></span>
|
||||
<button type="button" class="btn btn-link dropdown-toggle btn-sm" data-toggle="dropdown">
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a href="{% url 'manager:log:edit' log.pk %}"
|
||||
title="{% trans 'Edit' %}"
|
||||
class="wger-modal-dialog dropdown-item">{% trans 'Edit' %}</a>
|
||||
<a href="{% url 'manager:log:delete' log.pk %}"
|
||||
title="{% trans 'Delete' %}"
|
||||
class="wger-modal-dialog dropdown-item">{% trans 'Delete' %}</a>
|
||||
<a href="{% url 'manager:log:edit' log.pk %}" class="wger-modal-dialog dropdown-item">
|
||||
{% trans 'Edit' %}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{% url 'manager:log:delete' log.pk %}" class="wger-modal-dialog dropdown-item">
|
||||
{% trans 'Delete' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -65,7 +65,7 @@ $(document).ready(function() {
|
||||
{{ exercise }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-body px-0 py-0">
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for log in logs %}
|
||||
<li class="list-group-item">
|
||||
@@ -77,21 +77,19 @@ $(document).ready(function() {
|
||||
{{log.weight}} {% trans log.weight_unit.name %}
|
||||
|
||||
{% if is_owner %}
|
||||
<span class="editoptions">
|
||||
<div class="dropdown float-right">
|
||||
<button type="button" class="btn btn-light dropdown-toggle btn-sm" data-toggle="dropdown">
|
||||
<span class="{% fa_class 'cog' %}"></span>
|
||||
<button type="button" class="btn btn-link dropdown-toggle btn-sm" data-toggle="dropdown">
|
||||
</button>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<a href="{% url 'manager:log:edit' log.pk %}"
|
||||
title="{% trans 'Edit' %}"
|
||||
class="wger-modal-dialog dropdown-item">{% trans 'Edit' %}</a>
|
||||
<a href="{% url 'manager:log:delete' log.pk %}"
|
||||
title="{% trans 'Delete' %}"
|
||||
class="wger-modal-dialog dropdown-item">{% trans 'Delete' %}</a>
|
||||
<div class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<a href="{% url 'manager:log:edit' log.pk %}" class="wger-modal-dialog dropdown-item">
|
||||
{% trans 'Edit' %}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{% url 'manager:log:delete' log.pk %}" class="wger-modal-dialog dropdown-item">
|
||||
{% trans 'Delete' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{% load i18n wger_extras %}
|
||||
|
||||
<div class="btn-group btn-group-justified" role="group">
|
||||
<a href="{{value.workout.get_absolute_url}}" class="btn btn-light">{{ value.workout }}</a>
|
||||
<a href="{% url 'manager:log:log' value.workout.id %}" class="btn btn-light">{% trans "Log overview" %}</a>
|
||||
<a href="{{value.workout.get_absolute_url}}" class="btn btn-link">{{ value.workout }}</a>
|
||||
<a href="{% url 'manager:log:log' value.workout.id %}" class="btn btn-link">{% trans "Log overview" %}</a>
|
||||
|
||||
{% if is_owner %}
|
||||
{% if value.session %}
|
||||
<div class="btn-group">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-light dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button type="button" class="btn btn-link dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{% trans "Workout session" %}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
@@ -17,6 +17,7 @@
|
||||
<span class="{% fa_class 'edit-o' %}"></span>
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{% url 'manager:session:delete' value.session.pk %}" class="wger-modal-dialog dropdown-item">
|
||||
<span class="{% fa_class 'trash' %}"></span>
|
||||
{% trans "Delete" %}
|
||||
|
||||
@@ -98,16 +98,10 @@ $(document).ready(function () {
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div id="muscle-front"
|
||||
class="muscle-background"
|
||||
style="width: 120px; height: 220px; background-size: 120px; background-image: {% for background in muscle_backgrounds_front %}url({% static background %}){% if not forloop.last %},{% endif %}{% endfor %};">
|
||||
</div>
|
||||
{% render_muscles muscles.front muscles.frontsecondary %}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div id="muscle-back"
|
||||
class="muscle-background"
|
||||
style="width: 120px; height: 220px; background-size: 120px; background-image: {% for background in muscle_backgrounds_back %}url({% static background %}){% if not forloop.last %},{% endif %}{% endfor %};">
|
||||
</div>
|
||||
{% render_muscles muscles.back muscles.backsecondary %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -125,9 +119,9 @@ $(document).ready(function () {
|
||||
application for example google calendar, outlook or iCal. This will create
|
||||
an appointment for each training day with the appropriate exercises.{% endblocktrans %}</p>
|
||||
<p>
|
||||
<a href="{% url 'manager:workout:ical' workout.id uid token %}" class="btn btn-block btn-light">
|
||||
{% trans "Export calendar file" %}
|
||||
</a>
|
||||
<a href="{% url 'manager:workout:ical' workout.id uid token %}" class="btn btn-block btn-light">
|
||||
{% trans "Export calendar file" %}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -227,6 +221,7 @@ an appointment for each training day with the appropriate exercises.{% endblockt
|
||||
{% trans "Make a copy of this workout" %}
|
||||
</a>
|
||||
{% if is_owner %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="{% url 'manager:workout:delete' workout.id %}" class="dropdown-item wger-modal-dialog">
|
||||
<span class="{% fa_class 'trash' %}"></span>
|
||||
{% trans "Delete" %}
|
||||
|
||||
@@ -43,8 +43,8 @@ class WorkoutPdfLogExportTestCase(WgerTestCase):
|
||||
'attachment; filename=Workout-3-log.pdf')
|
||||
|
||||
# Approximate size only
|
||||
self.assertGreater(int(response['Content-Length']), 29000)
|
||||
self.assertLess(int(response['Content-Length']), 35000)
|
||||
self.assertGreater(int(response['Content-Length']), 38000)
|
||||
self.assertLess(int(response['Content-Length']), 42000)
|
||||
|
||||
def export_pdf_token_wrong(self):
|
||||
"""
|
||||
@@ -75,8 +75,8 @@ class WorkoutPdfLogExportTestCase(WgerTestCase):
|
||||
'attachment; filename=Workout-3-log.pdf')
|
||||
|
||||
# Approximate size only
|
||||
self.assertGreater(int(response['Content-Length']), 29000)
|
||||
self.assertLess(int(response['Content-Length']), 35000)
|
||||
self.assertGreater(int(response['Content-Length']), 38000)
|
||||
self.assertLess(int(response['Content-Length']), 42000)
|
||||
|
||||
def export_pdf_with_comments(self, fail=False):
|
||||
"""
|
||||
@@ -95,8 +95,8 @@ class WorkoutPdfLogExportTestCase(WgerTestCase):
|
||||
'attachment; filename=Workout-3-log.pdf')
|
||||
|
||||
# Approximate size only
|
||||
self.assertGreater(int(response['Content-Length']), 29000)
|
||||
self.assertLess(int(response['Content-Length']), 35000)
|
||||
self.assertGreater(int(response['Content-Length']), 38000)
|
||||
self.assertLess(int(response['Content-Length']), 42000)
|
||||
|
||||
def export_pdf_with_images(self, fail=False):
|
||||
"""
|
||||
@@ -115,8 +115,8 @@ class WorkoutPdfLogExportTestCase(WgerTestCase):
|
||||
'attachment; filename=Workout-3-log.pdf')
|
||||
|
||||
# Approximate size only
|
||||
self.assertGreater(int(response['Content-Length']), 29000)
|
||||
self.assertLess(int(response['Content-Length']), 35000)
|
||||
self.assertGreater(int(response['Content-Length']), 38000)
|
||||
self.assertLess(int(response['Content-Length']), 42000)
|
||||
|
||||
def export_pdf_with_images_and_comments(self, fail=False):
|
||||
"""
|
||||
@@ -136,8 +136,8 @@ class WorkoutPdfLogExportTestCase(WgerTestCase):
|
||||
'attachment; filename=Workout-3-log.pdf')
|
||||
|
||||
# Approximate size only
|
||||
self.assertGreater(int(response['Content-Length']), 29000)
|
||||
self.assertLess(int(response['Content-Length']), 35000)
|
||||
self.assertGreater(int(response['Content-Length']), 38000)
|
||||
self.assertLess(int(response['Content-Length']), 42000)
|
||||
|
||||
def test_export_pdf_anonymous(self):
|
||||
"""
|
||||
@@ -191,8 +191,8 @@ class WorkoutPdfTableExportTestCase(WgerTestCase):
|
||||
'attachment; filename=Workout-3-table.pdf')
|
||||
|
||||
# Approximate size only
|
||||
self.assertGreater(int(response['Content-Length']), 29000)
|
||||
self.assertLess(int(response['Content-Length']), 35000)
|
||||
self.assertGreater(int(response['Content-Length']), 38000)
|
||||
self.assertLess(int(response['Content-Length']), 42000)
|
||||
|
||||
def export_pdf_token_wrong(self):
|
||||
"""
|
||||
@@ -224,8 +224,8 @@ class WorkoutPdfTableExportTestCase(WgerTestCase):
|
||||
'attachment; filename=Workout-3-table.pdf')
|
||||
|
||||
# Approximate size only
|
||||
self.assertGreater(int(response['Content-Length']), 29000)
|
||||
self.assertLess(int(response['Content-Length']), 35000)
|
||||
self.assertGreater(int(response['Content-Length']), 38000)
|
||||
self.assertLess(int(response['Content-Length']), 42000)
|
||||
|
||||
def test_export_pdf_anonymous(self):
|
||||
"""
|
||||
|
||||
@@ -178,15 +178,12 @@ class ScheduleTestCase(WgerTestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotContains(response, 'This schedule is a loop')
|
||||
|
||||
# Commented out since travis was seemingly randomly failing this. See #468
|
||||
#
|
||||
# def test_schedule_detail_page_owner(self):
|
||||
# """
|
||||
# Tests the schedule detail page as the owning user
|
||||
# """
|
||||
#
|
||||
# self.user_login()
|
||||
# self.schedule_detail_page()
|
||||
def test_schedule_detail_page_owner(self):
|
||||
"""
|
||||
Tests the schedule detail page as the owning user
|
||||
"""
|
||||
self.user_login()
|
||||
self.schedule_detail_page()
|
||||
|
||||
def test_schedule_overview(self):
|
||||
"""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user