Merge branch 'master' into groups

# Conflicts:
#	requirements.txt
This commit is contained in:
Roland Geider
2020-10-18 20:33:32 +02:00
152 changed files with 1742 additions and 992 deletions

View File

@@ -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

View File

@@ -1,7 +1,8 @@
{
"env": {
"browser": true,
"jquery": true
"jquery": true,
"es6": true
},
"globals": {
"d3": true,

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
-----------

View File

@@ -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
View 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 (ˈɡɐ) 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/>
![Workout plan](https://raw.githubusercontent.com/wger-project/wger/master/wger/software/static/images/workout.png)
## 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.

View File

@@ -1,166 +0,0 @@
wger
====
wger (ˈɡɐ) 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.

View File

@@ -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

View File

@@ -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>``.

View File

@@ -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'),
]

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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::

View File

@@ -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.

View File

@@ -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

View File

@@ -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"]

View File

@@ -1,12 +1,17 @@
Demo image for wger
===================
Thank you for downloading wger Workout Manager. wger (ˈɡɐ) 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 (ˈɡɐ) 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
-------

View File

@@ -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

View File

@@ -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
-------

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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 \

View File

@@ -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"]

View File

@@ -1,10 +1,15 @@
# Development image for wger
wger (ˈɡɐ) 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 (ˈɡɐ) 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

View File

@@ -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

View File

@@ -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 = '*'

View File

@@ -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()

View File

@@ -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")

View File

@@ -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"

View File

@@ -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.

View File

@@ -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
View File

@@ -0,0 +1,11 @@
#
# Requirements for wger for production
#
# Regular packages
-r requirements.txt
gunicorn~=20.0
psycopg2-binary
django-redis

View File

@@ -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

View File

@@ -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',

View File

@@ -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:]

View File

@@ -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):
"""

View File

@@ -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

View File

@@ -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

View File

@@ -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"
),

View File

@@ -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():

View File

@@ -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")

View File

@@ -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')

View File

@@ -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

View File

@@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "template_no_context.html" %}
{% load i18n static %}
{% block title %}{% trans "Forbidden!" %}{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "template_no_context.html" %}
{% load i18n static %}
{% block title %}{% trans "Page not found" %}{% endblock %}

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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;">

View 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>

View File

@@ -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 %}

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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:

View File

@@ -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):

View File

@@ -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)')

View File

@@ -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):

View File

@@ -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))

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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'))

View File

@@ -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

View File

@@ -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__'

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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")))
)

View File

@@ -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

View File

@@ -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:
"""

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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" %}

View File

@@ -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" %}

View File

@@ -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):
"""

View File

@@ -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